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