开发CDN平台过程,涉及到需要对证书做有效性验证,开发了以下工具类接口
package hs.cdn.tool;
import org.apache.commons.codec.binary.Base64;
import org.bouncycastle.asn1.pkcs.RSAPrivateKey;
import org.bouncycastle.util.io.pem.PemObject;
import org.bouncycastle.util.io.pem.PemReader;
import ms.core.tool.DateTimeTool;
import ms.core.tool.JsonTool;
import ms.core.tool.SysTool;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import javax.crypto.Cipher;
import javax.security.cert.X509Certificate;
import java.io.*;
import java.nio.charset.Charset;
import java.security.*;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.RSAPrivateKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* openssl 证书公钥私钥读取加密解密工具类,主要用于验证上传的 openssl生成的证书和私钥文件是否正确的问题
* 主要逻辑:
* 1、根据私钥文件读取私钥
* 2、根据公钥文件读取公钥
* 3、根据证书文件读取公钥
* 4、根据证书公钥加密字符串
* 5、根据证书私钥解密字符串
* 6、如果字符串经过证书公钥加密后,再根据证书私钥解密后能后还原,说明上传的证书和私钥是正确的
*/
public class OpenSslUtils {
private static final String DEFAULT_ENCODING = "UTF-8";
private static final Charset DEFAULT_CHARSET = Charset.forName(DEFAULT_ENCODING);
/** 默认是RSA/NONE/PKCS1Padding */
private static final String CIPHER_ALGORITHM = "RSA/ECB/PKCS1Padding";
/** RSA密钥长度必须是64的倍数,在512~65536之间。默认是1024 */
private static final int KEY_SIZE = 1024;
/** RSA最大加密明文大小:明文长度(bytes) <= 密钥长度(bytes)-11 */
private static final int MAX_ENCRYPT_BLOCK = KEY_SIZE / 8 - 11;
/** RSA最大解密密文大小 */
private static final int MAX_DECRYPT_BLOCK = KEY_SIZE / 8;
private static Logger logger = LogManager.getLogger(OpenSslUtils.class);
/**
* 利用开源的工具类解析openssl私钥,openssl私钥文件格式为pem,
* 需要去除页眉页脚后才能被程序识别
* @param txtKey
* @return
*/
public static PrivateKey getPrivateKey(String txtKey) {
PrivateKey privKey = null;
PemReader pemReader = null;
try {
pemReader = new PemReader(new StringReader(txtKey));
PemObject pemObject = pemReader.readPemObject();
byte[] pemContent = pemObject.getContent();
//支持从PKCS#1或PKCS#8 格式的私钥文件中提取私钥
if (pemObject.getType().endsWith("RSA PRIVATE KEY")) {
//取得私钥 for PKCS#1
RSAPrivateKey asn1PrivKey = RSAPrivateKey.getInstance(pemContent);
RSAPrivateKeySpec rsaPrivKeySpec = new RSAPrivateKeySpec(asn1PrivKey.getModulus(), asn1PrivKey.getPrivateExponent());
KeyFactory keyFactory= KeyFactory.getInstance("RSA");
privKey= keyFactory.generatePrivate(rsaPrivKeySpec);
} else if (pemObject.getType().endsWith("PRIVATE KEY")) {
//取得私钥 for PKCS#8
PKCS8EncodedKeySpec privKeySpec = new PKCS8EncodedKeySpec(pemContent);
KeyFactory kf = KeyFactory.getInstance("RSA");
privKey = kf.generatePrivate(privKeySpec);
}
} catch (Exception e) {
logger.error(e, e);
}
try {
if (pemReader != null) pemReader.close();
} catch (IOException e) {
}
return privKey;
}
/**
* 从key文件读取私钥内容,并获取key对象
* @param file
* @return
*/
public static PrivateKey getPrivateKey(File file) {
if (file == null || !file.exists()) return null;
return getPrivateKey(SysTool.readTxtFile2Str(file, "UTF8"));
}
/**
* 从证书文本获取公钥
* @param content
* @return
*/
public static PublicKey getPublicKey(String txtKey) {
InputStream in = null;
PublicKey publicKey = null;
try {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
in = new ByteArrayInputStream(txtKey.getBytes());
Certificate crt = cf.generateCertificate(in);
publicKey = crt.getPublicKey();
} catch (Exception e) {
logger.error(e, e);
}
try {
if (in!=null) in.close();
} catch(Exception e) {
}
return publicKey;
}
/**
* 从证书公钥内容串,提取证书信息
* @param txtKey
* @return
*/
public static Map<String, Object> getPublicKeyInfo(String txtKey){
Map<String, Object> ret = new HashMap<String, Object>();
InputStream in = null;
try {
in = new ByteArrayInputStream(txtKey.getBytes());
X509Certificate crt = X509Certificate.getInstance(in);
ret.put("expiry", crt.getNotAfter());
ret.put("start", crt.getNotBefore());
ret.put("issue", crt.getIssuerDN().getName());
ret.put("ver", crt.getVersion());
} catch (Exception e) {
logger.error(e, e);
}
try {
if (in!=null) in.close();
} catch(Exception e) {
}
return ret;
}
/**
* 从证书文件获取公钥
*
* @param file
* @return
*/
public static PublicKey getPublicKey(File file) {
if (file==null || !file.exists()) return null;
return getPublicKey(SysTool.readTxtFile2Str(file, "UTF8"));
}
/**
* 从openssl公钥文件中读取公钥
* @param file
* @return
*/
public static PublicKey getPublicKeyFromFile(File file) {
if (file == null) return null;
PublicKey pubKey = null;
PemReader pemReader = null;
try {
pemReader = new PemReader(new FileReader(file));
PemObject pemObject = pemReader.readPemObject();
byte[] pemContent = pemObject.getContent();
//公钥需要使用x509格式编码
X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(pemContent);
KeyFactory kf = KeyFactory.getInstance("RSA");
pubKey = kf.generatePublic(pubKeySpec);
} catch (Exception e) {
logger.error(e, e);
}
try {
if (pemReader != null) pemReader.close();
} catch (Exception ex) {
}
return pubKey;
}
/**
* 公钥加密
* @param key
* @param plainBytes
* @return
*/
private static byte[] encrypt(PublicKey key, byte[] plainBytes) {
try {
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, key);
if (plainBytes.length <= MAX_ENCRYPT_BLOCK) {
return cipher.doFinal(plainBytes);
}
return cipher.doFinal(plainBytes);
} catch (Exception e) {
logger.error(e,e);
return null;
}
}
/**
* 根据公钥加密字符串
* @param key
* @param plainText 需要加密的字符串
* @return
*/
public static String encrypt(PublicKey key, String plainText) {
byte[] encodeBytes = encrypt(key, plainText.getBytes(DEFAULT_CHARSET));
return Base64.encodeBase64String(encodeBytes);
}
/**
* 私钥解密
* @param key
* @param encodedText
* @return
*/
private static String decrypt(PrivateKey key, byte[] encodedText) {
try {
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, key);
int inputLen = encodedText.length;
if (inputLen <= MAX_DECRYPT_BLOCK) {
return new String(cipher.doFinal(encodedText), DEFAULT_CHARSET);
}
byte[] cache = cipher.doFinal(encodedText);
return new String(cache);
} catch (Exception e) {
logger.error(e,e);
return null;
}
}
/**
* 根据私钥解密加密过的字符串
* @param key
* @param encodedText 加密过的字符串
* @return 解密后的字符串
*/
public static String decrypt(PrivateKey key, String encodedText) {
byte[] bytes = Base64.decodeBase64(encodedText);
return decrypt(key, bytes);
}
/**
* 验证证书
* @param cert
* @return
*/
public static String validateCert(File cert){
if (cert == null) return "证书CRT文件不能为空";
PublicKey publicKey = getPublicKey(cert);
if (publicKey == null) return "无法读取证书公钥,证书CRT文件格式错误";
return null;
}
/**
* 验证私钥
* @param privateKey
* @return
*/
public static String validatePrivateKey(File privateKey){
if (privateKey == null) return "证书私钥不能为空";
PrivateKey privKey = getPrivateKey(privateKey);
if (privKey == null) return "无法读取证书私钥,证书私钥文件格式错误";
return null;
}
/**
* 验证证书私钥是否匹配,如果不匹配返回错误消息
* @param cert
* @param privateKey
* @return 错误消息
*/
public static String validate(File cert, File privateKey) {
String res = validateCert(cert); //验证证书
if((res!=null)&&(res.length()>0)){
return res; //返回错误消息
}
res = validatePrivateKey(privateKey); //验证私钥
if((res!=null)&&(res.length()>0)){
return res; //返回错误消息
}
PublicKey publicKey = getPublicKey(cert);
PrivateKey privKey = getPrivateKey(privateKey);
String str = "test"; //测试字符串
String encryptStr = OpenSslUtils.encrypt(publicKey, str); //根据证书公钥对字符串进行加密
String decryptStr = OpenSslUtils.decrypt(privKey, encryptStr); //根据证书私钥对加密字符串进行解密
if(!str.equals(decryptStr)){ //字符串根据证书公钥加密,私钥解密后不能还原说明证书与私钥不匹配
return "证书与私钥不匹配";
}
return "success";
}
/**
* 验证公私钥是否匹配
* @param txtPrivateKey 私钥串
* @param txtPublicKey 公钥串
* @return 0-匹配;1-私钥错误;2-公钥错误;3-不匹配
*/
public static int keyMatched(String txtPrivateKey, String txtPublicKey) {
PrivateKey prvKey = OpenSslUtils.getPrivateKey(txtPrivateKey);
if (prvKey==null) return 1;
PublicKey pubKey = OpenSslUtils.getPublicKey(txtPublicKey);
if (pubKey==null) return 2;
String str = "public key & private key";
String encryptStr = encrypt(pubKey, str);
String decryptStr = decrypt(prvKey, encryptStr);
return str.equals(decryptStr) ? 0:3;
}
public static void main(String[] args) {
File privateKeyFile = new File("d:/4key.txt");
PrivateKey privKey = OpenSslUtils.getPrivateKey(privateKeyFile);
System.out.println("私钥:" + privKey);
File certFile = new File("d:/4.txt");
PublicKey publicKey = OpenSslUtils.getPublicKey(certFile);
System.out.println("公钥:" + publicKey);
//公私钥匹配验证
String validateResult = validate(certFile,privateKeyFile);
System.out.println("匹配结果:" + validateResult);
//加密
String str = "this is a test";
String encryptStr = OpenSslUtils.encrypt(publicKey, str);
System.out.println("密文:" + encryptStr);
//解密
String decryptStr = OpenSslUtils.decrypt(privKey, encryptStr);
System.out.println("明文:" + decryptStr);
//获取证书信息,如有效期等
Map<String, Object> map = getPublicKeyInfo(SysTool.readTxtFile2Str(certFile, "UTF8"));
if (map.get("expiry")!=null) {
Date dt = (Date) map.get("expiry");
System.out.println("证书有效期:"+DateTimeTool.dateToDateString(dt));
}
System.out.println(JsonTool.beanToJson(map));
}
}