第4章使用數字簽名來確定數據的身份,保證了身份的確定性以及不可篡改性和不可否認性。使用數字簽名的前提是接收數據者能夠確性驗證簽名時所使用的公鑰確實是某個人的,數字證書可以解決這一問題。
本章主要內容:
使用Keytool工具創建數字證書
使用Keytool工具和Java程序讀取並顯示數字證書
使用Keytool工具和Java程序維護密鑰庫
使用Java程序簽發數字證書
對單個數字證書作初步檢驗
數字證書的創建
數字證書的主要功能是保存公鑰和某個人或機構的對應關係,本節介紹幾種數字證書的創建方法。它們都使用了keytool工具的-genkey參數。
5.1.1 使用默認的密鑰庫和算法創建數字證書
★ 實例說明
本實例使用J2SDK提供的keytool工具用默認的密鑰庫和算法創建幾個數字證書。
★運行程序
keytool程序運行時加上命令行參數–genkey即可。
在命令行中輸入“keytool –genkey”將自動使用默認的算法生成公鑰和私鑰,並以交互方式獲得公鑰持有者的信息。其交互過程如下,其中帶下劃線的字符爲用戶鍵盤輸入的內容,其他爲系統提示的內容。
C:\>keytool -genkey
輸入keystore密碼: 123456
您的名字與姓氏是什麼?
[Unknown]: Xu Yingxiao1
您的組織單位名稱是什麼?
[Unknown]: Network Center
您的組織名稱是什麼?
[Unknown]: Shanghai University
您所在的城市或區域名稱是什麼?
[Unknown]: ZB
您所在的州或省份名稱是什麼?
[Unknown]: Shanghai
該單位的兩字母國家代碼是什麼
[Unknown]: CN
CN=Xu Yingxiao1, OU=Network Center, O=Shanghai University, L=ZB, ST=Shanghai, C=CN 正確嗎?
[否]: 是
輸入的主密碼
(如果和 keystore 密碼相同,按回車): abcdefg
C:\>
如果使用中文操作系統,上述操作中輸入的“是”不能用英文“yes”代替。如果沒有DOS下的中文輸入系統的話,可以在Windows的“記事 本”中輸入一箇中文字符“是”,然後點擊DOS窗口左上角的 圖標,選擇“編輯/粘貼”菜單,或直接點擊窗口中的 工具將中文字符“是”粘貼到DOS窗口,如圖5-1所示。
圖5-1 將中文字符粘貼到DOS窗口
以上操作將生成一個公鑰和一個私鑰,這裏並未指定使用何算法,將使用默認的DSA算法。
同時上述操作將創建一個數字證書,證書中包含了新生成的公鑰和一個名字爲“CN=Xu Yingxiao1, OU=Network Center, O=Shanghai University, L=ZB, ST=Shanghai, C=CN”的主體(人或機構)的對應關係。其中“CN=Xu Yingxiao1, OU=Network Center, O=Shanghai University, L=ZB, ST=Shanghai, C=CN”是X.500格式的全名,包含了主體的國家、州、城市、機構、單位和名字。這樣,這個證書將證明相應的公鑰是這個人或機構所擁有的。
以上生成的公鑰、私鑰和證書都保存在用戶的主目錄中創建一個默認的文件“.keystore”中。如果使用的是Window 98操作系統,用戶的主目錄是c:\windows,在該目錄下可以找到“.keystore”文件。如果是Windows 2000系統,用戶主目錄是c:\ Documents and Setting\用戶名。
由於“.keystore”中包含了私鑰,所以是一個需要保密的文件,因此上述操作提示爲該文件設置一個密碼:“輸入keystore密碼”, 這裏因爲是第一次使用該密鑰庫,因此輸入的密碼“123456”將成爲該默認的密鑰庫的密碼(實際使用時應該設置複雜的口令)。以後再使用這個密鑰庫時必 須提供這個口令纔可以使用。
以上操作最後還提示“輸入的主密碼”,這裏“mykey”是默認的別名,使用該名字可以在密鑰庫“.keystore”中找到對應的公鑰、私鑰和證書。此處輸入的密碼是對應於該別名的私鑰的密碼,密鑰庫中每個別名可以使用不同的密碼加以保護。
5.1.2 使用別名
密鑰庫中可以存放多個條目(公鑰/私鑰對和證書),它們在密鑰庫中以別名(alias)區分。5.1.1小節在使用keytool工具時沒有指 定別名,因此係統使用了默認的別名mykey。如果再次運行“keytool –genkey”,則系統將提示“keytool錯誤: java.lang.Exception: 沒有創建鍵值對,別名 已經存在”,因此當密鑰庫中有多個公鑰/私鑰對和證書時,應該使用別名。
★ 實例說明
本實例使用J2SDK提供的keytool工具用在默認的密鑰庫中利用別名增加多個證書。
★運行程序
keytool程序運行時加上命令行參數–alias即可。
在命令行中輸入“keytool –genkey –alias xuyingxiao2”將自動使用默認的算法生成別名爲xuyingxiao2的公鑰和私鑰,並以交互方式獲得公鑰持有者的信息。其交互過程如下:
C:\>keytool -genkey -alias xuyingxiao2
輸入keystore密碼: 123456
您的名字與姓氏是什麼?
[Unknown]: Xu Yingxiao2
您的組織單位名稱是什麼?
[Unknown]: Network Center
您的組織名稱是什麼?
[Unknown]: SHU
您所在的城市或區域名稱是什麼?
[Unknown]: ZB
您所在的州或省份名稱是什麼?
[Unknown]: SH
該單位的兩字母國家代碼是什麼
[Unknown]: CN
CN=Xu Yingxiao2, OU=Network Center, O=SHU, L=ZB, ST=SH, C=CN 正確嗎?
[否]: 是
輸入的主密碼
(如果和 keystore 密碼相同,按回車):
其中“輸入keystore密碼:”後面輸入的內容必須和5.1.1小節相同的密碼,否則將無法訪問密鑰庫,並提示如下錯誤:“keytool 錯誤: java.io.IOException: Keystore was tampered with, or password was incorrect”,這是因爲5.1.1小節已經爲默認的密鑰庫設置了該密碼,以後使用該密鑰庫都必須提供該密碼。
在“輸入的主密碼”的提示後這裏不妨直接按“回車鍵”,這樣該私鑰將使用和密鑰庫相同的密碼“123456”來保護。
以上操作將在用戶主目錄的“.keystore”文件(如對於Windows 98用戶是c:\windows\.keystore)中增加一對公鑰和私鑰(DSA算法),同時增加一個數字證書,證書中包含了新生成的公鑰和一個名字 爲“CN=Xu Yingxiao2, OU=Network Center, O=Shanghai University, L=ZB, ST=Shanghai, C=CN”的主體(人或機構)的對應關係。
5.1.3 使用指定的算法和密鑰庫和有效期
5.1.1和5.1.2小節中使用的是默認的算法和密鑰庫,本節介紹如何自己指定算法和密鑰庫。
★ 實例說明
本實例使用J2SDK提供的keytool工具用RSA算法和在指定的密鑰庫mykeystore中創建公鑰/私鑰對和證書。
★運行程序
keytool的-keyalg參數可以指定密鑰的算法,如果需要指定密鑰的長度,可以再加上-keysize參數。密鑰長度默認爲1024位,使用DSA算法時,密鑰長度必須在512到1024之間,並且是64的整數倍。
Keytool的-keystore參數可以指定密鑰庫的名稱。密鑰庫其實是存放密鑰和證書的文件,密鑰庫對應的文件如果不存在自動創建。
-validity參數可以指定所創建的證書有效期是多少天。
如在命令行中輸入“keytool -genkey -alias mytest -keyalg RSA -keysize 1024 -keystore mykeystore -validity 4000”將使用RSA算法生成1024位的公鑰/私鑰對及整數,密鑰長度爲1024位,證書有效期爲4000天。使用的密鑰庫爲mykeystore文 件。
C:\java\ch5>keytool -genkey -alias mytest -keyalg RSA -keysize 1024 -keystore mykeystore -validity 4000
輸入keystore密碼: wshr.ut
您的名字與姓氏是什麼?
[Unknown]: Xu Yingxiao
您的組織單位名稱是什麼?
[Unknown]: Network Center
您的組織名稱是什麼?
[Unknown]: Shanghai University
您所在的城市或區域名稱是什麼?
[Unknown]: ZB
您所在的州或省份名稱是什麼?
[Unknown]: Shanghai
該單位的兩字母國家代碼是什麼
[Unknown]: CN
CN=Xu Yingxiao, OU=Network Center, O=Shanghai University, L=ZB, ST=Shanghai, C=CN 正確嗎?
[否]: 是
輸入的主密碼
(如果和 keystore 密碼相同,按回車):
C:\java\ch5>
由於當前目錄下沒有mykeystore文件,因此以上操作將在當前目錄建立文件名爲mykeystore的文件,並提示輸入一個密碼加以保 護:“輸入keystore密碼:”。因爲這裏使用的密鑰庫和5.1.1小節及5.1.2小節不是同一個文件,因此這裏輸入的密碼和5.1.1小節及 5.1.2小節沒有必要一致,這裏不妨設置爲“wshr.ut”。這樣,以後再使用這個密鑰庫文件時必須提供該密碼。
對其中的“輸入的主密碼”,這裏不妨直接按回車鍵,這樣mykeysotre文件中的mytest條目將使用和密鑰庫相同的密碼:“wshr.ut”。
5.1.4 使用非交互模式
★ 實例說明
前面各小節都是通過屏幕交互方式輸入證書擁有者的信息,本實例使用J2SDK提供的keytool工具直接在命令行參數中指定所有的信息來創建公鑰/私鑰對和證書。
★運行程序
前面各小節交互輸入的內容主要有:
密鑰庫的密碼:這可以用命令行參數-storepass來指定。
別名條目的主密碼:這可以用命令行參數-storepass來指定。
證書擁有者的信息:這可以用命令行參數- dname來指定。該參數的值是一個字符串,其格式是:“CN=XX, OU= XX, O= XX, L= XX, ST= XX, C= XX”,其中CN,OU,O,L,ST,C分別代表以前各小節交互性輸入的名字與姓氏(Common Name)、組織單位名稱(Organization Unit)、組織名稱((Organization)、城市或區域名稱(Locality)、州或省份名稱(State)、國家代碼(Country)。
如可以在命令行輸入如下內容來向密鑰庫mykeystore添加條目。
keytool -genkey -dname "CN=tmp, OU=NC, O=Shanghai University, L=ZB, ST=Shanghai, C=CN" -alias tmp -keyalg RSA -keystore mykeystore -keypass wshr.ut -storepass wshr.ut -validity 1000
該命令使用的密鑰庫和5.1.3小節一樣,是mykeystore文件,因此這裏的-storepass參數必須和5.1.3小節一樣使用 “wshr.ut”,否則無法訪問密鑰庫。條目的別名不妨使用tmp,條目的密碼通過-keypass參數指定,這裏不妨仍舊使用“wshr.ut”,也 可以任意設定。
以上命令必須在命令行中一行輸完,有些操作系統對命令行中一行輸入的字符有限制,這時可以將以上命令放在一個批處理文件中(必須在一行中,中間 不能換行)。如將該命令用Windows的“記事本”編輯,以文件名5.1.4.bat保存,則運行5.1.4.bat將自動完成所有操作。
又如執行下面的命令將在當前目錄生成一個密鑰庫文件lfkeystore,密鑰庫的密碼是wshr.ut,其中存放的證書別名爲lf。有效期爲 3500天。證書中包含的是需要公開的信息:一個主體“CN=Liu Fang, OU=Packaging, O=Shanghai University, L=ZB, ST=Shanghai, C=CN”擁有某個RSA公鑰。公鑰對應的私鑰也保存在密鑰庫lfkeystore中,並用密碼wshr.ut加以保護。
keytool -genkey -dname "CN=Liu Fang, OU=Packaging, O=Shanghai University, L=ZB, ST=Shanghai, C=CN" -alias lf -keyalg RSA -keysize 1024 -keystore lfkeystore -keypass wshr.ut -storepass wshr.ut -validity 3500
將該命令用Windows的“記事本”編輯,命令一行輸完,以文件名5.1.4_2.bat保存,則運行5.1.4_2.bat將自動完成所有操作。
5.2數字證書的顯示
5.1節用各種方式創建了多個數字證書,本節使用各種方式顯示這些數字證書的信息,它們有的使用keytool工具的-list參數,有的直接通過Java編程來實現。
5.2.1 使用Keytool直接從密鑰庫顯示條目信息
★ 實例說明
本實例使用J2SDK提供的keytool工具直接從密鑰庫中顯示證書信息。
★運行程序
keytool的命令行參數-list可以顯示密鑰庫中的證書信息,如輸入:
keytool –list
則顯示默認的密鑰庫中的證書信息。如下:
C:\java\ch5>keytool -list
輸入keystore密碼: 123456
Keystore 類型: jks
Keystore 提供者: SUN
您的 keystore 包含 2 輸入
xuyingxiao2, 2002-11-22, keyEntry,
認證指紋 (MD5): 65:C9:FD:8C:82:C7:36:E1:7C:D9:AD:9A:34:25:5C:71
mykey, 2002-11-22, keyEntry,
認證指紋 (MD5): BE:F1:9F:45:5F:4E:02:FF:94:83:39:73:E1:F5:59:9C
程序開始要求輸入密鑰庫的密碼,在5.1.1小節我們已經爲默認密鑰庫設置了密碼“123456”,因此這裏必須輸入相同的密碼才能使用密鑰庫。
在5.1.1和5.1.2小節我們已向默認的密鑰庫中添加了兩個條目:mykey和xuyingxiao2,在此處的輸出信息中可以看到這兩個條目的名稱、創建日期、條目類型(keyEntry,密鑰條目)以及認證指紋。認證指紋其實是該條目的消息摘要。
如果進一步使用-alias參數則可以顯示指定的條目的信息,如:
C:\java\ch5>keytool -list -alias xuyingxiao2
輸入keystore密碼: 123456
xuyingxiao2, 2002-11-22, keyEntry,
認證指紋 (MD5): 65:C9:FD:8C:82:C7:36:E1:7C:D9:AD:9A:34:25:5C:71
如果進一步使用-keystore參數則可以顯示指定的密鑰庫中的證書信息,如:
C:\java\ch5>keytool -list -keystore mykeystore
輸入keystore密碼: wshr.ut
Keystore 類型: jks
Keystore 提供者: SUN
您的 keystore 包含 2 輸入
mytest, 2002-12-5, keyEntry,
認證指紋 (MD5): B2:DC:75:CD:60:B7:1E:7A:97:EE:E8:A4:31:D6:26:C6
tmp, 2002-12-5, keyEntry,
認證指紋 (MD5): 5C:FA:ED:8E:AE:30:1B:2B:CF:39:ED:4D:6F:94:E1:6B
5.2.2 使用Keytool直接從密鑰庫顯示證書詳細信息
★ 實例說明
本實例使用J2SDK提供的keytool工具直接從密鑰庫中顯示證書的詳細信息。
★運行程序
5.2.1的各個命令加上-v參數可以顯示證書的詳細信息,如:
C:\java\ch5>keytool -list -v -keystore lfkeystore -alias lf
別名名稱: lf
創建日期: 2002-12-5
輸入類型:KeyEntry
認證鏈長度: 1
認證 [1]:
Owner: CN=Liu Fang, OU=Packaging, O=Shanghai University, L=ZB, ST=Shanghai,
發照者: CN=Liu Fang, OU=Packaging, O=Shanghai University, L=ZB, ST=Shangha
CN
序號: 3deec441
有效期間: Thu Dec 05 11:13:05 CST 2002 至: Thu Jul 05 11:13:05 CST 2012
認證指紋:
MD5: 55:73:8D:16:05:E1:F8:5F:F8:25:C7:29:C3:D6:48:67
SHA1: 3F:75:6A:DC:E7:7B:32:64:C5:99:1E:CC:9B:9E:77:88:59:21:C2:33
其中包含了發照者(簽發者)、序號、有效期、MD5和SHA1認證指紋等額外信息,其含義在本章後續內容中將涉及。
5.2.3 使用Keytool將數字證書導出到文件
★ 實例說明
本實例使用J2SDK提供的keytool工具將指定的證書從密鑰庫導出爲編碼過和沒編碼過兩種格式的文件。
★運行程序
使用keytool的-export參數可以將別名指定的證書導出到文件,文件名通過-file參數指定。如輸入如下命令:
C:\java\ch5>keytool -export -alias xuyingxiao2 -file xuyingxiao2.cer
輸入keystore密碼: 123456
保存在文件中的認證
則將默認密鑰庫中的xuyingxiao2條目對應的證書導出到文件xuyingxiao2.cer中。由於命令行中沒有用storepass給出密碼,因此屏幕提示輸入keystore密碼。由於證書中不包含私鑰,因此不需要條目的主密碼。
該操作完成後將在當前目錄中創建xuyingxiao2.cer文件,該文件即是默認密鑰庫中的xuyingxiao2條目對應的證書,它包含了公鑰和主體的對應關係,內容也可以公開。
輸入如下命令則可以指定密鑰庫:
C:\java\ch5>keytool -export -alias lf -file lf.cer -keystore lfkeystore –storepass wshr.ut
保存在文件中的認證
該操作完成後將在當前目錄中創建lf.cer文件。
如果用文本編輯器打開xuyingxiao2.cer或lf.cer,將會發現它是二進制文件,有些內容無法顯示,這不利於公佈證書。在導出證書時加上-rfc參數則可以使用一種可打印的編碼格式來保存證書。如:
C:\java\ch5> keytool -export -alias mytest -file mytest.cer -keystore mykeystore -storepass wshr.ut -rfc
保存在文件中的認證
則當前目錄下將增加一個文件mytest.cer,其內容是編碼過的,可以在屏幕上顯示、拷貝或打印。如圖5-2所示。
圖5-2 編碼後的證書內容
5.2.4 使用Keytool從文件中顯示證書
★ 實例說明
本實例使用J2SDK提供的keytool工具將5.2.3小節導出的證書文件顯示出來。
★運行程序
使用keytool的-printcert參數可以將5.2.3小節導出到證書文件詳細內容顯示出來,文件名稱通過-file參數指定。如:
C:\java\ch5>keytool -printcert -file lf.cer
Owner: CN=Liu Fang, OU=Packaging, O=Shanghai University, L=ZB, ST=Shanghai, C=CN
發照者: CN=Liu Fang, OU=Packaging, O=Shanghai University, L=ZB, ST=Shanghai, C=CN
序號: 3deec441
有效期間: Thu Dec 05 11:13:05 CST 2002 至: Thu Jul 05 11:13:05 CST 2012
認證指紋:
MD5: 55:73:8D:16:05:E1:F8:5F:F8:25:C7:29:C3:D6:48:67
SHA1: 3F:75:6A:DC:E7:7B:32:64:C5:99:1E:CC:9B:9E:77:88:59:21:C2:33
對編碼過的證書可以同樣顯示,如:
C:\java\ch5>keytool -printcert -file mytest.cer
Owner: CN=Xu Yingxiao, OU=Network Center, O=Shanghai University, L=ZB, ST=Shanghai, C=CN
發照者: CN=Xu Yingxiao, OU=Network Center, O=Shanghai University, L=ZB, ST=Shanghai, C=CN
序號: 3deec043
有效期間: Thu Dec 05 10:56:03 CST 2002 至: Sun Nov 17 10:56:03 CST 2013
認證指紋:
MD5: B2:DC:75:CD:60:B7:1E:7A:97:EE:E8:A4:31:D6:26:C6
SHA1: 32:E5:89:16:7E:25:7F:86:16:94:34:36:95:44:D7:CF:14:C8:F2:1E
5.2.5 在Windows中從文件顯示證書
★ 實例說明
本實例在Windows中直接顯示5.2.3小節導出的證書文件。
★運行程序
5.2.3小節導出的證書文件中,只要文件名以.cer爲後綴,Windows操作系統就可以直接識別。如在Windows中雙擊lf.cer 圖標,將出現圖5-3所示證書窗口。其中包含了證書的所有者、頒發者、有效期等信息,這些信息和5.2.4小節使用keytool顯示出的信息一致。
由於該證書是用自己的私鑰對該證書進行數字簽名的,即自己給自己簽發的證書,因此窗口中顯示警告信息:“該證書發行機構根證書沒受信任”。在後續章節中將介紹證書的簽發問題。
圖5-3 證書的常規信息
點擊圖5-3的“詳細資料”,可以看到證書的版本、序號、簽名算法、頒發者、有效期、主題(即全名)、公鑰算法、拇印算法、拇印等信息。其中的拇印即認證指紋,和5.2.4小節顯示的SHA1認證指紋相同。如圖5-4所示。
圖5-4 證書的詳細信息
同樣,點擊編碼過的證書文件如mytest.cer可以看到類似信息。
5.2.6 Java程序從證書文件讀取證書
前面各小節都使用了keytool工具來顯示證書,本小節開始涉及編程部分,通過自己編寫的Java程序來訪問密鑰庫。
★ 實例說明
本實例使用5.2.3小節得到的證書文件mytest.cer、lf.cer等,演示瞭如何編程讀取證書的信息。
★ 編程思路:
在java.security.cert包中有Certificate類代表證書,使用其toString( )方法可以得到它所代表的證書的所有信息。爲了得到Certificate類型的對象,可以使用java.security.cert包中的 CertificateFactory類,它的generateCertificate( )方法可以從文件輸入流生成Certificate類型的對象。具體步驟如下:
(1) 獲取CertificateFactory類型的對象
CertificateFactory cf=CertificateFactory.getInstance("X.509");
分析:CertificateFactory類是一個工廠類,必須通過getInstance( )方法生成對象,其參數指定證書的類型,這裏使用“X.509”,它是一個廣泛使用的數字證書標準。
(2) 獲取證書文件輸入流
FileInputStream in=new FileInputStream(args[0]);
分析:不妨從命令行參數讀取5.2.3小節的證書文件,創建文件輸入流。
(3) 生成Certificate類型的對象
Certificate c=cf.generateCertificate(in);
分析:執行第1步得到的CertificateFactory類型的對象的generateCertificate( )方法,以第2步的文件輸入流作爲其參數。
(4) 顯示證書內容
String s=c.toString( );
分析:執行toString( )方法,可以將其寫入文件,或在屏幕上顯示出來。
★代碼與分析:
完整代碼如下:
import java.io.*;
import java.security.cert.*;
public class PrintCert{
public static void main(String args[ ]) throws Exception{
CertificateFactory cf=CertificateFactory.getInstance("X.509");
FileInputStream in=new FileInputStream(args[0]);
Certificate c=cf.generateCertificate(in);
in.close();
String s=c.toString( );
// 顯示證書
FileOutputStream fout=new FileOutputStream("tmp.txt");
BufferedWriter out= new BufferedWriter(new OutputStreamWriter(fout));
out.write(s,0,s.length( ));
out.close();
}
}
程序最後創建文件輸出流,將讀取到的證書信息保存在文件tmp.txt中。
★運行程序
輸入java PrintCert mytest.cer來運行程序,將創建文件tmp.txt,其內容如下:
[
[
Version: V1
Subject: CN=Xu Yingxiao, OU=Network Center, O=Shanghai University, L=ZB, ST=Shanghai, C=CN
Signature Algorithm: MD5withRSA, OID = 1.2.840.113549.1.1.4
Key: com.sun.net.ssl.internal.ssl.JSA_RSAPublicKey@d99a4d
Validity: [From: Thu Dec 05 10:56:03 CST 2002,
To: Sun Nov 17 10:56:03 CST 2013]
Issuer: CN=Xu Yingxiao, OU=Network Center, O=Shanghai University, L=ZB, ST=Shanghai, C=CN
SerialNumber: [ 3deec043 ]
]
Algorithm: [MD5withRSA]
Signature:
0000: BE D5 F3 3C FE 53 16 0E DC FE A0 1C 7C F1 AF 31 ...<.S.........1
0010: F3 3B 0C 36 2E 1D 32 1F 87 B3 B4 1D 82 BB 4A BB .;.6..2.......J.
0020: DE 5D 35 90 BC A8 CF 42 45 61 ED 3D 19 DF 7D AB .]5....BEa.=....
0030: 45 F2 4A 19 C1 6B 19 0E F7 EC CE C6 1A 40 9F A9 E.J..k.......@..
0040: 6B 8C 49 DA CC 85 67 D9 C8 91 67 DB 33 6B 47 96 k.I...g...g.3kG.
0050: 70 D6 91 69 24 43 D5 81 6C 9D C5 9D 4D 40 23 01 p..i$C..l...M@#.
0060: 65 72 B6 27 FB 1B F3 8F 4A 16 0B 31 E2 EB 19 42 er.'....J..1...B
0070: 50 C7 70 62 6E FC A4 76 03 3E 22 7C 26 00 47 ED P.pbn..v.>".&.G.
]
從中同樣可以看到該證書的版本、主體、簽名算法、公鑰、有效期、簽名、序號和簽名等信息。從中還可以看出簽名者和主體完全相同,即該證書是用自己對應的私鑰進行數字簽名的,簽名的算法是“MD5withRSA”,簽名的結果也以十六進制和二進制顯示了出來。
5.2.7 Java程序從密鑰庫直接讀取證書
★ 實例說明
5.2.6小節的程序依賴於用keytool先將數字證書導出,本實例通過自己編寫的Java程序來直接訪問密鑰庫讀取證書的信息。
★ 編程思路:
在java.security包中的KeyStore 類代表密鑰庫,使用其load( )方法可以從密鑰庫文件輸入流中加載密鑰庫,使用其getCertificate( )方法可以從密鑰庫中提取證書。具體步驟如下:
(1) 創建密鑰庫的文件輸入流
FileInputStream in=new FileInputStream(name);
分析:其中name是字符串類型的參數,即密鑰庫文件的文件名。
(2) 創建KeyStore對象
KeyStore ks=KeyStore.getInstance("JKS");
分析:KeyStore類是工廠類,必須用getInstance( )方法生成對象。其參數指定密鑰庫的類型。一般是“JKS”,如果創建密鑰庫時使用了其他類型的密鑰庫,這裏應指定對應的類型。注意KeyStore類的名稱中字母K和S均爲大寫。
(3) 加載密鑰庫
ks.load(in,pass.toCharArray());
分析:執行上1步創建的 Keystore對象的load( )方法加載密鑰庫。其第一個參數是第1步得到的文件輸入流,第二個參數是創建密鑰庫時設置的口令。對於5.1.1小節的缺省密鑰庫,當時設置的口令爲 “123456”,5.1.3小節的密鑰庫mykeystore,當時設置的口令爲“wshr.ut”。
(4) 獲取密鑰庫中的證書
Certificate c=ks.getCertificate(alias);
分析:執行第2步得到的KeyStore 對象的getCertificate( )方法,可以獲得密鑰庫中的證書,得到Certificate對象。以後,便可以像5.2.6小節一樣使用該證書了。
★代碼與分析:
完整代碼如下:
import java.io.*;
import java.security.*;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
public class PrintCert2{
public static void main(String args[ ]) throws Exception{
String pass="wshr.ut";
String alias="mytest";
String name="mykeystore";
FileInputStream in=new FileInputStream(name);
KeyStore ks=KeyStore.getInstance("JKS");
ks.load(in,pass.toCharArray());
Certificate c=ks.getCertificate(alias);
in.close();
System.out.println(c.toString( ));
}
}
在程序的import語句中使用了“import java.security.cert.Certificate;”而不是“import java.security.cert.*”。這是因爲J2SDK1.4中在java.security和java.security..cert包中都 有Certificate類,由於本示例程序既用到了java.security包又用到了java.security..cert包,因此若使用 “import java.security. cert.*”,則編譯器將無法確定使用的是哪個Certificate類,編譯時將報錯。
java.security包中的Certificate類是爲了和以前版本的JDK兼容而保留的,已經不提倡使用。編寫新的程序是應該使用 java.security..cert包中的Certificate類。如果在import語句中使用的是“import java.security.cert.*”,則在程序中應該使用java.security..cert.Certificate來使用該類。如本實例 中若將
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
改爲:
import java.security.cert.*;
則程序中的
Certificate c=ks.getCertificate(alias);
應該改爲:
java.security..cert.Certificate c=ks.getCertificate(alias);
本實例中使用的密鑰庫是5.1.3小節創建的mykeystore文件,在5.1.3小節爲其設置了口令“wshr.ut”,並添加了一個條 目:mytest,本實例準備讀取mytest條目對應的證書。因此,在程序開頭提供了這三條信息。此外程序中只是在加載密鑰庫時用到了口令,在提取證書 時並不需要mytest的口令。
如果準備提取5.1.1小節的默認的密鑰庫中mykey條目,則可以將程序中
String pass="wshr.ut";
String alias="mytest";
String name="mykeystore";
改爲:
String pass="123456";
String alias="mykey";
String userhome=System.getProperty("user.home");
String name=userhome+File.separator+".keystore";
其中“123456”是5.1.1小節爲默認的密鑰庫設置的密碼,“mykey”是5.1.1小節添加的條目默認的別名,也可以使用5.1.2 小節中的別名xuyingxiao2。由於不同操作系統中默認密鑰庫的存放位置和文件分隔符都可能不同,因此程序通過 System.getProperty("user.home")語句獲取用戶主目錄,通過File.separator獲取文件分隔符,最後和文件名 “.keystore”共同組成密鑰庫文件的完整路徑。對於Windows 98操作系統,通過這種方式獲得的name的值是:“c:\windows\.keystore”。在5.1.1小節爲mykey條目設置的口令 “abcdef”在程序中並不需要,因爲該口令主要是保護密鑰庫中的私鑰。
★運行程序
輸入java PrintCert2運行程序,程序顯示mykeystore密鑰庫中的mytest條目對應的證書。其輸出結果很多,可以使用“java PrintCert2> tmp2.txt”將輸出結果重定向到文件tmp2.txt中,可以看到tmp2.txt中的內容和5.2.6小節得到的內容完全一樣。
5.2.8 Java程序顯示證書指定信息(全名/公鑰/簽名等)
★ 實例說明
5.2.6和5.2.7小節在得到證書後都是將其用toString( )方法打印出所有內容,本小節介紹如何從證書中只提取所需要的信息。包括版本、序列號、主體的全名、簽發者的全名、有效期、簽名算法、簽名和公鑰等。其中 公鑰和全名(人或機構)是證書中包含的最重要的信息。
★ 編程思路:
無論使用5.2.6小節還是5.2.7小節的方法獲得證書對象後,都可以使用其方法獲得各種信息。假設證書對象可通過變量t訪問,可以按照如下方法獲取其指定信息
(1) 獲取證書
CertificateFactory cf=CertificateFactory.getInstance("X.509");
FileInputStream in=new FileInputStream(args[0]);
java.security.cert.Certificate c=cf.generateCertificate(in);
in.close();
分析:這裏不妨從5.2.4小節中導出的文件中讀取Certificate對象。
(2) 將證書轉換爲X509類型
X509Certificate t=(X509Certificate) c;
分析:X509Certificate是Certificate類的子類,Keytool工具生成的證書是符合X509標準的,X509Certificate類中提供了更多的方法可以獲取X509證書的相關信息。以下幾個步驟給出了幾個方法,可以根據需要選用。
(3) 獲取版本號
t.getVersion( )
分析:getVersion( )方法返回整型數代表證書符合X509標準的哪個版本。
(4) 獲取序列號
t.getSerialNumber( ).toString(16)
分析:每個證書在創建時都會分配一個唯一的序列號,用getSerialNumber( )方法可以獲取,它返回的BigInteger類型的對象,通過其方法toString(16)可以將其轉換爲16進制的字符串。
(5) 獲取主體和簽發者的全名
t.getSubjectDN( )
t.getIssuerDN( )
分析:證書中包含的主要信息是公鑰和主體(人或機構)的對應關係,該主體的全名可通過getSubjectDN( )方法獲得,它返回Principal類型的對象,可直接轉換爲“CN=XX, OU=XX, O= XX, L= XX, ST= XX, C= XX”類型的字符串。
證書的信息由自己或另外的機構簽發,簽發者的全名類似地通過t.getIssuerDN( )方法獲得。
(6) 獲取證書的有效期
t.getIssuerDN( )
t.getNotBefore( )
分析:getIssuerDN( )和getNotBefore( )方法分別獲取證書的有效期起始日期和有效期截至日期。它們返回的是Date( )類型的對象。
(7) 獲取證書的簽名算法
t.getSigAlgName( )
分析:。自己或其他機構簽發該證書是用簽發者的私鑰對該證書進行數字簽名來實現的,數字簽名所使用的算法名稱可以通過getSigAlgName( )方法獲得。其返回值是字符串類型。
(8) 獲取證書的簽名
byte[] sig=t.getSignature( );
new BigInteger(sig).toString(16)
分析:數字簽名的結果可以用getSignature( )方法獲得,該方法返回值是byte類型的數組,數字簽名一般可用16進制來表示,因此使用BigInteger類將byte類型的數組轉換爲 BigInteger類型,既而用BigInteger類的toString(16)方法將其轉換爲16進制字符串。
(9) 獲取證書的公鑰
t.getPublicKey( )
byte[ ] pkenc=pk.getEncoded( )
分析:證書中包含的主要信息是公鑰和主體(人或機構)的對應關係。主體已在第5步獲得,而公鑰則可以使用getPublicKey( )方法獲得。它返回PublicKey類型的對象,可以用於驗證簽名、顯示等。這裏不妨將其編碼打印出來。
★代碼與分析:
完整代碼如下:
import java.io.*;
import java.security.*;
import java.security.cert.*;
import java.util.*;
import java.math.*;
public class ShowCertInfo{
public static void main(String args[ ]) throws Exception{
CertificateFactory cf=CertificateFactory.getInstance("X.509");
FileInputStream in=new FileInputStream(args[0]);
java.security.cert.Certificate c=cf.generateCertificate(in);
in.close();
X509Certificate t=(X509Certificate) c;
System.out.println("版本號 "+t.getVersion());
System.out.println("序列號 "+t.getSerialNumber().toString(16));
System.out.println("全名 "+t.getSubjectDN());
System.out.println("簽發者全名\n"+t.getIssuerDN());
System.out.println("有效期起始日 "+t.getNotBefore());
System.out.println("有效期截至日 "+t.getNotAfter());
System.out.println("簽名算法 "+t.getSigAlgName());
byte[] sig=t.getSignature();
System.out.println("簽名\n"+new BigInteger(sig).toString(16));
PublicKey pk=t.getPublicKey();
byte[ ] pkenc=pk.getEncoded();
System.out.println("公鑰");
for(int i=0;i
System.out.print(pkenc[i]+",");
}
}
}
★運行程序
輸入java ShowCertInfo mytest.cer > tt.txt運行程序,顯示5.2.3小節導出的證書mytest.cer,並將顯示結果重定向到文件tt.txt中,tt.txt中得到的輸出如下:
版本號 1
序列號 3deec043
全名 CN=Xu Yingxiao, OU=Network Center, O=Shanghai University, L=ZB, ST=Shanghai, C=CN
簽發者全名
CN=Xu Yingxiao, OU=Network Center, O=Shanghai University, L=ZB, ST=Shanghai, C=CN
有效期起始日 Thu Dec 05 10:56:03 CST 2002
有效期截至日 Sun Nov 17 10:56:03 CST 2013
簽名算法 MD5withRSA
簽名
-412a0cc301ace9f123015fe3830e50ce0cc4f3c9d1e2cde0784c4be27d44b54421a2ca6
f435730bdba9e12c2e6208254ba0db5e63e94e6f108133139e5bf60569473b625337a
9826376e9824cc94b8698f296e96dbbc2a7e93623a62b2bfdcfe9a8d49d804e40c70b5
e9f4ce1d14e6bdaf388f9d91035b89fcc1dd83d9ffb813
公鑰
48,-127,-97,48,13,6,9,42,-122,72,-122,-9,13,1,1,1,5,0,3,-127,-115,0,48,-127,-119,2,-127,-127,0,-22,106,19,77,-35,117,16,-4,36,-73,71,117,-63,-77,-79,26,92,113,51,13,8,62,-51,-7,93,88,32,112,53,-29,71,-43,109,67,-127,-123,-52,105,-14,69,35,37,79,-73,75,15,111,112,-91,122,-128,-59,82,127,-97,-81,-10,70,-15,-111,-122,29,17,109,-81,57,102,-77,-80,-123,-65,-58,117,58,-11,126,74,112,-55,27,57,-9,90,106,4,-3,-121,-110,-70,-92,-108,-124,-46,50,112,-22,-50,49,64,-73,-80,3,88,31,65,-113,-110,-13,-92,-22,-14,-17,-35,-126,-39,108,-84,57,-26,-71,-55,7,-90,21,-96,108,-80,-21,2,3,1,0,1,
該結果和以前各種方法顯示的信息一致。
5.3密鑰庫的維護
5.1和5.2節使用keytool在密鑰庫中創建公鑰/私鑰對,將公鑰的持有者信息以數字證書的形式保存在密鑰庫中,並分別使用keytool和Java程序來讀取數字證書的信息。本節介紹如何對密鑰庫中的這些證書和密鑰進行刪除、修改等維護。
5.3.1使用Keytool刪除指定條目
★ 實例說明
本實例使用J2SDK提供的keytool工具從密鑰庫中刪除指定的條目。
★運行程序
keytool的命令行參數-delete可以刪除密鑰庫中的條目。進行刪除操作之前先在密鑰庫中添加一些條目供試驗。 也可將mykeystore文件做個備份,以便試驗完後恢復原來的密鑰。類似5.1.4小節的做法,在批處理文件5.3.1.bat中輸入如下命令(必須 輸入在一行中):
keytool -genkey -dname "CN=tmp1, OU=tmp, O= tmp, L= tmp, ST= tmp, C= tmp " -alias tmp1 -keyalg RSA -keystore mykeystore -keypass abcdefg -storepass wshr.ut
執行5.3.1.bat批處理文件,將在密鑰庫mykeystore中創建一個臨時條目tmp1。在mykeystore文件所在目錄中執行:
keytool -list -keystore mykeystore -storepass wshr.ut
將顯示:
Keystore 類型: jks
Keystore 提供者: SUN
您的 keystore 包含 3 輸入
mytest, 2002-12-5, keyEntry,
認證指紋 (MD5): B2:DC:75:CD:60:B7:1E:7A:97:EE:E8:A4:31:D6:26:C6
tmp, 2002-12-5, keyEntry,
認證指紋 (MD5): 5C:FA:ED:8E:AE:30:1B:2B:CF:39:ED:4D:6F:94:E1:6B
tmp1, 2002-12-5, keyEntry,
認證指紋 (MD5): 7B:0F:2B:C3:68:20:5C:C6:34:F3:90:10:1C:0E:66:28
從中可以看出密鑰庫mykeystore中共有三個條目。
下面開始刪除該條目,如輸入:
keytool –delete –alias tmp1 –keystore mykeystore
在屏幕提示“輸入keystore密碼”時輸入密鑰庫的密碼“wshr.ut”。這樣,剛纔執行2.bat添加的條目tmp1將被刪除。執行
keytool -list -keystore mykeystore -storepass wshr.ut
將看到密鑰庫mykeystore中只剩下兩個條目。
5.3.2使用Keytool修改指定條目的口令
★ 實例說明
本實例使用J2SDK提供的keytool工具修改密鑰庫中指定條目的口令。
★運行程序
keytool的命令行參數-keypassword可以修改密鑰庫中指定條目的口令。進行刪除操作之前和5.3.1小 節一樣執行2.bat創建一個臨時的條目tmp1。在5.3.1小節中可以看到該條目在創建時設置的口令爲“abcdefg”,現在我們準備把它改成 “123456”。
其操作過程如下:
C:\java\ch5>keytool -keypasswd -alias tmp1 -keystore mykeystore
輸入keystore密碼: wshr.ut
輸入的主密碼abcdefg
新 的主密碼: 123456
重新輸入新 的主密碼: 123456
這時,tmp1條目的口令就改成了123456,以後如果要從密鑰庫mykeystore中提取tmp1條目對應的私鑰時就必須提供該口令。
以上交互式操作也可全部通過命令行參數指定,例如如果想把密碼再由123456改爲asdfgh,則可以在命令行輸入如下命令:
keytool –keypasswd –alias tmp1 –keypass 123456 –new asdfgh –storepass wshr.ut –keystore mykeystore
5.3.3 Java程序列出密鑰庫所有條目
★ 實例說明
本實例使用Java程序列出密鑰庫mykeystore中所有條目的別名。
★ 編程思路:
java.security包中的KeyStore類提供的aliases( )方法可以列出KeyStore類所代表的密鑰庫中的所有條目。
具體步驟如下:
(1) 獲取密鑰庫mykeystorede的KeyStore對象,並加載密鑰庫
FileInputStream in=new FileInputStream("mykeystore");
KeyStore ks=KeyStore.getInstance("JKS");
ks.load(in, "wshr.ut".toCharArray());
分析:該步驟和 5.2.7小節第1-3步相同。其中“wshr.ut”是密鑰庫的密碼。
(2) 執行KeyStore對象的aliases( )方法
Enumeration e=ks.aliases( );
分析:該步驟和 5.2.7小節第1-3步相同。其中“wshr.ut”是密鑰庫的密碼。該方法返回枚舉類型的對象,其中包含了密鑰庫中所有條目的別名。
(3) 處理枚舉對象
while( e.hasMoreElements( )) {
System.out.println(e.nextElement( ));
}
分析:枚舉對象的hasMoreElements( )方法可以判斷其中是否還有元素,nextElement( )方法可以從枚舉對象中取出元素。由於該枚舉對象中包含的是密鑰庫中的各個條目的別名名稱,因此可以通過打印語句將名稱打印出來。
★代碼與分析:
完整代碼如下:
import java.util.*;
import java.io.*;
import java.security.*;
public class ShowAlias{
public static void main(String args[ ]) throws Exception{
String pass="wshr.ut";
String name="mykeystore";
FileInputStream in=new FileInputStream(name);
KeyStore ks=KeyStore.getInstance("JKS");
ks.load(in,pass.toCharArray());
Enumeration e=ks.aliases( );
while( e.hasMoreElements()) {
System.out.println(e.nextElement());
}
}
}
★運行程序
輸入“java ShowAlias”運行程序,將顯示所有條目的別名:
mytest
tmp
tmp1
5.3.4 Java程序修改密鑰庫口令
★ 實例說明
本實例使用Java程序修改密鑰庫mykeystore的口令。
★ 編程思路:
Java程序修改密鑰庫口令實際上是創建一個用新的密鑰庫,並設置新的口令。java.security包中的KeyStore類提供了store( )方法,它可以將KeyStore類中的信息寫入該方法參數中指定的新文件,並將口令修改爲該方法參數中設置的新口令。
具體步驟如下:
(1) 獲取密鑰庫mykeystorede的KeyStore對象,並加載密鑰庫
String name="mykeystore";
char[ ] oldpass=args[0].toCharArray();
FileInputStream in=new FileInputStream(name);
KeyStore ks=KeyStore.getInstance("JKS");
ks.load(in,oldpass);
分析:該步驟和 5.3.3小節相同。這裏,我們通過第一個命令行參數讀入密鑰庫的原有口令,以方便後面恢復口令。
(2) 創建新密鑰庫輸出流
FileOutputStream output=new FileOutputStream(name);
分析:新的密鑰庫可以是一個新的文件,也可以使用原有的“mykeystore”文件。這裏我們使用原有的文件,這樣程序運行起來更像是修改原有的口令。
(3) 執行KeyStore類的store( )方法
ks.store(output,newpass);
分析:其中ks是第1步得到的KeyStore類型的對象,store( )方法 第一個參數是第2步指定的文件輸出流,第二個參數是爲密鑰庫設置的新口令。
★代碼與分析:
完整代碼如下:
import java.io.*;
import java.security.*;
public class SetStorePass{
public static void main(String args[ ]) throws Exception{
char[ ] oldpass=args[0].toCharArray();
char[ ] newpass=args[1].toCharArray();
String name="mykeystore";
FileInputStream in=new FileInputStream(name);
KeyStore ks=KeyStore.getInstance("JKS");
ks.load(in,oldpass);
in.close();
FileOutputStream output=new FileOutputStream(name);
ks.store(output,newpass);
output.close();
}
}
★運行程序
運行程序之前可先輸入如下命令將mykeystore密鑰庫文件備份到文件mykeystore.bat中:
C:\java\ch5>copy mykeystore mykeystore.bak
1 file(s) copied
輸入“java SetStorePass wshr.ut mynewpass”運行程序,將密鑰庫mykeystore的口令將由原來的wshr.ut修改爲mynewpass,這時再使用keytool查看該 密鑰庫時,應該輸入新口令:mynewpass,否則將無法訪問密鑰庫。如:
C:\java\ch5>keytool -list -keystore mykeystore
輸入keystore密碼: wshr.ut
keytool錯誤: java.io.IOException: Keystore was tampered with, or password was incorrect
C:\java\ch5>keytool -list -keystore mykeystore
輸入keystore密碼: mynewpass
Keystore 類型: jks
Keystore 提供者: SUN
您的 keystore 包含 3 輸入
mytest, 2002-12-5, keyEntry,
認證指紋 (MD5): B2:DC:75:CD:60:B7:1E:7A:97:EE:E8:A4:31:D6:26:C6
tmp, 2002-12-5, keyEntry,
認證指紋 (MD5): 5C:FA:ED:8E:AE:30:1B:2B:CF:39:ED:4D:6F:94:E1:6B
tmp1, 2002-12-5, keyEntry,
認證指紋 (MD5): 51:F7:46:AE:20:87:BF:3F:03:B9:54:56:AF:CC:09:F5
從中可以看出密鑰庫內容仍舊保持不變。再輸入“java SetStorePass mynewpass wshr.ut”運行程序,則mykeystore的口令恢復到wshr.ut。可以使用4.1.2小節的DigestInput程序檢驗 mykeystore在口令恢復後和最初的文件是否相同。如:
C:\java\ch5>java DigestInput mykeystore
f48d2ec0d5da98bde2d390374d1bd3c2
C:\java\ch5>java DigestInput mykeystore.bak
f48d2ec0d5da98bde2d390374d1bd3c2
可見,密鑰庫文件mykeystore和修改口令前備份過的mykeystore.bak文件的消息摘要相同,兩個文件完全相同。
5.3.5 Java程序修改密鑰庫條目的口令及添加條目
★ 實例說明
本實例使用Java程序修改密鑰庫mykeystore中指定條目的口令。同時演示了Java程序從密鑰庫中提取證書、私鑰以及增加條目等操作。
★ 編程思路:
Java程序修改密鑰庫指定條目的口令,實際上將密鑰庫中該條目別名對應的證書、私鑰提取出來,重新寫入密鑰庫。重新寫入時使用相同的別名,口令則重新設置。
具體步驟如下:
(1) 讀取相關參數
String name="mykeystore";
String alias=args[0];
char[ ] storepass="wshr.ut".toCharArray();
char[ ] oldkeypass=args[1].toCharArray();
char[ ] newkeypass=args[2].toCharArray();
分析:這裏不妨通過第一個命令行參數讀入別名的名稱,第二個命令行參數讀入該別名條目的原有口令,第三個命令行參數讀入爲該別名條目設置的新口令。其中wshr.ut爲密鑰庫的口令。
(2) 獲取密鑰庫mykeystorede的KeyStore對象,並加載密鑰庫
FileInputStream in=new FileInputStream(name);
KeyStore ks=KeyStore.getInstance("JKS");
ks.load(in,storepass);
分析:該步驟和 5.3.2小節類似。
(3) 獲取別名對應的條目的證書鏈
Certificate[ ] cchain=ks.getCertificateChain(alias);
分析:執行KeyStore對象的getCertificateChain ( )方法,獲取其參數對應的條目的證書鏈。
(4) 讀取別名對應的條目的私鑰
PrivateKey pk=(PrivateKey)ks.getKey(alias,oldkeypass);
分析:執行KeyStore對象的getKey( )方法,獲取其參數對應的條目的私鑰,保護私鑰的口令也通過方法的參數傳入。
(5) 向密鑰庫中添加條目
ks.setKeyEntry(alias,pk,newkeypass,cchain);
分析:執行KeyStore對象的setKeyEntry ( )方法,方法的第一個參數指定所添加條目的別名,這裏別名使用欲修改的條目的別名,這樣將覆蓋欲修改的條目。如果使用新的別名,則會增加一個條目。第二個 參數爲該條目的私鑰,第三個參數爲設置的新口令,第四個參數是對應於該私鑰的公鑰的證書鏈。
(6) 將KeyStore對象內容寫入新文件
FileOutputStream output=new FileOutputStream("333");
ks.store(output,storepass);
分析:執行KeyStore類的store( )方法,將修改後的KeyStore保存在新的文件中。這裏不妨使用文件名“333”。也可以像5.3.4小節一樣使用原有的文件名“mykeystore”
★代碼與分析:
完整代碼如下:
import java.io.*;
import java.security.*;
import java.security.cert.Certificate;
public class SetKeyPass{
public static void main(String args[ ]) throws Exception{
String name="mykeystore";
String alias=args[0];
char[ ] storepass="wshr.ut".toCharArray();
char[ ] oldkeypass=args[1].toCharArray();
char[ ] newkeypass=args[2].toCharArray();
FileInputStream in=new FileInputStream(name);
KeyStore ks=KeyStore.getInstance("JKS");
ks.load(in,storepass);
Certificate[ ] cchain=ks.getCertificateChain(alias);
PrivateKey pk=(PrivateKey)ks.getKey(alias,oldkeypass);
ks.setKeyEntry(alias,pk,newkeypass,cchain);
in.close();
FileOutputStream output=new FileOutputStream("333");
ks.store(output,storepass);
output.close();
}
}
★運行程序
輸入“java SetKeyPass mytest wshr.ut newpass”運行程序,將把密鑰庫mykeystore的別名爲mytest的條目的口令由“wshr.ut”改爲“newpass”,並重新保存到 文件333中。然後我們可以使用5.3.2小節的方法將口令再改一次,從中可以看到mytest條目的口令(主密碼)確實已經是程序中設置的 “newpass”了:
C:\java\ch5>keytool -keypasswd -alias mytest -keystore 333
輸入keystore密碼: wshr.ut
輸入的主密碼newpass
新 的主密碼: 123456
重新輸入新 的主密碼: 123456
5.3.6 Java程序檢驗別名及刪除條目
★ 實例說明
本實例使用Java程序檢驗某個別名是否在密鑰庫中,若在,則在密鑰庫中刪除該條目。
★ 編程思路:
KeyStore類提供的containsAlias( )方法可以判斷參數中指定的別名條目是否在密鑰庫中,deleteEntry( ) 方法可以刪除方法參數中指定的別名條目,
具體步驟如下:
(1) 獲取密鑰庫KeyStore類型的對象
FileInputStream in=new FileInputStream(name);
KeyStore ks=KeyStore.getInstance("JKS");
ks.load(in,pass.toCharArray());
分析:該步驟和5.3.3小節相同。
(2) 檢驗別名條目是否在密鑰庫中
ks.containsAlias(args[0])
分析:這裏不妨從命令行參數讀取別名字符串,將其作爲參數傳遞給KeyStore對象的containsAlias( )方法。若字符串指定的別名在KeyStore對象對應的密鑰庫中存在,則返回true;否則返回false。
(3) 刪除別名對應的條目
ks.deleteEntry(args[0])
分析:將從命令行參數讀取的別名字符串作爲參數傳遞給KeyStore對象的deleteEntry ( )方法。
(4) 重新寫入
ks.deleteEntry(args[0])
分析:將從命令行參數讀取的別名字符串作爲參數傳遞給KeyStore對象的deleteEntry ( )方法。
(5) 將KeyStore對象內容寫入新文件
FileOutputStream output=new FileOutputStream(name);
ks.store(output,pass.toCharArray());
分析:和5.3.4小節一樣,執行KeyStore類的store( )方法,將修改後的KeyStore重新保存在mykeystore文件中。也可以像5.3.5小節一樣換個文件名保存新的密鑰庫。
★運行程序
我們先輸入“java DeleteAlias Hi”運行程序,試圖刪除一個不存在的條目“hi”,然後輸入“java DeleteAlias tmp1”運行程序,將5.3.2小節執行5.3.1.bat得到的臨時條目tmp1刪除,最後使用keytool查看刪除的效果。操作過程及效果如下:
C:\java\ch5>java DeleteAlias Hi
Alias not exist
C:\java\ch5>java DeleteAlias tmp1
Alias tmp1 deleted
C:\java\ch5>keytool -list -keystore mykeystore
輸入keystore密碼: wshr.ut
Keystore 類型: jks
Keystore 提供者: SUN
您的 keystore 包含 2 輸入
mytest, 2002-12-5, keyEntry,
認證指紋 (MD5): B2:DC:75:CD:60:B7:1E:7A:97:EE:E8:A4:31:D6:26:C6
tmp, 2002-12-5, keyEntry,
認證指紋 (MD5): 5C:FA:ED:8E:AE:30:1B:2B:CF:39:ED:4D:6F:94:E1:6B
5.4數字證書的簽發
從5.1節可以看出,任何人都可以很方便地創建數字證書,宣稱某個公鑰是某個人或機構所擁有的。這樣,當用戶收到某個證書後,如何確定這個證書宣稱的內容到底是真的還是假的呢?
和現實生活中一樣,要有權威的機構檢查證書中內容的真實性,然後再簽發證書(在證書上蓋章)。在計算機的世界中,這個蓋章的過程就是數字簽名,即權威機構用自己的私鑰對證書進行數字簽名。
這種權威機構已經有很多,如Verisign,Thawte等,這些機構稱爲CA(Certification Authorities),這些CA的公鑰已經以證書的形式包含在許多操作系統中。
CA檢查別人的數字證書,確定可靠後用使用自己的私鑰爲證書籤名。用戶收到這樣的證書後,用相應CA的公鑰進行檢驗,若檢驗通過,說明證書是可 靠的。這是因爲根據數字簽名的原理,其他人僞造的簽名這樣的簽名將無法通過驗證。本書附錄中介紹瞭如何將5.1節創建的證書交給一個著名的 CA(Verisign)簽發,以增強其可信性。本章使用Java程序實現CA,對其他證書進行簽名,這樣便於理解簽發證書的實質。
5.4.1 確定CA的權威性——安裝CA的證書
本章假定你自己是CA,全名是“CN=Xu Yingxiao, OU=Network Center, O=Shanghai University, L=ZB, ST=Shanghai, C=CN”,簡稱“, Xu Yingxiao”。你在計算機中的身份以5.2.3小節得到的證書mytest.cer來代表,它是在第5.1.3小節創建的,隨證書一起創建的還有和 證書中的公鑰相對應的私鑰,全, 部保存在密鑰庫mykeystore中,別名爲mytest。
該證書的文本如圖5-2所示。可以通過電子郵件、網頁或蓋有公章的正式文件確定如圖5-2所示的文件mytest.cer是權威的,在部門或單 位內部是值得信任的,並可將其認證指紋(即消息摘要,拇印)公佈。在5.2.4小節中我們已經看到其認證指紋爲:MD5: B2:DC:75:CD:60:B7:1E:7A:97:EE:E8:A4:31:D6:26:C6。
用戶得到CA自身的證書後,可以將證書安裝在計算機操作系統中,以便計算機自動檢驗其他證書是否值得信任。
★ 實例說明
本實例將代表CA“Xu Yingxiao”的證書文件mytest.cer安裝在用戶的機器中,以便在計算機中確立mytest.cer證書的權威性。
★運行程序
雙擊5.2.3小節導出的證書文件mytest.cer,出現圖5-5所示的證書窗口:
圖5-5 CA的證書
點擊窗口中的“安裝證書”按鈕,出現圖5-6所示的證書管理器導入嚮導窗口。
圖5-6 證書管理器導入嚮導窗口
點擊其中的“下一步”按鈕,出現圖5-7所示的選定證書存儲區的窗口。
圖5-7選定證書存儲區的窗口。
不妨使用該窗口的默認選擇:“根據證書類型,自動選擇證書存儲區”,繼續點擊其中的“下一步”按鈕,出現圖5-8所示的完成證書管理器導入嚮導的提示。
圖5-8完成證書管理器導入嚮導的提示
該窗口顯示證書將被存儲到“受信任的根目錄證書發行機構”區域,這樣,以後操作系統將自動信任由mytest.cer證書籤發的其他證書。點擊其中的“完成”按鈕,出現最後的確認窗口,如圖5-9所示。
圖5-9 最後覈實證書的窗口
如果用戶懷疑該證書是否正確,可以再覈實一遍該窗口中顯示的拇印,確認後點擊其中的“是”按鈕,最後提示導入成功。
以上操作主要是針對我們自己創建的CA的證書:mytest.cer,如果使用著名的CA如Verisign,則本小節的操作就不需要了,因爲Verisign等著名的CA的證書已經存儲在常用的操作系統中了。
此外,本小節的操作有一定的風險性,必須妥善保存mytest.cer對應的私鑰。因此這裏只是用於編程實驗,實驗結束最好使用5.5.3小節的方法撤銷本小節的操作。
5.4.2 驗證CA的權威性——顯示CA的證書
實例說明
本實例驗證5.4.1將代表CA“Xu Yingxiao”的證書mytest.cer安裝在用戶的機器中,以便在計算機中確立mytest.cer證書的權威性。
★運行程序
雙擊5.2.3小節導出的證書mytest.cer,出現圖5-10所示的證書窗口。
圖5-10 受信任的證書窗口
和圖5-5相比,證書窗口中原先顯示的“該證書發行機構根證書沒受信任”警告已經消失了,取而代之的是該證書的用途。點擊該窗口中的“證書路徑”標籤,出現圖5-11的證書路徑窗口,其中顯示了該證書是正確的。
圖5-11 證書路徑窗口
5.4.3 Java程序簽發數字證書
實例說明
本實例使用5.4.1小節確定的CA“Xu Yingxiao”對5.1.3小節創建的證書“Liu Fang”(證書文件爲5.2.3小節的lf.cer文件)進行簽發,該實例使我們對Verisign等CA是如何簽發證書的有一個實際的瞭解。
★ 編程思路:
CA簽發數字證書應該使用自己的私鑰,CA自身的證書中並不包含私鑰信息,因此需從密鑰庫mykeystore中提取。此外,由於被簽發的證書還需要知道CA的名字,這可以從CA的證書中獲得。
簽發證書實際上是創建了一個新的證書,本實例使用J2SDK內部使用的sun.security.x509包中的X509CertImpl類來 創建新的證書,該類的構造器中傳入有關新的證書各種信息,主要信息來自被簽發的lf.cer,只是對某些必須修改的信息如序列號、有效期、簽發者等進行重 新設置。最後使用X509CertImpl類的sign( )方法用CA的私鑰進行簽名。可以打印新的證書的信息,也可以將其保存在密鑰庫中。
具體步驟如下:
(1) 從密鑰庫讀取CA的證書
FileInputStream in=new FileInputStream(name);
KeyStore ks=KeyStore.getInstance("JKS");
ks.load(in,storepass);
java.security.cert.Certificate c1=ks.getCertificate(alias);
分析:這裏name的值爲“mykeystore”,alias的值爲“mytest”。和 5.2.7小節第1-4步相同。
(2) 從密鑰庫讀取CA的私鑰
PrivateKey caprk=(PrivateKey)ks.getKey(alias,cakeypass);
分析:該步驟和 5.3.5小節第4步類似,執行KeyStore對象的getKey( )方法,獲取其參數對應的條目的私鑰,保護私鑰的口令也通過方法的參數傳入。該口令必須和創建證書時所輸入的“主密碼”相同。所獲得的私鑰用於後面的簽名。
(3) 從CA的證書中提取簽發者信息
byte[] encod1=c1.getEncoded();
X509CertImpl cimp1=new X509CertImpl(encod1);
X509CertInfo cinfo1=(X509CertInfo)cimp1.get(X509CertImpl.NAME+
"."+X509CertImpl.INFO);
X500Name issuer=(X500Name)cinfo1.get(X509CertInfo.SUBJECT+
"."+CertificateIssuerName.DN_NAME);
分析:首先提取CA的證書的編碼,然後用該編碼創建X509CertImpl類型的對象,通過該對象的get( )方法獲取X509CertInfo類型的對象,該對象封裝了證書的全部內容,最後通過該對象的get( )方法獲得X500Name類型的簽發者信息。這些類在J2SDK1.4的API文檔中並無介紹,但可以直接使用。
(4) 獲取待簽發的證書
CertificateFactory cf=CertificateFactory.getInstance("X.509");
FileInputStream in2=new FileInputStream(args[0]);
java.security.cert.Certificate c2=cf.generateCertificate(in2);
分析:待簽發的證書可以像5.2.7小節那樣從密鑰庫中讀取,也可以像5.2.6小節那樣從導出的證書文件讀取。這裏不妨採用後面一種方法,其 編程步驟和5.2.6小節相同。證書文件的名稱不妨從命令行參數讀入,該證書可以使用在5.1.4小節中創建、在5.2.3小節導出的文件lf.cer。
(5) 從待簽發的證書提取證書信息
byte[] encod2=c2.getEncoded();
X509CertImpl cimp2=new X509CertImpl(encod2);
X509CertInfo cinfo2=(X509CertInfo)cimp2.get(
X509CertImpl.NAME+"."+X509CertImpl.INFO);
分析: 新證書的主要信息來自待簽發的證書,待簽發的證書中這些信息主要封裝在X509CertInfo對象中,所以和第3步類似,先提取待簽發者的證書編碼,然 後創建X509CertImpl類型的對象,最後通過該對象的get( )方法獲取X509CertInfo類型的對象。
以後就可以使用該對象創建新的證書了,再創建新證書之前,還需要使用其set( )方法對其中部分信息作一些必要的修改。
(6) 設置新證書有效期
Date begindate =new Date();
//30000 day
Date enddate =new Date(begindate.getTime()+3000*24*60*60*1000L);
CertificateValidity cv=new CertificateValidity(begindate,enddate);
cinfo2.set(X509CertInfo.VALIDITY,cv);
分析:新證書的開始生效時間不妨從簽發之時開始,因此首先使用new Date( )獲取當前時間。新證書截止日期不能超過CA,作爲測試,這裏不妨設置截止日期爲3000天以後,因此使用new Date( )再創建一個日期對象,其參數傳入長整型的值,即在原先日期的基礎上增加3000天的時間(毫秒數)。
最後通過這兩個日期創建CertificateValidity類型的對象,並把它作爲參數傳遞給上一步得到的X509CertInfo對象的set()方法以設置有效期。
(7) 設置新證書序列號
int sn=(int)(begindate.getTime()/1000);
CertificateSerialNumber csn=new CertificateSerialNumber(sn);
cinfo2.set(X509CertInfo.SERIAL_NUMBER,csn);
分析:每個證書有一個唯一的序列號,這裏不妨以當前的時間(以秒爲單位)爲序列號,創建CertificateSerialNumber對象, 並作爲參數傳遞給X509CertInfo對象的set()方法以設置序列號。
(8) 設置新證書籤發者
cinfo2.set(X509CertInfo.ISSUER+"."+CertificateIssuerName.DN_NAME,issuer);
分析:執行X509CertInfo對象的set()方法設置簽發者,傳入的參數即第3步得到的簽發者信息。
(9) 設置新證書籤名算法信息
AlgorithmId algorithm =
new AlgorithmId(AlgorithmId.md5WithRSAEncryption_oid);
info2.set(CertificateAlgorithmId.NAME+"."
+CertificateAlgorithmId.ALGORITHM, algorithm);
分析:首先生成AlgorithmId類型的對象,在其構造器中指定CA簽名該證書所使用的的算法爲md5WithRSA, 然後將其作爲參數傳遞給X509CertInfo對象的set()方法以設置簽名算法信息。
(10) 創建證書並使用CA的私鑰對其簽名
X509CertImpl newcert=new X509CertImpl(cinfo2);
newcert.sign(caprk,"MD5WithRSA");
分析:X509CertImpl類是X509證書的底層實現,將第5步得到的待簽發的證書信息(部分信息已經在7~9步作了修改)傳遞給其構造 器,將得到新的證書,執行其sign( )方法,將使用CA的私鑰對證書進行數字簽名,第一個參數即第1步獲得的CA的私鑰,第二個參數即簽名所用的算法。 這樣,就得到了經過CA簽名的證書。
(11) 將新證書存入密鑰庫
ks.setCertificateEntry("lf_signed", newcert) ;
FileOutputStream out=new FileOutputStream("newstore");
ks.store(out,"newpass".toCharArray());
分析:和5.3.5小節第6步類似,使用KeyStore對象的store( )方法將KeyStore對象中的內容寫入密鑰庫文件。store( )方法的第一個參數指定密鑰庫文件的文件輸出流,這裏不妨以“newstore”作爲新的密鑰庫文件名,也可以直接使用原有的密鑰庫 “mykeystore”覆蓋原有的密鑰庫。第二個參數爲密鑰庫文件設置保護口令,這裏不妨以“newpass”作爲口令。
在執行KeyStore對象的store( )方法之前,需要將上一步得到的簽過名的新證書寫入KeyStore對象,這裏使用了KeyStore對象的setCertificateEntry( )方法,其第一個參數設置了新證書在密鑰庫中的別名,第二個參數傳入上一步得到的證書。也可像5.3.5小節第6步類似的方法,使用KeyStore對象 的setKeyEntry( )方法,這時應該將被簽名證書lf.cer對應的私鑰先從mykeystore中提取出來,然後傳遞給setKeyEntry( )方法。並需用新證書構造數組傳遞給setKeyEntry( )方法。如:
PrivateKey prk=(PrivateKey)ks.getKey("lf","wshr.ut".toCharArray( ));
java.security.cert.Certificate[] cchain={newcert};
ks.setKeyEntry("signed_lf",prk,"newpass".toCharArray(),cchain);
★代碼與分析:
完整代碼如下:
import java.io.*;
import java.security.*;
import java.security.cert.*;
import java.util.*;
import java.math.*;
import sun.security.x509.*;
public class SignCert{
public static void main(String args[ ]) throws Exception{
char[] storepass="wshr.ut".toCharArray( );
char[] cakeypass="wshr.ut".toCharArray( );
String alias="mytest";
String name="mykeystore";
// Cert of CA-----c1
FileInputStream in=new FileInputStream(name);
KeyStore ks=KeyStore.getInstance("JKS");
ks.load(in,storepass);
java.security.cert.Certificate c1=ks.getCertificate(alias);
PrivateKey caprk=(PrivateKey)ks.getKey(alias,cakeypass);
in.close();
//得到簽發者
byte[] encod1=c1.getEncoded();
X509CertImpl cimp1=new X509CertImpl(encod1);
X509CertInfo cinfo1=(X509CertInfo)cimp1.get(X509CertImpl.NAME+
"."+X509CertImpl.INFO);
X500Name issuer=(X500Name)cinfo1.get(X509CertInfo.SUBJECT+
"."+CertificateIssuerName.DN_NAME);
// Cert of lf-----c2
CertificateFactory cf=CertificateFactory.getInstance("X.509");
FileInputStream in2=new FileInputStream(args[0]);
java.security.cert.Certificate c2=cf.generateCertificate(in2);
in2.close();
byte[] encod2=c2.getEncoded();
X509CertImpl cimp2=new X509CertImpl(encod2);
X509CertInfo cinfo2=(X509CertInfo)cimp2.get(
X509CertImpl.NAME+"."+X509CertImpl.INFO);
//設置新證書有效期
Date begindate =new Date();
//60 day
Date enddate =new Date(begindate.getTime()+3000*24*60*60*1000L); CertificateValidity cv=new CertificateValidity(begindate,enddate);
cinfo2.set(X509CertInfo.VALIDITY,cv);
//設置新證書序列號
int sn=(int)(begindate.getTime()/1000);
CertificateSerialNumber csn=new CertificateSerialNumber(sn);
cinfo2.set(X509CertInfo.SERIAL_NUMBER,csn);
//設置新證書籤發者
cinfo2.set(X509CertInfo.ISSUER+"."+
CertificateIssuerName.DN_NAME,issuer);
//設置新證書算法
AlgorithmId algorithm =
new AlgorithmId(AlgorithmId.md5WithRSAEncryption_oid);
cinfo2.set(CertificateAlgorithmId.NAME+
"."+CertificateAlgorithmId.ALGORITHM, algorithm);
// 創建證書
X509CertImpl newcert=new X509CertImpl(cinfo2);
// 簽名
newcert.sign(caprk,"MD5WithRSA");
System.out.println(newcert);
// 存入密鑰庫
ks.setCertificateEntry("lf_signed", newcert) ;
/*
PrivateKey prk=(PrivateKey)ks.getKey("lf",
"wshr.ut".toCharArray( ));
java.security.cert.Certificate[] cchain={newcert};
ks.setKeyEntry("lf_signed",prk,
"newpass".toCharArray(),cchain);
*/
FileOutputStream out=new FileOutputStream("newstore");
ks.store(out,"newpass".toCharArray());
out.close();
}
}
程序中添加了打印語句將新創建的證書的相關信息在屏幕上打印出來。
★運行程序
在當前目錄下存放5.1.3小節創建密鑰庫mykeystore,其中包含了CA的證書。當前目錄下同時有5.2.3小節導出的證書lf.cer,簽發之前假定我們已經覈實過lf.cer中包含的信息,確認無誤,接下來我們開始用CA對該證書進行簽名。
輸入“java SignCert lf.cer >1.txt”運行程序,則程序將從密鑰庫中取出CA的私鑰對lf.cer證書進行簽名,輸出結果已重定向到文件1.txt中,打開1.txt文件,可以看到如下有關新的證書的信息。
[
[
Version: V1
Subject: CN=Liu Fang, OU=Packaging, O=Shanghai University, L=ZB, ST=Shanghai, C=CN
Signature Algorithm: MD5withRSA, OID = 1.2.840.113549.1.1.4
Key: com.sun.net.ssl.internal.ssl.JSA_RSAPublicKey@ac2f9c
Validity: [From: Thu Dec 05 12:04:35 CST 2002,
To: Mon Feb 21 12:04:35 CST 2011]
Issuer: CN=Xu Yingxiao, OU=Network Center, O=Shanghai University, L=ZB, ST=Shanghai, C=CN
SerialNumber: [ 3deed053 ]
]
Algorithm: [MD5withRSA]
Signature:
0000: D2 3F 52 38 62 BF ED 59 D0 E5 B1 83 E3 4C 56 C9 .?R8b..Y.....LV.
0010: 9C 8F C8 37 13 35 31 2F 36 F7 A0 9E CD 04 2C 58 ...7.51/6.....,X
0020: 72 DE 0C B6 46 F9 AF CD 96 E3 2D CF 70 9E 1A E5 r...F.....-.p...
0030: 9A B3 D9 12 97 EA 7C 97 4A F9 E6 8B 93 52 C4 42 ........J....R.B
0040: 13 6F EC 43 FD 30 ED B2 19 92 13 FD 0B DA A6 8C .o.C.0..........
0050: 9B 3F 08 62 A9 9F 4B 23 CD A8 A0 CB BE 60 09 85 .?.b..K#.....`..
0060: E4 EC 3C 5E D7 CE BC 44 E7 F5 43 0B 01 EA 93 A3 ..<^...D..C.....
0070: CB EA 83 B3 BF 2F B4 2E 83 12 54 A4 55 AE E2 5C ...../....T.U..\
]
從中可以看出簽發者已經由原先的“CN=Liu Fang, OU=Packaging, O=Shanghai University, L=ZB, ST=Shanghai, C=CN”(自己給自己簽名),變爲了:“CN=Xu Yingxiao, OU=Network Center, O=Shanghai University, L=ZB, ST=Shanghai, C=CN”(CA的簽名)。有效期也設置成了3000天。
新的證書同時由密鑰保存在了密鑰庫newstore中,輸入如下命令可以查看密鑰庫中的新證書。
C:\java\ch5\Sign>keytool -list -keystore newstore
輸入keystore密碼: newpass
輸入keystore密碼: newpass
Keystore 類型: jks
Keystore 提供者: SUN
您的 keystore 包含 3 輸入
mytest, 2002-12-5, keyEntry,
認證指紋 (MD5): B2:DC:75:CD:60:B7:1E:7A:97:EE:E8:A4:31:D6:26:C6
lf_signed, 2002-12-5, trustedCertEntry,
認證指紋 (MD5): D3:7E:C0:72:5D:41:46:CA:7A:8E:85:21:1B:DA:89:0F
tmp, 2002-12-5, keyEntry,
認證指紋 (MD5): 5C:FA:ED:8E:AE:30:1B:2B:CF:39:ED:4D:6F:94:E1:6B
其中lf_signed即程序中籤發lf.cer生成的新證書,注意其類型爲“trustedCertEntry”而不是 “keyEntry”。這是因爲程序中使用的是KeyStore對象的setCertificateEntry( )方法保存證書,它只將證書導入了密鑰庫,而沒有導入對應的私鑰。如果採用本小節第11步的分析中的方法,使用KeyStore對象的 setKeyEntry( )方法,則這裏顯示的lf_signed就和其他兩個條目一樣是“keyEntry”了。
5.4.4 數字證書籤名後的發佈
★ 實例說明
5.4.3小節將簽名後的數字證書保存在密鑰庫中,本實例介紹CA對某個人或機構的證書進行簽名後,如何將簽名後的證書提交給對方。
★ 運行程序
CA對數字證書籤名後可以將其導出到文件。類似5.2.3小節,在命令行中一行輸入如下命令:
keytool –export –alias lf_signed -keystore newstore –storepass newpass –rfc –file lf_signed.cer
這樣5.4.3小節的簽名後的數字證書將被到出到文件lf_signed.cer,可以將該文件E-mail給對方,也可以用Windows的 記事本打開該證書文件,將其編碼內容通過E-mail或Web等方式發佈。其他人只要將其文本粘貼下來,保存到文件名以“.cer”爲後綴的文本文件中即 可。該證書內容如下所示:
在Windows中雙擊該文件,將出現圖5-12所示的證書窗口。
圖5-12 簽名後的證書窗口
和圖5-3相比,它不再有“該證書發行機構根證書沒受信任”的警告,這是因爲在5.4.1小節中我們已經在機器中安裝了其發行機構的證 書:mytest.cer。此外,圖5-12中還可以看出其顯示的頒發者已經是Xu Yingxiao,而不是圖5-3所示的自己給自己簽名(自簽名證書)。
實際上,本書附錄中介紹的將證書交給Verisign等CA簽發後得到的也是類似這裏得到的lf_signed文件的證書。
得到該證書後,可以如5.5.1和5.5.2小節那樣驗證和顯示簽名後的證書,也可以如5.5.3小節那樣將簽名後的證書導入密鑰庫。
5.5數字證書的檢驗
第5.1節我們創建了自簽名的數字證書,第5.4節創建了通過我們自己的CA簽名的數字證書,在附錄中通過Verisign等CA簽名的證書,本節介紹如何驗證這些證書是否有效。
5.5.1 Java程序驗證數字證書的有效期
★ 實例說明
本實例使用5.2.3小節得到的證書文件mytest.cer和lf.cer以及5.4.4小節得到的證書文件lf_signed,演示瞭如何檢驗證書在某個日期是否有效。
★ 編程思路:
使用5.2.6或5.2.7小節得到的X509Certificate類型的對象可以方便地檢驗證書在某個日期是否有效,只要執行其 checkValidity( )方法,方法的參數中傳入日期。若已經過期,則程序會生成CertificateExpiredException異常,若尚未開始生效,則生成 CertificateNotYetValid異常。
具體步驟如下:
(1) 獲取X509Certificate類型的對象
CertificateFactory cf=CertificateFactory.getInstance("X.509");
FileInputStream in=new FileInputStream(args[0]);
java.security.cert.Certificate c=cf.generateCertificate(in);
X509Certificate t=(X509Certificate) c;
分析:和5.2.6小節一樣,從命令行參數讀入需要驗證的證書文件。也可以像5.2.7小節那樣從密鑰庫中直接讀取證書。
(2) 獲取日期
Calendar cld=Calendar.getInstance( );
int year=Integer.parseInt(args[1]);
int month=Integer.parseInt(args[2])-1;
int day=Integer.parseInt(args[3]);
cld.set(year,month,day);
Date d=cld.getTime( );
分析:我們的目的是驗證證書在某個日期是否有效,因此不妨從命令行讀入年月日,由此生成Date( )對象。由於Date類的很多設置年月日的方法已經不提倡使用,因此改用Calendar類,Calendar類也是一個工廠類,通過 getInstance( )方法獲得對象,然後使用set( )方法設置時間,最後通過其getTime( )方法獲得Date( )對象,
由於Calendar類的set( )方法參數是整數,因此對命令行參數讀入的年月日字符串使用Integer.parseInt( )方法轉換爲整型數。由於Calendar類的set( )方法設置月份時從0開始,0代表1月,11代表12月,因此命令行讀入的月份要減去1。
(3) 檢驗證書
t.checkValidity(d);
分析:執行第1步得到的X509Certificate對象的checkValidity( )方法,方法參數傳入第2步得到的Date對象。
(4) 處理CertificateExpiredException異常
catch(CertificateExpiredException e){
System.out.println("Expired");
System.out.println(e.getMessage());
}
分析:第3步若生成CertificateExpiredException異常,表明證書在指定的日期已經過期,可以在catch語句中作相關處理。這裏簡單地打印一句“Expired”,並顯示相關的異常信息。
(5) 處理CertificateNotYetValidException異常
catch(CertificateNotYetValidException e){
System.out.println("Too early");
System.out.println(e.getMessage());
}
分析:第3步若生成CertificateNotYetValidException異常,表明證書在指定的日期尚未開始生效,可以在catch語句中作相關處理。這裏簡單地打印一句“Too early”,並顯示相關的異常信息。
★ 代碼與分析
完整代碼如下:
import java.io.*;
import java.security.*;
import java.security.cert.*;
import java.util.*;
public class CheckCertValid{
public static void main(String args[ ]) throws Exception{
// X509Certificate對象
CertificateFactory cf=CertificateFactory.getInstance("X.509");
FileInputStream in=new FileInputStream(args[0]);
java.security.cert.Certificate c=cf.generateCertificate(in);
in.close();
X509Certificate t=(X509Certificate) c;
//讀取日期
Calendar cld=Calendar.getInstance();
int year=Integer.parseInt(args[1]);
int month=Integer.parseInt(args[2])-1; // as 0 is Jan, 11
int day=Integer.parseInt(args[3]);
cld.set(year,month,day);
Date d=cld.getTime();
System.out.println(d);
//檢驗有效期
try{
t.checkValidity(d);
System.out.println("OK");
}catch(CertificateExpiredException e){ //過期
System.out.println("Expired");
System.out.println(e.getMessage());
}
catch(CertificateNotYetValidException e){ //尚未生效
System.out.println("Too early");
System.out.println(e.getMessage());
}
}
}
★運行程序
在當前目錄下保存要檢驗的證書,如5.2.3小節得到的證書文件mytest.cer和lf.cer,5.4.4小節得到的證書文件lf_signed mytest.cer等。
如mytest.cer有效期是2002年12月5日至2013年11月17日,則可輸入如下幾個命令測試程序。輸入“java CheckCertValid mytest.cer 2002 12 4”運行程序檢測證書mytest.cer在2002年12月4日是否有效,屏幕輸出如下:
Wed Dec 04 13:26:41 CST 2002
Too early
NotBefore: Thu Dec 05 10:56:03 CST 2002
輸入“java CheckCertValid mytest.cer 2003 6 16”運行程序檢測證書mytest.cer在2003年6月16日是否有效,屏幕輸出如下:
Mon Jun 16 13:27:42 CST 2003
OK
輸入“java CheckCertValid mytest.cer 2013 11 18”運行程序檢測證書mytest.cer在2013年11月19日是否有效,屏幕輸出如下:
Mon Nov 18 13:28:16 CST 2013
Expired
NotAfter: Sun Nov 17 10:56:03 CST 2013
5.5.2 使用Windows查看證書路徑驗證證書的簽名
★ 實例說明
本實例使用5.4.4小節得到的經過CA簽名的證書文件lf_signed,演示了在Windows中查看該證書是否值得信任。
★運行程序
直接在Windows中用鼠標雙擊證書文件lf_signed,出現5.4.4小節圖5-12所示的證書窗口,由於在5.4.1小節中已經將籤 發者“CN=Xu Yingxiao, OU=Network Center, O=Shanghai University, L=ZB, ST=Shanghai, C=CN”的證書安裝在Windows中了。因此圖5-12檢驗後沒有警告信息,表明該證書通過了驗證。
進一步可在圖5-12中點擊“證書路徑” 標籤,將出現圖5-13所示的證書路徑窗口,從中可以看出該證書“Liu Fang”是由“Xu Yingxiao”簽發的。Windows自動檢驗證書的簽名,並在“證書狀態”下面顯示“該證書是正確的”。如果點擊圖5-13證書路徑中的“Xu Yingxiao”,則圖5-13窗口中的“查看證書”按鈕將被激活,點擊後將顯示簽發者“Xu Yingxiao”的證書。
如果我們再使用密鑰庫lfkeystore中的證書“Liu Fang”(lf.cer)對應的私鑰給其他證書籤名,則證書路徑將更長。
圖5-13 簽名後證書的證書路徑
5.5.3 Windows中卸載證書
★ 實例說明
本實例將5.4.1安裝的證書“Xu Yingxiao”卸載,然後再查看由“Xu Yingxiao”簽名的證書。
★運行程序
Windows中啓動Internet Explorer瀏覽器,選擇“工具/選項”菜單,出現圖5-14所示的Internet選項窗口,點擊其中的內容標籤,出現圖5-15所示的 Internet選項內容設置窗口。點擊其中的“證書”按鈕,出現圖5-16所示的證書管理器窗口。這裏,顯示了所有安裝的證書。在5.4.1小節安裝的 證書“Xu Yingxiao”被自動安裝在“受信任的根目錄證書發行機構”中,點擊該標籤,出現圖5-17的窗口,在其中選中“Xu Yingxiao”證書,點擊刪除,隨後出現兩次提示,選擇“是”確認刪除,則5.4.1小節的操作中添加的證書將被刪除。Windows不再信任 CA“Xu Yingxiao”的證書,也就不再信任“Xu Yingxiao”所簽發的證書。此時像5.5.2小節那樣點擊“lf_sign”將顯示“由於信息不足,不能驗證該證書”,如圖5-18所示。
圖5-14 Internet選項窗口
圖 5-15 Internet選項內容設置窗口
圖 5-16 證書管理器窗口
圖 5-17 受信任的根目錄證書發行機構
圖 5-18 不再受信任的已簽名證書
5.5.4 Java程序使用CA公鑰驗證已簽名的證書
5.5.2小節是通過Windows程序自動驗證證書是否合法。本小節通過Java程序驗證某個證書是否確實是某個CA簽發的。
★ 實例說明
本實例使用CA“Xu Yingxiao”的證書mytest.cer,檢驗某個證書文件lf_signed.cer,看它是否確實是CA“Xu Yingxiao”簽發的。
★ 編程思路:
首先讀取CA的證書mytest.cer,取得其公鑰,然後讀取待檢驗的證書lf_signed.cer,獲得其證書對象後,執行證書對象的Verify( )方法進行驗證,
具體步驟如下:
(1) 獲取CA的證書
CertificateFactory cf=CertificateFactory.getInstance("X.509");
FileInputStream in1=new FileInputStream(args[0]);
java.security.cert.Certificate cac=cf.generateCertificate(in1);
分析:不妨和5.2.4一樣從文件讀入CA“Xu Yingxiao”的證書,文件名不妨從命令行參數傳入。
(2) 獲取待檢驗的證書
FileInputStream in2=new FileInputStream(args[1]);
java.security.cert.Certificate lfc=cf.generateCertificate(in2);
分析:和上一步類似,從不妨和5.2.4一樣從文件讀入待檢驗的證書,文件名不妨從命令行參數傳入。
(3) 獲取CA的公鑰
PublicKey pbk=cac.getPublicKey( );
分析:使用CA證書對象的getPublicKey( )方法獲得CA的公鑰,用於證書檢驗。
(4) 檢驗證書
lfc.verify(pbk);
pass=true;
分析:執行被檢驗證書對象的verify( )方法,其參數傳入第3步獲得的公鑰。如果該證書確實是由該公鑰簽名的,將正常運行,可以執行到pass=true一句,否則將產生異常對象。
(5) 處理異常對象
catch(Exception e){
pass=false;
}
分析:主要有四類異 常,NoSuchAlgorithmException,InvalidKeyException,NoSuchProviderException,SignatureException 和CertificateException等,詳見API文檔,可以分不同異常分別處理,也可統一給pass變量賦值false.
★代碼與分析:
完整代碼如下:
import java.io.*;
import java.security.*;
import java.security.cert.*;
public class CheckCertSign{
public static void main(String args[ ]) throws Exception{
String cacert=args[0];
String lfcert=args[1];
//CA的證書
CertificateFactory cf=CertificateFactory.getInstance("X.509");
FileInputStream in1=new FileInputStream(cacert);
java.security.cert.Certificate cac=cf.generateCertificate(in1);
in1.close();
//待檢驗的證書
FileInputStream in2=new FileInputStream(lfcert);
java.security.cert.Certificate lfc=cf.generateCertificate(in2);
in2.close();
PublicKey pbk=cac.getPublicKey( );
boolean pass=false;
try{
lfc.verify(pbk);
pass=true;
}
catch(Exception e){
pass=false;
System.out.println(e);
}
if(pass){
System.out.println("The Certificate is signed by the CA Xu Yingxiao");
}
else{
System.out.println("!!!not signed by the CA Xu Yingxiao");
}
}
}
★運行程序
輸入java CheckCertSign mytest.cer lf_signed.cer運行程序,將檢驗lf_signed.cer是否確實由mytest.cer對應的CA所簽發,程序輸出如下:
C:\java\ch5\check>java CheckCertSign mytest.cer lf_signed.cer
The Certificate is signed by the CA Xu Yingxiao
輸入java CheckCertSign mytest.cer mytest.cer運行程序,將檢驗mytest.cer是否確實由mytest.cer對應的CA所簽發(自簽名),程序輸出如下:
The Certificate is signed by the CA Xu Yingxiao
輸入java CheckCertSign mytest.cer lf.cer運行程序,將檢驗lf.cer是否確實由mytest.cer對應的CA所簽發,程序輸出如下:
java.security.SignatureException: Signature does not match.
!!!The Certificate is not signed by the CA Xu Yingxiao
同樣,如果輸入如下命令再創建一個名稱相同的證書:
keytool -genkey -dname "CN=Xu Yingxiao, OU=Network Center, O=Shanghai University, L=ZB, ST=Shanghai, C=CN" -alias mytest -keyalg RSA -keysize 1024 -keystore hackerstore -keypass wshr.ut -storepass wshr.ut
使用該的證書重複本章的步驟冒充CA“Xu Yingxiao”給其他證書籤名,儘管簽名後的證書中顯示出來仍是“CN=Xu Yingxiao, OU=Network Center, O=Shanghai University, L=ZB, ST=Shanghai, C=CN”簽名的,但使用本小節的方法將可以發現它無法通過CA“Xu Yingxiao”公鑰的驗證。因爲只有真正的CA“Xu Yingxiao”對應的私鑰簽發的證書纔可通過本小節的驗證。
本章介紹了數字證書的概念、創建、讀取、簽發及初步驗證等。驗證數字證書證在編程中用得比較多,下一章介紹和驗證數字證書的相關的證書鏈及其驗證。
(轉)http://www.gzsec.com/oldversion/filesys/news_view.asp?newsid=306