來源:賽迪網
作者:李素科
(轉載序:網上找的好文章,一篇就把我找了幾天的所有東西都概括進來了,真是非常感謝作者:李素科 其實在找資料的過程當中,主要沒解決的問題在於如何獲得KeyStore檔案中的PrivateKey,本來查jsdk 1.4 api文檔就可以知道了,但是居然從上到下看了2遍,沒有發現這個方法:load() .......)
認證(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的顯示內容,這裡不再列舉。
圖2 認證mycert.crt的內容顯示
現在已經讀取了認證的一些內容,那麼怎樣使用認證呢?我們可以假設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)要比這個複雜多了。