10.4 证书使用

Java 6提供了完善的数字证书管理实现,我们几乎无需关注相关具体算法,仅通过操作密钥库和数字证书就可完成相应的加密/解密和签名/验证操作。密钥库管理私钥,数字证书管理公钥,私钥和密钥分属消息传递两方,进行加密消息传递。

因此,我们可以将密钥库看做私钥相关操作的入口,数字证书则是公钥相关操作的入口。

本文以KeyTool产生的密钥库和证书为例,演示证书使用相关操作。加载密钥库需要提供密钥库文件路径和密钥库密码,如代码清单10-24所示。

代码清单10-24 加载密钥库


/**

*获得KeyStore

*@param keyStorePath密钥库路径

*@param password密码

*@return KeyStore密钥库

*/

private static KeyStore getKeyStore(String keyStorePath, String password)throws Exception{

//实例化密钥库

KeyStore ks=KeyStore.getInstance(KeyStore.getDefaultType());

//获得密钥库文件流

FileInputStream is=new FileInputStream(keyStorePath);

//加载密钥库

ks.load(is, password.toCharArray());

//关闭密钥库文件流

is.close();

return ks;

}


加载密钥库后,我们就能通过相应的方法获得私钥,也可以获得数字证书。获得私钥实现如代码清单10-25所示。

代码清单10-25 由密钥库获得私钥


//获得密钥库

KeyStore ks=getKeyStore(keyStorePath, password);

//获得私钥

PrivateKey privateKey=(PrivateKey)ks.getKey(alias, password.toCharArray());


这里通过我们已实现的getKeyStore()获得密钥库,输入别名(参数alias)和密码(参数password)即可获得私钥。获得私钥后,可按照本书第8章介绍的RSA算法实现进行“私钥加密,公钥解密”和“公钥加密,私钥解密”两项操作。

如果我们需要从密钥库中获得签名算法,只能通过由密钥库中获取的数字证书并强转为X509Certificate实例,通过其getSigAlgName()方法获得对应的签名算法。完整代码如代码清单10-26所示。

代码清单10-26 由密钥库获得数字证书构建数字签名对象


//获得密钥库

KeyStore ks=getKeyStore(keyStorePath, password);

//获得证书

X509Certificate x509Certificate=(X509Certificate)ks.getCertificate(alias);

//构建签名,由证书指定签名算法

Signature signature=Signature.getInstance(x509Certificate.getSigAlgName());


获得数字签名对象后,我们可使用私钥进行签名操作。相关实现与本书第9章RSA算法实现非常相似。

相比于密钥库操作,数字证书的操作更为简单,我们只需要给出数字整证书的路径,并加载它即可。完整代码实现如代码清单10-27所示。

代码清单10-27 加载数字证书


/**

*获得Certificate

*@param certificatePath证书路径

*@return Certificate证书

*@throws Exception

*/

private static Certificate getCertificate(String certificatePath)throws Exception{

//实例化证书工厂

CertificateFactory certificateFactory=CertificateFactory.getInstance("X.509");

//取得证书文件流

FileInputStream in=new FileInputStream(certificatePath);

//生成证书

Certificate certificate=certificateFactory.generateCertificate(in);

//关闭证书文件流

in.close();

return certificate;

}


目前,Java 6仅支持X.509类型的数字证书。

通过以上方式加载得到数字证书后,我们可以直接获取公钥,如代码清单10-28所示。

代码清单10-28 由数字证书获得公钥


//获得证书

Certificate certificate=getCertificate(certificatePath);

//获得公钥

PublicKey publicKey=certificate.getPublicKey();


得到公钥后,我们就可以参考本书第8章介绍的RSA算法实现进行“公钥加密,私钥解密”和“私钥加密,公钥解密”两项操作。

如果使用数字证书进行验证签名操作时,需要将获得的证书对象强转为X509Certificate实例。完整代码如代码清单10-29所示。

代码清单10-29 初始化签名对象


//获得证书

X509Certificate x509Certificate=(X509Certificate)getCertificate(certificatePath);

//由证书构建签名

Signature signature=Signature.getInstance(x509Certificate.getSigAlgName());

//由证书初始化签名,实际上是使用了证书中的公钥

signature.initVerify(x509Certificate);


这里需要注意的是,数字签名对象初始化(initVerify()方法)时使用了数字证书而非公钥。该方法在内部实际上是使用数字证书的公钥。

至此,我们可使用数字证书做“私钥验证”操作。

上述操作的完整代码实现如代码清单10-30所示。

代码清单10-30 基于密钥库和数字证书的加密/解密和签名/验证操作


import java.io.FileInputStream;

import java.security.KeyStore;

import java.security.PrivateKey;

import java.security.PublicKey;

import java.security.Signature;

import java.security.cert.Certificate;

import java.security.cert.CertificateFactory;

import java.security.cert.X509Certificate;

import javax.crypto.Cipher;

/**

*证书组件

*@author梁栋

*@version 1.0

*/

public abstract class CertificateCoder{

//类型证书X509

public static final String CERT_TYPE="X.509";

/**

*由KeyStore获得私钥

*@param keyStorePath密钥库路径

*@param alias别名

*@param password密码

*@return PrivateKey私钥

*@throws Exception

*/

private static PrivateKey getPrivateKeyByKeyStore(String keyStorePath,

String alias, String password)throws Exception{

//获得密钥库

KeyStore ks=getKeyStore(keyStorePath, password);

//获得私钥

return(PrivateKey)ks.getKey(alias, password.toCharArray());

}

/**

*由Certificate获得公钥

*@param certificatePath证书路径

*@return PublicKey公钥

*@throws Exception

*/

private static PublicKey getPublicKeyByCertificate(String certificatePath)

throws Exception{

//获得证书

Certificate certificate=getCertificate(certificatePath);

//获得公钥

return certificate.getPublicKey();

}

/**

*获得Certificate

*@param certificatePath证书路径

*@return Certificate证书

*@throws Exception

*/

private static Certificate getCertificate(String certificatePath)throws Exception{

//实例化证书工厂

CertificateFactory certificateFactory=CertificateFactory.getInstance(CERT_TYPE);

//取得证书文件流

FileInputStream in=new FileInputStream(certificatePath);

//生成证书

Certificate certificate=certificateFactory.generateCertificate(in);

//关闭证书文件流

in.close();

return certificate;

}

/**

*获得Certificate

*@param keyStorePath密钥库路径

*@param alias别名

*@param password密码

*@return Certificate证书

*@throws Exception

*/

private static Certificate getCertificate(String keyStorePath, String alias,

String password)throws Exception{

//获得密钥库

KeyStore ks=getKeyStore(keyStorePath, password);

//获得证书

return ks.getCertificate(alias);

}

/**

*获得KeyStore

*@param keyStorePath密钥库路径

*@param password密码

*@return KeyStore密钥库

*@throws Exception

*/

private static KeyStore getKeyStore(String keyStorePath, String password)

throws Exception{

//实例化密钥库

KeyStore ks=KeyStore.getInstance(KeyStore.getDefaultType());

//获得密钥库文件流

FileInputStream is=new FileInputStream(keyStorePath);

//加载密钥库

ks.load(is, password.toCharArray());

//关闭密钥库文件流

is.close();

return ks;

}

/**

*私钥加密

*@param data待加密数据

*@param keyStorePath密钥库路径

*@param alias别名

*@param password密码

*@return byte[]加密数据

*@throws Exception

*/

public static byte[]encryptByPrivateKey(byte[]data, String keyStorePath,

String alias, String password)throws Exception{

//取得私钥

PrivateKey privateKey=getPrivateKeyByKeyStore(keyStorePath, alias, password);

//对数据加密

Cipher cipher=Cipher.getInstance(privateKey.getAlgorithm());

cipher.init(Cipher.ENCRYPT_MODE, privateKey);

return cipher.doFinal(data);

}

/**

*私钥解密

*@param data待解密数据

*@param keyStorePath密钥库路径

*@param alias别名

*@param password密码

*@return byte[]解密数据

*@throws Exception

*/

public static byte[]decryptByPrivateKey(byte[]data, String keyStorePath,

String alias, String password)throws Exception{

//取得私钥

PrivateKey privateKey=getPrivateKeyByKeyStore(keyStorePath, alias, password);

//对数据加密

Cipher cipher=Cipher.getInstance(privateKey.getAlgorithm());

cipher.init(Cipher.DECRYPT_MODE, privateKey);

return cipher.doFinal(data);

}

/**

*公钥加密

*@param data待加密数据

*@param certificatePath证书路径

*@return byte[]加密数据

*@throws Exception

*/

public static byte[]encryptByPublicKey(byte[]data, String certificatePath)

throws Exception{

//取得公钥

PublicKey publicKey=getPublicKeyByCertificate(certificatePath);

//对数据加密

Cipher cipher=Cipher.getInstance(publicKey.getAlgorithm());

cipher.init(Cipher.ENCRYPT_MODE, publicKey);

return cipher.doFinal(data);

}

/**

*公钥解密

*@param data待解密数据

*@param certificatePath证书路径

*@return byte[]解密数据

*@throws Exception

*/

public static byte[]decryptByPublicKey(byte[]data, String certificatePath)

throws Exception{

//取得公钥

PublicKey publicKey=getPublicKeyByCertificate(certificatePath);

//对数据加密

Cipher cipher=Cipher.getInstance(publicKey.getAlgorithm());

cipher.init(Cipher.DECRYPT_MODE, publicKey);

return cipher.doFinal(data);

}

/**

*签名

*@param keyStorePath密钥库路径

*@param alias别名

*@param password密码

*@return byte[]签名

*@throws Exception

*/

public static byte[]sign(byte[]sign, String keyStorePath, String alias,

String password)throws Exception{

//获得证书

X509Certificate x509Certificate=(X509Certificate)getCertificate

(keyStorePath, alias, password);

//构建签名,由证书指定签名算法

Signature signature=Signature.getInstance(x509Certificate.getSigAlgName());

//获取私钥

PrivateKey privateKey=getPrivateKeyByKeyStore(keyStorePath, alias, password);

//初始化签名,由私钥构建

signature.initSign(privateKey);

signature.update(sign);

return signature.sign();

}

/**

*验证签名

*@param data数据

*@param sign签名

*@param certificatePath证书路径

*@return boolean验证通过为真

*@throws Exception

*/

public static boolean verify(byte[]data, byte[]sign, String

certificatePath)throws Exception{

//获得证书

X509Certificate x509Certificate=(X509Certificate)getCertificate(certificatePath);

//由证书构建签名

Signature signature=Signature.getInstance(x509Certificate.getSigAlgName());

//由证书初始化签名,实际上是使用了证书中的公钥

signature.initVerify(x509Certificate);

signature.update(data);

return signature.verify(sign);

}

}


我们假定密钥库文件zlex.keystore存储在D盘根目录,数字证书文件zlex.cer也存储在D盘根目录。对上述代码做验证,如代码清单10-31所示。

代码清单10-31 基于密钥库和数字证书的加密/解密和签名/验证操作测试用例


import static org.junit.Assert.*;

import org.apache.commons.codec.binary.Hex;

import org.junit.Test;

/**

*证书校验

*@author梁栋

*@version 1.0

*/

public class CertificateCoderTest{

private String password="123456";

private String alias="www.zlex.org";

private String certificatePath="d:/zlex.cer";

private String keyStorePath="d:/zlex.keystore";

/**

*公钥加密—私钥解密

*@throws Exception

*/

@Test

public void test1()throws Exception{

System.err.println("公钥加密—私钥解密");

String inputStr="数字证书";

byte[]data=inputStr.getBytes();

//公钥加密

byte[]encrypt=CertificateCoder.encryptByPublicKey(data, certificatePath);

//私钥解密

byte[]decrypt=CertificateCoder.decryptByPrivateKey(encrypt,

keyStorePath, alias, password);

String outputStr=new String(decrypt);

System.err.println("加密前:\n"+inputStr);

System.err.println("解密后:\n"+outputStr);

//验证数据一致

assertArrayEquals(data, decrypt);

}

/**

*私钥加密—公钥解密

*@throws Exception

*/

@Test

public void test2()throws Exception{

System.err.println("私钥加密—公钥解密");

String inputStr="数字签名";

byte[]data=inputStr.getBytes();

//私钥加密

byte[]encodedData=CertificateCoder.encryptByPrivateKey(data,

keyStorePath, alias, password);

//公钥加密

byte[]decodedData=CertificateCoder.decryptByPublicKey(encodedData,

certificatePath);

String outputStr=new String(decodedData);

System.err.println("加密前:\n"+inputStr);

System.err.println("解密后:\n"+outputStr);

assertEquals(inputStr, outputStr);

}

/**

*签名验证

*@throws Exception

*/

@Test

public void testSign()throws Exception{

String inputStr="签名";

byte[]data=inputStr.getBytes();

System.err.println("私钥签名—公钥验证");

//产生签名

byte[]sign=CertificateCoder.sign(data, keyStorePath, alias, password);

System.err.println("签名:\n"+Hex.encodeHexString(sign));

//验证签名

boolean status=CertificateCoder.verify(data, sign, certificatePath);

System.err.println("状态:\n"+status);

//校验

assertTrue(status);

}

}


观察控制台输出,“公钥加密,私钥解密”输出如下所示:


公钥加密—私钥解密

加密前:

数字证书

解密后:

数字证书


“私钥加密,公钥解密”输出如下所示:


私钥加密—公钥解密

加密前:

数字签名

解密后:

数字签名


双向加密/解密操作完全通过!

我们再来关注签名/验证操作,控制台输出如下所示:


私钥签名—公钥验证

签名:

0f0344c3db3d3349a9037ce98468ae8ea1aa9fd70f47139928c2e986185401971e0382d991b56893

e07190304079955c79a06e1750045e27723e29d538664bd92c6dddeb0a6b51a76c32de5b5bdb8d2be493

dfbabc8d94855b215d0b5b775b6008f44caa0c4f6ca574cb5cea8c69427bbce07c662fcdcaef8a22e2dd 50d462cb

状态:

true


我们在无需知晓密钥和算法的前提下,完成了数据加密/解密和签名/验证操作。