第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