与银行的交互中为了保证数据保密以及防止数据篡改,通常使用RSA非对称加密算法对数据进行加密和签名,现在也因为安全监管的要求,开始使用SM2算法。对接过程中发现对密钥的生成,存储和读取有很多不解的地方,该文用于记录遇到的相关问题。文中只会涉及到openssl以及Java security库的简单应用
密钥生成
私钥生成
openssl genrsa -out rsa_private.pem 2048
- genrsa:是openssl生成RSA私钥的标准命令
- -out:t 指定输出的文件
- 2048 指rsa算法长度,可以替换为其他长度,例如1024。
PKCS#8标准转换
需要注意的是openssl默认生成的是PKCS#1标准的密钥,存储格式是PEM。Java标准库默认是不支持PKCS#1标准,因此需要转换为PKCS#8标准。
openssl pkcs8 -topk8 -inform PEM -in rsa_private.pem -outform PEM -nocrypt > rsa_private_pkcs8.pem
- pkcs8:是openssl转换pcks8的标准命令
- -topk8:通常,输入时需要 PKCS#8 私钥,并且将写入传统格式的私钥。使用 -topk8 选项,情况正好相反:它读取传统格式的私钥并写入 PKCS#8 格式的密钥。
- -inform:输入key的格式,可以是PEM,也可以是DER
- -in:输入文件
- -outform:输出格式,可以是PEM,也可以是DER
- -nocrypt:对密钥不加密
PEM和DER是密钥的两种编码方式,PEM格式是以-----BEGIN PRIVATE KEY-----
或-----BEGIN PUBLIC KEY-----
开头,以-----END PRIVATE KEY-----
或者-----END PUBLIC KEY-----
结尾,中间是经过Base64编码后的内容,每64字节做一次换行。
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0E4UTGrrbvjmK/1Fvm4R
jic0hAn4N54mFCPeCOVZ5VTo5mVlU1mxyaIpFvjOH811JRsYezmiXE3lXF2qqkOY
GFB5CqFmqgxyEpXtKWMbYS1Ou0fzHEZJHHioqx7SBEYBuHsQu8Y+dMort09V7kJv
AS7CjJqlXZBPa/PWGRG6RwGU2XUt8DDa8mNA632G5Z1I612yVMP8K5xTMcisJSKN
zZpmw+pKd5Atzbx1AgWfUmc6n++jNYqMBSpMEqXePf+dAt/9y0cMeS/9UOmQ6YUu
Iyr5CZxU0TtGE2AlFemz7NBKiIa8ol78cgyl15HZx5Ip9PJmHDcH6c2XcqtCD5M4
awIDAQAB
-----END PUBLIC KEY-----
DER格式是直接密钥的二进制编码。
如果需要从PKCS#8转换为PKCS#1格式可以使用如下命令
openssl rsa -in rsa_private_pkcs8.pem -out rsa_private.pem
导出公钥
-- 导出pem格式
openssl rsa -in rsa_private.pem -pubout -outform PEM -out rsa_public.pem
-- 导出DER格式
openssl rsa -in rsa_private.pem -pubout -outform DER -out rsa_public.der
标准转换方法与私钥转换一致
自签名证书生成
# 使用自有私钥生成证书
openssl req -new -x509 -days 365 -key rsa_private.key -out cert.crt
- -new:是生成证书请求
- -x509: 生成证书格式
- -key:指定私钥,这里pkcs#1或者PKCS#8都是可以的
- -out:输出证书文件
Java读取密钥文件
读取PEM格式私钥
public static PrivateKey readPemPrivateKey(String filename) throws Exception {
// 读取文件
String key = new String(Files.readAllBytes(Paths.get(filename)), Charset.defaultCharset());
String privateKeyPEM = key
.replace("-----BEGIN PRIVATE KEY-----", "")
.replaceAll("\n", "")
.replace("-----END PRIVATE KEY-----", "");
byte[] encoded = Base64.getDecoder().decode(privateKeyPEM);
PKCS8EncodedKeySpec pkcs8 = new PKCS8EncodedKeySpec(encoded);
KeyFactory kf = KeyFactory.getInstance("RSA");
return kf.generatePrivate(pkcs8);
}
读取PEM格式公钥
public static PublicKey readPemPublicKey(String filename) throws Exception {
String key = new String(Files.readAllBytes(Paths.get(filename)), Charset.defaultCharset());
String publicKeyPEM = key
.replace("-----BEGIN PUBLIC KEY-----", "")
.replaceAll(System.lineSeparator(), "")
.replace("-----END PUBLIC KEY-----", "");
byte[] encoded = Base64.getDecoder().decode(publicKeyPEM);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(encoded);
return (RSAPublicKey) keyFactory.generatePublic(keySpec);
}
读取DER格式私钥
public static PrivateKey readDerPrivateKey(String filename) throws Exception {
// 读取文件
byte[] keyBytes = Files.readAllBytes(Paths.get(filename));
PKCS8EncodedKeySpec pkcs8 = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory kf = KeyFactory.getInstance("RSA");
PrivateKey privateKey = kf.generatePrivate(pkcs8);
// 转换编码
String data = Base64.getEncoder().encodeToString(privateKey.getEncoded());
System.out.println(data);
return privateKey;
}
读取DER格式公钥
public static PrivateKey readDerPrivateKey(String filename) throws Exception {
// 读取文件
byte[] keyBytes = Files.readAllBytes(Paths.get(filename));
PKCS8EncodedKeySpec pkcs8 = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory kf = KeyFactory.getInstance("RSA");
PrivateKey privateKey = kf.generatePrivate(pkcs8);
// 转换编码
String data = Base64.getEncoder().encodeToString(privateKey.getEncoded());
System.out.println(data);
return privateKey;
}
Java读取X509证书
public static X509Certificate loadCert(String filename) throws Exception {
FileInputStream in = new FileInputStream(filename);
CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509Certificate validateCert = (X509Certificate) cf.generateCertificate(in);
return validateCert;
}
使用加载的密钥完成加密和解密
public static void doEncryptAndDecrypt(PublicKey publicKey, PrivateKey privateKey, String msg) throws Exception {
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
String out = Base64.getEncoder().encodeToString(cipher.doFinal(msg.getBytes(StandardCharsets.UTF_8)));
// 解密
cipher.init(Cipher.DECRYPT_MODE, privateKey);
out = new String(cipher.doFinal(Base64.getDecoder().decode(out)));
if (msg.equals(out)) {
System.out.println("加密、解密OK");
}
}
参考资料
- [1] 如何从文件中加载RSA密钥,By jdhurst
- [2] Java 中的 RSA 公钥加密,By Jonm
- [3] Java加密体系(一)java.security包,By noexceptionsir
- [4] Java加密体系,By Oracle
- [5] 关于X509证书和密钥的概念, By 没有感情的杀手