X509Certificate


證書(Certificate,也稱public-key certificate)是用某種簽名算法對某些內容(比如公鑰)進行數字簽名后得到的、可以用來當成信任關系中介的數字憑證。證書發行機構通過發行證書告知證書使用者或實體其公鑰(public-key)以及其它一些輔助信息。證書在電子商務安全交易中有着廣泛的應用,證書發行機構也稱CA(Certificate Authority)。

應用證書


證書在公鑰加密應用中的作用是保證公鑰在某些可信的機構發布,其在協議SSL、電子交易協議SET等方面有重要的應用。圖1顯示了一個最簡單的證書應用方法:



圖1 證書應用方法


證書的應用步驟是:

(1) A把自己的公鑰PKA送到CA(Certificate Authority);

(2) CA用自己的私鑰和A的公鑰生成A的證書,證書內包括CA的數字簽名。簽名對象包括需要在證書中說明的內容,比如A的公鑰、時間戳、序列號等,為了簡化這里不妨假設證書中只有三項內容:A的公鑰PKA、時間戳TIME1、序列號IDA。那么CA發送給A的簡單證書憑證可表達為:CertA=Eca[TIME1,IDA,PKA];

(3) B同樣把自己的公鑰PKB送到CA;

(4) B得到CA發布的證書CertB;

(5) A告知B證書CertA;

(6) B告知A證書CertB。

A、B各自得到對方證書后,利用從CA得到的公鑰(在CA的自簽證書中)驗證彼此對方的證書是否有效,如果有效,那么就得到了彼此的公鑰。利用對方的公鑰,可以加密數據,也可以用來驗證對方的數字簽名。

本文為了方便說明,並沒有使用從CA獲得的證書,而是通信雙方各自產生自簽證書,也就是說圖1的A和B並沒有經過CA,不過前提是A和B之間是互相擁有對方的證書。

證書的內容和意義如表1所示(這里以通用X .509證書格式為例)。

表1 證書內容和意義


證書內容 意義
Version 告訴這個X.509證書是哪個版本的,目前有v1、V2、v3
Serial Number 由證書分發機構設置證書的序列號
Signature Algorithm Identifier 證書采用什么樣的簽名算法
Issuer Name 證書發行者名,也就是給這個證書簽名的機構名
Validity Period 證書有效時間范圍
Subject Name 被證書發行機構簽名后的公鑰擁有者或實體的名字,采用X.500協議,在Internet上的標志是惟一的。例如:CN=Java,OU=Infosec,O=Infosec Lab,C=CN表示一個subject name。


對證書的詳細定義及其應用相關的各種協議,這里不加詳細說明,詳細細節請查看RFC2450、RFC2510、RFC2511、RFC2527、RFC2528、RFC2559、RFC2560、RFC2585、RFC2587等文檔。

生成自簽證書



個人或機構可以從信任的證書分發機構申請得到證書,比如說,可以從http://ca.pku.edu.cn 得到一個屬於個人的證書。這里可以利用J2SDK的安全工具keytool手工產生自簽證書,所謂自簽證書是指證書中的“Subject Name”和“Issuer Name”相同的證書。

下面產生一個自簽證書。安裝完J2SDK(這里用的是J2SDK1.4)后,在J2SDK安裝目錄的bin目錄下,有一個keytool的可執行程序。利用keytool產生自簽證書的步驟如下:

第一步,用-genkey命令選項,產生公私密鑰對。在控制台界面輸入:keytool -genkey -alias testkeypair -keyalg RSA -keysize 1024 -sigalg MD5withRSA。這里的-alias表示使用這對公私密鑰產生新的keystore入口的別名(keystore是用來存放管理密鑰對和證書鏈的,缺省位置是在使用者主目錄下,以.keystore為名的隱藏文件,當然也可指定某個路徑存放.keystore文件);-keyalg是產生公私鑰對所用的算法,這里是RSA;-keysize定義密鑰的長度;-sigalg是簽名算法,選擇MD5withRSA,即用RSA簽名,然后用MD5哈希算法摘要。接下來,系統會提示進行一些輸入:

輸入keystore密碼:  abc123
您的名字與姓氏是什么?
  [Unknown]:  Li
您的組織單位名稱是什么?
  [Unknown]:  InfosecLab
您的組織名稱是什么?
  [Unknown]:  InfosecLab Group
您所在的城市或區域名稱是什么?
  [Unknown]:  Beijing
您所在的州或省份名稱是什么?
  [Unknown]:  Beijing
該單位的兩字母國家代碼是什么
  [Unknown]:  CN
CN=Li, OU=InfosecLab, O=InfosecLab Group, L=Beijing, ST=Beijing, C=CN 正確嗎?
[否]:  y
輸入<testkeypair>的主密碼 (如果和 keystore 密碼相同,按回車):


第二步,產生自簽證書,輸入以下命令:

keytool -selfcert -alias testkeypair -dname "CN=Li, OU=InfosecLab, O=InfosecLab 
Group, L=Beijing, ST=Beijing, C=CN" 
輸入keystore密碼:  abc123


第三步,導出自簽證書,由上面兩步產生的證書,已經存放在以“testkeypair”為別名的keystore入口了,如果使用其文件,必須導出證書。輸入:

keytool -export -rfc -alias testkeypair -file mycert.crt  
輸入keystore密碼:  abc123
保存在文件中的認證 <mycert.crt>


這樣,就得到了一個自簽的證書mycert.crt。注意,選項rfc是把證書輸出為RFC1421定義的、用Base64最終編碼的格式。

讀取證書



Java為安全應用提供了豐富的API,J2SDK1.4 的JSSE (JavaTM Secure Socket Extension) 包括javax.security.certificate包,並且提供對證書的操作方法。而對證書的讀操作,只用java.security.cert. CertificateFactory和java.security.cert.X509Certificate就可以了。下面是讀取證書內容的部分代碼:

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.table.*;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.io.*;
public class CARead extends JPanel {
 private String CA_Name; 
 private String CA_ItemData[][] = new String[9][2];
 private String[] columnNames = {"證書字段標記","內容" };
 public CARead(String CertName) {
  CA_Name=CertName;
  /* 三個Panel用來顯示證書內容*/
  JTabbedPane tabbedPane = new JTabbedPane();
  JPanel panelNormal = new JPanel();
  tabbedPane.addTab("普通信息", panelNormal);   
  JPanel panelAll=new JPanel();
  panelAll.setLayout(new BorderLayout());
  tabbedPane.addTab("所有信息",panelAll);
  JPanel panelBase64=new JPanel();
  panelBase64.setLayout(new BorderLayout());
  tabbedPane.addTab("Base64編碼信息",panelBase64);
  /* 讀取證書常規信息 */
  Read_Normal(panelNormal);
  /* 讀取證書文件字符串表示內容 */
  Read_Bin(panelAll);
  /* 讀取證原始Base64編碼形式的證書文件 */
  Read_Raw(panelBase64);
  tabbedPane.setSelectedIndex(0);
  setLayout(new GridLayout(1, 1)); 
  add(tabbedPane);
 }
  /*以下是定義的Read_Normal(),Read_Bin(),Read_Raw()以及main() 
  這里省略...   */  
}



定義證書信息的讀取函數如下:

private int Read_Normal(JPanel panel){
 String Field;
 try{
  CertificateFactory certificate_factory=CertificateFactory.getInstance("X.509");
  FileInputStream file_inputstream=new FileInputStream(CA_Name);
  X509Certificate 
x509certificate=(X509Certificate)certificate_factory.generateCertificate
(file_inputstream);
  Field=x509certificate.getType();
  CA_ItemData[0][0]="類型";
  CA_ItemData[0][1]=Field;
  Field=Integer.toString(x509certificate.getVersion());
  CA_ItemData[1][0]="版本";
  CA_ItemData[1][1]=Field;	
  Field=x509certificate.getSubjectDN().getName();
  CA_ItemData[2][0]="標題";
  CA_ItemData[2][1]=Field;
  /* 以下類似,這里省略 
  Field=x509certificate.getNotBefore().toString();得到開始有效日期
  Field=x509certificate. getNotAfter().toString();得到截止日期
  Field=x509certificate.getSerialNumber().toString(16);得到序列號
  Field=x509certificate.getIssuerDN().getName();得到發行者名
  Field=x509certificate.getSigAlgName();得到簽名算法
  Field=x509certificate.getPublicKey().getAlgorithm();得到公鑰算法 */
  file_inputstream.close();
  final JTable table = new JTable(CA_ItemData, columnNames);
  TableColumn tc=null;
  tc = table.getColumnModel().getColumn(1);
  tc.setPreferredWidth(600); 
  panel.add(table);
 }catch(Exception exception){
  exception.printStackTrace();
  return -1;
 }
 return 0;
}



如果以字符串形式讀取證書,加入下面Read_Bin這個函數。其中CertificateFactory.generateCertificate() 這個函數可以從證書標准編碼(RFC1421定義)中解出可讀信息。Read_Bin函數代碼如下:

private int Read_Bin(JPanel panel){
 try{
  FileInputStream file_inputstream=new FileInputStream(CA_Name);
  DataInputStream data_inputstream=new DataInputStream(file_inputstream);
  CertificateFactory certificatefactory=CertificateFactory.getInstance("X.509");
  byte[] bytes=new byte[data_inputstream.available()];
  data_inputstream.readFully(bytes);
  ByteArrayInputStream bais=new ByteArrayInputStream(bytes);
  JEditorPane Cert_EditorPane;
  Cert_EditorPane=new JEditorPane();
  while(bais.available()>0){
  X509Certificate 
Cert=(X509Certificate)certificatefactory.generateCertificate(bais);
  Cert_EditorPane.setText(Cert_EditorPane.getText()+Cert.toString());
 }
 Cert_EditorPane.disable();
 JScrollPane edit_scroll=new JScrollPane(Cert_EditorPane);
 panel.add(edit_scroll);
 file_inputstream.close();
 data_inputstream.close();
 }catch( Exception exception){
  exception.printStackTrace();
  return -1;
 }
 return 0;	
}



如果要得到原始證書編碼后的信息,則可用如下代碼:

private int Read_Raw(JPanel panel){
 try{		
  JEditorPane Cert_EditorPane=new JEditorPane();
  String CertText=null;
  File inputFile = new File(CA_Name);
  FileReader in = new FileReader(inputFile);
  char[] buf=new char[2000];
  int len=in.read(buf,0,2000);
  for(int i=1;i<len;i++) 
  {   
   CertText=CertText+buf[i];
  }
  in.close();
  Cert_EditorPane.setText(CertText);
  Cert_EditorPane.disable();
  JScrollPane edit_scroll=new JScrollPane(Cert_EditorPane);
  panel.add(edit_scroll);
 }catch( Exception exception){
  exception.printStackTrace();
  return -1;
 }
 return 0;	
}



最后用這個小程序看一看剛才生成的證書mycert.crt內容,把文件名寫入main()中:

public static void main(String[] args) {
 JFrame frame = new JFrame("證書閱讀器");
 frame.addWindowListener(new WindowAdapter() {
  public void windowClosing(WindowEvent e) {System.exit(0);}
 });
 frame.getContentPane().add(new CARead("mycert.crt"),BorderLayout.CENTER);
 frame.setSize(700, 425);
 frame.setVisible(true);
}



證書mycert.crt的內容顯示如圖2所示,所有信息和Base64的顯示內容,這里不再列舉。

現在已經讀取了證書的一些內容,那么怎樣使用證書呢?我們可以假設A和B要共享一個絕密的文件F,B信任並擁有A的證書,也就是說B擁有A的公鑰。那么A通過A和B共知的加密算法(對稱密鑰算法,比如DES算法)先加密文件F,然后對加密后的F進行簽名和散列摘要(比如MD5算法,目的是保證文件的完整性),然后把F發送到B。B收到文件后,先用A的證書中的公鑰驗證簽名,然后再用通過共知的加密算法解密,就可以得到原文件了。這里使用的數字簽名,可以保證B得到的文件,就是A的,A不能否認其不擁有文件F,因為只有A擁有可以讓A的公鑰驗證其簽名的私鑰,同時這里使用DES算法加密,使得文件有保密性。

使用DES算法的加密解密函數類似,這里不對加密算法做進一步討論,詳細請看J2SDK的JSE部分內容,加密簽名、解密驗證文件結構見圖3。



圖3 加密簽名、解密驗證文件結構圖



加密函數中的desKeyData存放DES加密密鑰,如果要在程序中指定,可以設置為:

static byte[] desKeyData = { (byte)0x01, (byte)0x02, (byte)0x03, (byte)0x04, 
(byte)0x05, (byte)0x06, (byte)0x07, (byte)0x08 };


加密函數寫成:

public static void crypt(byte[] cipherText,String outFileName){		
 try{
  DESKeySpec desKeySpec = new DESKeySpec(desKeyData);
  SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
  SecretKey secretKey = keyFactory.generateSecret(desKeySpec);
  Cipher cdes = Cipher.getInstance("DES");
  cdes.init(Cipher.ENCRYPT_MODE, secretKey);
  byte[] ct = cdes.doFinal(cipherText);
  try{
   FileOutputStream out=new FileOutputStream(outFileName);
   out.write(ct);
   out.close();
  }catch(IOException e){
   e.printStackTrace();
  }
 }catch (Exception e){
  e.printStackTrace();
 }
}


其中ct就是加密后的內容,outFileName保存加密后文件的文件名。把cdes.init(Cipher.ENCRYPT_MODE, secretKey)換成cdes.init(Cipher.DECRYPT_MODE, secretKey)就是解密文件了。

文件加密后就要對文件簽名,保證A發送到B的文件不可偽造。下面是用存放在.keystore中的私鑰進行簽名的函數,簽名使用的摘要算法是MD5。其中sigText是被簽名內容的輸入數組,outFileName是保存簽名后輸出文件的名稱,KeyPassword是讀取Keystore使用的密碼,KeyStorePath是存放.keystore文件的路徑,函數代碼如下:

public static void sig(byte[] sigText, String outFileName,String 
KeyPassword,String KeyStorePath){
 char[] kpass;
 int i;
 try{
  KeyStore ks = KeyStore.getInstance("JKS");
  FileInputStream ksfis = new FileInputStream(KeyStorePath); 
  BufferedInputStream ksbufin = new BufferedInputStream(ksfis);  
  kpass=new char[KeyPassword.length()];
  for(i=0;i<KeyPassword.length();i++)
   kpass[i]=KeyPassword.charAt(i);
  ks.load(ksbufin, kpass);
  PrivateKey priv = (PrivateKey) ks.getKey(KeystoreAlias,kpass );
  Signature rsa=Signature.getInstance("MD5withRSA");  
  rsa.initSign(priv);
  rsa.update(sigText);
  byte[] sig=rsa.sign();
  System.out.println("sig is done");
  try{
   FileOutputStream out=new FileOutputStream(outFileName);
   out.write(sig);
   out.close();
  }catch(IOException e){
   e.printStackTrace();
  }    
 }catch(Exception e){
  e.printStackTrace();
 }
}


驗證簽名需要存放簽名文件和被簽名的文件以及證書,其中,updateData存放被簽名文件的內容,sigedText存放得到的簽名內容,CertName是證書名。驗證簽名代碼如下:

public static void veriSig(byte[] updateData, byte[] sigedText){
    try{  
        CertificateFactory 
certificatefactory=CertificateFactory.getInstance("X.509");
		FileInputStream fin=new FileInputStream(CertName);
		X509Certificate 
certificate=(X509Certificate)certificatefactory.generateCertificate(fin);
	    PublicKey pub = certificate.getPublicKey();
	    Signature rsa=Signature.getInstance("MD5withRSA");
        rsa.initVerify(pub);
        rsa.update(updateData);
        boolean verifies=rsa.verify(sigedText);
        System.out.println("verified "+verifies);
        if(verifies){
               System.out.println("Verify is done!");
          }else{
               System.out.println("verify is not successful");
        }	    
	  }catch(Exception e){    
            e.printStackTrace();	           	
	 }
}


可以用keytool產生兩個自簽的簽名證書,或者到某個CA去申請兩個證書。用Java編寫加密和驗證程序,上述例子只是一個非常簡單的證書應用,實際協議對證書的使用(比如SSL)要比這個復雜多了。
 
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM