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();
		}
	}


这样就完成了整个的消息传输过程。

你可能感兴趣的:(java,C++,c,算法,C#)