Java安全基础 part 2 -- 数字证书

4. 数字证书

这个部分比较长,并且我现在还有些概念有一点混淆,就先当成是一个入门吧,以后如果需要的话再详细的研究。

正如您可能注意到的,数字签名中描述的数字签名方式有一个问题。它证实消息是由特定的发送方发送的,但我们怎么才能知道发送方确实是她所说的那个人呢?如果某人实际上是 Amanda,却自称 Alice,并对一条消息进行了签名,那会怎么样呢?因为Amanda可以用自己的公私钥来代替Alice的公私钥,而Bob并不能区分。

我们可以通过使用数字证书来改进安全性,它将一个身份标识连同公钥一起进行封装,并由称为证书发行机构或 CA(Certificate Authority)的第三方进行数字签名。java支持X.509证书标准。

这里还要说一下证书的一些概念

首先是证书的格式问题,国际上通用的X.509标准的证书格式如下所示:

Version 告诉这个X.509证书是哪个版本的,目前有v1、V2、v3
Serial Number 由证书分发机构设置证书的序列号
Signature Algorithm Identifier 证书采用什么样的签名算法
Issuer Name 证书发行者名,也就是给这个证书签名的机构名
Validity Period 证书有效时间范围
Subject Name 被证书发行机构签名后的公钥拥有者或实体的名字,采用X.500协议,在Internet上的标志是惟一的。例如:CN=Java,OU=JCP,O=JCP,C=CN表示一个subject name。

在证书的种类上,又可以分为CA证书,自颁发证书和自签证书。
CA证书是由一个CA颁发给另一个CA的证书。自颁发证书:是指证书的颁发者和主体是同一个CA的证书。自签证书:是自颁发证书的一种特殊情形,由CA使用其私钥来对证书进行签名,而相应公钥就在证书中。在实际应用中,“CA证书”通常指的是颁发者和主体是不同的证书,自签证书通常也称作“根证书”。自签证书是自颁发证书的一种,在如下情形中,一张证书是自颁发证书而不是自签证书:CA在进行密钥更替时,可能会存在两个密钥对(新密钥对和旧密钥对),可能会存在用新私钥签发旧公钥或用旧私钥签发新公钥的情形,如此形成的证书是自颁发证书,却不是自签证书。

这里对证书的类别的概念还有一些混淆。

先说明下,下面是针对自签证书的通信过程的描述

它的大致认证流程如下所示:
假设有如下图的这样一个通信过程,

		┌───────┐
│ │
┌──→│ CA │←──┐
│ │ │ │
│ └───────┘ │
│ │
│ │
↓ ↓
┌───────┐ ┌───────┐
│ │ │ │
│ A │←─────→│ B │
│ │ │ │
└───────┘ └───────┘


(1) A把自己的公钥送到CA(Certificate Authority)
(2) CA用自己的私钥和A的公钥生成A的证书CertA
(3) B同样把自己的公钥送到CA
(4) B得到CA发布的证书CertB
(5) A告知B证书CertA
(6) B告知A证书CertB

这里只说明B验证A身份的过程。
首先,B用CA的证书中(CA会生成一个公私钥对,私钥用于签名A的公钥,形成A的证书,另外还有一个可以公开的证书,里面保存了公钥,用于解密A的证书)提取公钥并验证A的证书,等于说提取了A的公钥,这样当A再次发送签名后的消息来认证身份时,就可以确定A的身份了,因为其他人是没有A的私钥的,只有A的私钥加密的消息才能用前面B获得的公钥来解密。

这样,我们来重复本文最上面的过程,因为Amanda无法获得CA的数字证书(因为CA认证的权威性,可以看成CA会调查Amanda的身份,Amanda不会从CA处获得一个证明她是Alice的证书),她最多能获得的只有Alice的公钥和CA的公钥,并且Bob已经通过Alice的证书获得了Alice的公钥,并确定这个就是Alice的而不是Amanda的,所以Amanda也就无法进行认证了。

在Java中,可以使用keytool程序来成生一个自己的自签证书。

示例如下这是我机器上的运行效果

(1) 首先是生成公私密钥对。

	C:\Program Files\Java\jdk1.6.0_11\bin>keytool -genkey -alias zddkeypair -keyalg
RSA -keysize 1024 -sigalg MD5withRSA
输入keystore密码:
再次输入新密码:
您的名字与姓氏是什么?
[Unknown]: zhang
您的组织单位名称是什么?
[Unknown]: my_company
您的组织名称是什么?
[Unknown]: my_group
您所在的城市或区域名称是什么?
[Unknown]: bejing
您所在的州或省份名称是什么?
[Unknown]: bejing
该单位的两字母国家代码是什么
[Unknown]: CN
CN=zhang, OU=my_company, O=my_group, L=bejing, ST=bejing, C=CN 正确吗?
[否]: y

输入<zddkeypair>的主密码
(如果和 keystore 密码相同,按回车):


(2) 导出自签证书。

	C:\Program Files\Java\jdk1.6.0_11\bin>keytool -export -rfc -alias zddkeypair -fi
le certificate.crt
输入keystore密码:
保存在文件中的认证 <certificate.crt>


生成的certificate.crt文件是一个类似如下内容的文件:

	-----BEGIN CERTIFICATE-----
MIICRTCCAa6gAwIBAgIESXRCyjANBgkqhkiG9w0BAQUFADBnMQswCQYDVQQGEwJDTjEPMA0GA1UE
CBMGYmVqaW5nMQ8wDQYDVQQHEwZiZWppbmcxETAPBgNVBAoMCG15X2dyb3VwMRMwEQYDVQQLDApt
eV9jb21wYW55MQ4wDAYDVQQDEwV6aGFuZzAeFw0wOTAxMTkwOTA3MjJaFw0wOTA0MTkwOTA3MjJa
MGcxCzAJBgNVBAYTAkNOMQ8wDQYDVQQIEwZiZWppbmcxDzANBgNVBAcTBmJlamluZzERMA8GA1UE
CgwIbXlfZ3JvdXAxEzARBgNVBAsMCm15X2NvbXBhbnkxDjAMBgNVBAMTBXpoYW5nMIGfMA0GCSqG
SIb3DQEBAQUAA4GNADCBiQKBgQCVjmXRQfi4m+7rSvVHazcSehSRkjmKUEr8t6c61fjyF+fkhL/V
XZaGlh/wd7DABWlEMNISnVjCWQUzSyNy8s+qJLC9Q3Ucvuq2ETvBFT6S12sUbgxISZgL6q7GFKi/
Pvm0F0Y0EJ8tQvDeS2WUQQMoja5S/68nufG298/QYscp5QIDAQABMA0GCSqGSIb3DQEBBQUAA4GB
AHR0kxfq8PycD/Oxy204RfjExKFcRQ4oy78wTlBvL764ODQur0t3sVaklMvp3rKq8YuDwJl2azrg
CtVAVq23K7AhIbIasBV0CHv/Ozit2FOc6asf3fqfPapnhdptFG/OBKLY2wJ5VVBBdnWJJQN1Xma6
4pGPES3JTOtr9LTQSrom
-----END CERTIFICATE-----



下面就可以使用Java的CertPath API来对证书进行操作了,主要涉及的类包括java.security.cert.CertificateFactory和java.security.cert.X509Certificate

(1) 读取证书信息:

	public int readNormal(String caName) {
try {
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
InputStream is = new FileInputStream(caName);
X509Certificate x509certificate = (X509Certificate) certFactory.generateCertificate(is);

System.out.println(x509certificate.getType());// 类型,此处为X.509
System.out.println(Integer.toString(x509certificate.getVersion()));// 版本
System.out.println(x509certificate.getSubjectDN().getName());// 标题
System.out.println(x509certificate.getNotBefore().toString());// 得到开始有效日期
System.out.println(x509certificate.getNotAfter().toString());// 得到截止日期
System.out.println(x509certificate.getSerialNumber().toString(16));// 得到序列号
System.out.println(x509certificate.getIssuerDN().getName());// 得到发行者名
System.out.println(x509certificate.getSigAlgName());// 得到签名算法
System.out.println(x509certificate.getPublicKey().getAlgorithm());// 得到公钥算法

is.close();

} catch (Exception exception) {
exception.printStackTrace();
return -1;
}
return 0;
}


程序的输出结果如下:

	X.509
3
CN=zhang, OU=my_company, O=my_group, L=bejing, ST=bejing, C=CN
Mon Jan 19 17:07:22 CST 2009
Sun Apr 19 17:07:22 CST 2009
497442ca
CN=zhang, OU=my_company, O=my_group, L=bejing, ST=bejing, C=CN
SHA1withRSA
RSA


(2) 另外还可以用字符串的形式读出证书中的所有信息,代码如下:

	public int readBin(String caName) {
try {
InputStream is = new FileInputStream(caName);
DataInputStream dis = new DataInputStream(is);
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
byte[] bytes = new byte[dis.available()];
dis.readFully(bytes);
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
while (bais.available() > 0) {
X509Certificate cert = (X509Certificate) certFactory.generateCertificate(bais);
System.out.println(cert.toString());
}
is.close();
dis.close();
} catch (Exception e) {
e.printStackTrace();
return -1;
}
return 0;
}


程序的输出结果如下:

	[
[
Version: V3
Subject: CN=zhang, OU=my_company, O=my_group, L=bejing, ST=bejing, C=CN
Signature Algorithm: SHA1withRSA, OID = 1.2.840.113549.1.1.5

Key: Sun RSA public key, 1024 bits
modulus: 105021964031260379880263196724140193515047424583156551071835767950517790035107801381623633906826635553202089529186569829948173161732525424091312886420289852680956880344315643908394178428572596666051842308193134159605407089750744578899723615725614803522663035751060997575607368955265575418023953448248180943333
public exponent: 65537
Validity: [From: Mon Jan 19 17:07:22 CST 2009,
To: Sun Apr 19 17:07:22 CST 2009]
Issuer: CN=zhang, OU=my_company, O=my_group, L=bejing, ST=bejing, C=CN
SerialNumber: [ 497442ca]

]
Algorithm: [SHA1withRSA]
Signature:
0000: 74 74 93 17 EA F0 FC 9C 0F F3 B1 CB 6D 38 45 F8 tt..........m8E.
0010: C4 C4 A1 5C 45 0E 28 CB BF 30 4E 50 6F 2F BE B8 ...\E.(..0NPo/..
0020: 38 34 2E AF 4B 77 B1 56 A4 94 CB E9 DE B2 AA F1 84..Kw.V........
0030: 8B 83 C0 99 76 6B 3A E0 0A D5 40 56 AD B7 2B B0 ....vk:...@V..+.
0040: 21 21 B2 1A B0 15 74 08 7B FF 3B 38 AD D8 53 9C !!....t...;8..S.
0050: E9 AB 1F DD FA 9F 3D AA 67 85 DA 6D 14 6F CE 04 ......=.g..m.o..
0060: A2 D8 DB 02 79 55 50 41 76 75 89 25 03 75 5E 66 ....yUPAvu.%.u^f
0070: BA E2 91 8F 11 2D C9 4C EB 6B F4 B4 D0 4A BA 26 .....-.L.k...J.&

]


实际情况下怎样使用证书呢?还是看一个实际的例子,我们可以假设A和B要共享一个绝密的文件F,B信任并拥有A的证书,也就是说B拥有A的公钥。那么A通过A和B共知的加密算法(对称密钥算法,比如DES算法)先加密文件F,然后对加密后的F进行签名和散列摘要(比如MD5算法,目的是保证文件的完整性),然后把F发送到B。B收到文件后,先用A的证书中的公钥验证签名,然后再用通过共知的加密算法解密,就可以得到原文件了。这里使用的数字签名,可以保证B得到的文件,就是A的,A不能否认其不拥有文件F,因为只有A拥有可以让A的公钥验证其签名的私钥,同时这里使用DES算法加密,使得文件有保密性。

下面举一个使用自签证书的例子:

首先,发送消息方对消息进行私钥加密

	public static void crypt(byte[] cipherText, String file) {
try {
// 生成私钥
KeyGenerator keyGen = KeyGenerator.getInstance("DES");
keyGen.init(56);
Key key = keyGen.generateKey();

// 生成DES的Cipher
Cipher cdes = Cipher.getInstance("DES");
cdes.init(Cipher.ENCRYPT_MODE, key);
byte[] ct = cdes.doFinal(cipherText);

try {
// 将加密后的文件写回磁盘
FileOutputStream out = new FileOutputStream(file);
out.write(ct);
out.close();
} catch (IOException e) {
e.printStackTrace();
}
} catch (Exception e) {
e.printStackTrace();
}
}


然后把消息发送给CA进行消息签名,或者说生成数字证书

	public void signature(byte[] sigText, String file, String pswd, String keyStore, String alias) {
char[] kpass;
int i;
try {
KeyStore ks = KeyStore.getInstance("JKS");
// keyStore默认为名字为.keystore的隐藏文件,在用户的主目录下
BufferedInputStream ksbufin = new BufferedInputStream(new FileInputStream(keyStore));
// 访问KeyStore的密码
kpass = new char[pswd.length()];
for (i = 0; i < pswd.length(); i++) {
kpass[i] = pswd.charAt(i);
}
ks.load(ksbufin, kpass);

// 取得CA的私钥来进行数字签名
PrivateKey priv = (PrivateKey) ks.getKey(alias, kpass);
Signature rsa = Signature.getInstance("MD5withRSA");
rsa.initSign(priv);
rsa.update(sigText);
byte[] sig = rsa.sign();
System.out.println("sig is done");
try {
FileOutputStream out = new FileOutputStream(file);
out.write(sig);
out.close();
} catch (IOException e) {
e.printStackTrace();
}
} catch (Exception e) {
e.printStackTrace();
}
}


最后,接收消息端对数字证书进行验证

	public static void veriSignature(byte[] updateData, byte[] sigedText, String certName) {
try {
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
FileInputStream fin = new FileInputStream(certName);
X509Certificate cert = (X509Certificate) certFactory.generateCertificate(fin);
// 通过自签证书获得公钥
PublicKey pub = cert.getPublicKey();
Signature rsa = Signature.getInstance("MD5withRSA");
rsa.initVerify(pub);
rsa.update(updateData);
// 验证
boolean verifies = rsa.verify(sigedText);
System.out.println("verified " + verifies);
if (verifies) {
System.out.println("Verify is done!");
} else {
System.out.println("verify is not successful");
}
} catch (Exception e) {
e.printStackTrace();
}
}


这样就完成了整个的消息传输过程。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值