在公钥密码学中(也称为非对称密码学),加密机制依赖于两个密钥:公钥和私钥。公钥用于加密消息,而只有私钥的所有者才能解密消息。实际应用中通常需要对公钥和私钥进行序列化,然后分发密钥实现在不同场景、不同语言环境中使用。本文主要介绍如何生成RSA密钥对并序列化为文件,最后使用密钥对进行跨语言应用。
概念介绍
RSA, 即 Rivest–Shamir–Adleman,是非对称加密算法。不同于对称加密算法(如AES、DES),它需要两个密钥,公钥可共享给任何人,用于加密信息;私钥仅我们自己保存,用于解密数据。
-
X.509 是定义公钥证书格式的标准,用于描述公钥及其他信息。
-
PKCS8 是存储私钥的标准。为了安全起见,可以使用对称加密算法对私钥再次进行加密。该标准不仅可以处理RSA私钥,还可以处理其他算法。PKCS8私钥通常通过PEM编码格式进行交换。
-
DER 是最流行的数据存储编码格式,如X.509证书文件、PKCS8私钥文件。它是二进制编码格式,内容不能通过文本文件直接查看。
-
PEM 是对DER证书内容进行base64编码的机制,PEM也能编码其他类型数据,如公钥、私钥以及请求证书。PEM文件还可能包括一些格式信息,示例如下:
-----BEGIN PUBLIC KEY-----
...Base64 encoding of the DER encoded certificate...
-----END PUBLIC KEY-----
生成RSA密钥对
正式开始加密之前,需要生成RSA密钥对。可以使用java.security
包中的KeyPairGenerator
类实现:
KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
generator.initialize(2048);
KeyPair pair = generator.generateKeyPair();
生成密钥为2048个字节,下面可以生成公钥和私钥:
PrivateKey privateKey = pair.getPrivate();
PublicKey publicKey = pair.getPublic();
现在可以使用公钥加密数据,私钥解密数据。在加密数据之前,我们可能需要传输密钥,下面先看看如何把密钥保存为文件。
保存密钥文件
把密钥对存储在内存中不是好的选项,大多数情况下密钥不会变化,因此可以存储为文件,还可能需要分发给别人。
DER编码文件
通过getEncoded()方法,返回密钥内容的字节数组:
try (FileOutputStream fos = new FileOutputStream("public.key")) {
fos.write(publicKey.getEncoded());
}
从文件中读取key,返回字节数组:
File publicKeyFile = new File("public.key");
byte[] publicKeyBytes = Files.readAllBytes(publicKeyFile.toPath());
然后使用KeyFactory类重新创建密钥实例:
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(publicKeyBytes);
keyFactory.generatePublic(publicKeySpec);
密钥字节内容需要使用EncodedKeySpec进行包装。这里使用X509EncodedKeySpec标准,它是缺省标准。上面实例实现了公钥的保存和读取,也可以使用同样方式实现对私钥的保存和读取。这里需要提醒的是:尽可能保障私钥的安全,我们还可以对私钥进行对称加密,防止非法访问带来安全问题。
PEM编码文件
DER编码文件是二进制格式,不能使用文本文件直接查看。下面介绍存储密钥为PEM编码文件,即使用BASE64进行编码,保存为文本文件。
首先是文件写入:
Base64.Encoder encoder = Base64.getEncoder();
// 密钥对的文件名
String outFile = "pairFileName";
FileOutputStream out = new FileOutputStream(outFile + ".key");
out.write(encoder.encode(privateKey.getEncoded()));
out.close();
System.err.println("Private key format: " + privateKey.getFormat());
out = new FileOutputStream(outFile + ".pub");
out.write(encoder.encode(publicKey.getEncoded()));
out.close();
System.err.println("Public key format: " + publicKey.getFormat());
没有特别的部分,仅使用encoder.encode进行Base64编码。
下面是读取过程:
/* Read all the public key bytes */
Path path = Paths.get(fileName + ".pub");
byte[] bytes = Files.readAllBytes(path);
Base64.Decoder deCoder = Base64.getDecoder();
/* Generate public key. */
X509EncodedKeySpec ks = new X509EncodedKeySpec(deCoder.decode(bytes));
KeyFactory kf = KeyFactory.getInstance("RSA");
PublicKey publicKey = kf.generatePublic(ks);
System.err.println("Public key format: " + publicKey.getFormat());
没有太多要解释的,仅多了一步解码过程,私钥的读取过程类似。
如果PEM文件包括格式信息,则读取前需要先处理:
public RSAPrivateKey readPrivateKey(File file) throws Exception {
String key = new String(Files.readAllBytes(file.toPath()), Charset.defaultCharset());
Base64.Decoder deCoder = Base64.getDecoder();
String privateKeyPEM = key
.replace("-----BEGIN PRIVATE KEY-----", "")
.replaceAll(System.lineSeparator(), "")
.replace("-----END PRIVATE KEY-----", "");
byte[] encoded = deCoder.decode(privateKeyPEM);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded);
return (RSAPrivateKey) keyFactory.generatePrivate(keySpec);
}
RSA加密解密数据
加密过程
现在如何对简单字符串进行加密解密。首先定义明文字符串:
String secretMessage = "These are secret messages";
需要使用Cipher对象并初始化为公钥加密模式:
Cipher encryptCipher = Cipher.getInstance("RSA");
encryptCipher.init(Cipher.ENCRYPT_MODE, publicKey);
准备好cipher对象,使用doFinal方法加密消息,它的参数为字节数组,因此调用前需要对字符串进行转换:
byte[] secretMessageBytes = secretMessage.getBytes(StandardCharsets.UTF_8);)
byte[] encryptedMessageBytes = encryptCipher.doFinal(secretMessageBytes);
啊哈,终于成功加密了消息。如果需要在数据库中存储消息或通过REST API发送消息,仍然需要使用BASE64进行编码:
String encodedMessage = Base64.getEncoder().encodeToString(encryptedMessageBytes);
Base64.getEncoder()是apache commons提供的工具类。编码后消息更易使用、方便读取。
解密过程
现在解释解密密文为明文。仍然需要cipher实例,这次初始化为解密模式,同时需要传入私钥:
Cipher decryptCipher = Cipher.getInstance("RSA");
decryptCipher.init(Cipher.DECRYPT_MODE, privateKey);
和前面一样执行DoFinal方法:
byte[] decryptedMessageBytes = decryptCipher.doFinal(encryptedMessageBytes);
String decryptedMessage = new String(decryptedMessageBytes, StandardCharsets.UTF_8);
需要说明的是,如果是Base64格式,需要先解码。
跨语言使用
我们使用Golang生成rsa密钥对,然后通过java解密或验证。
golang生成密钥对
func main() {
// Create the keys pair
priv, pub := GenerateRsaKeyPair()
// Export the keys to pem string
priv_pem := ExportRsaPrivateKeyAsPemStr(priv)
pub_pem, _ := ExportRsaPublicKeyAsPemStr(pub)
writeFile(priv_pem, "pri.key")
writeFile(pub_pem, "pub.key")
// 使用公钥加密文本
var reqCode string = "These are secret messages"
reqCodes, _ := rsa.EncryptPKCS1v15(rand.Reader, pub, []byte(reqCode))
reqString := base64.StdEncoding.EncodeToString(reqCodes)
writeFile(reqString, "reqStr.txt")
}
func writeFile(content string, fileName string) {
file, _ := os.Create(fileName)
defer file.Close()
_, _ = file.WriteString(content)
}
func GenerateRsaKeyPair() (*rsa.PrivateKey, *rsa.PublicKey) {
privkey, _ := rsa.GenerateKey(rand.Reader, 2048)
return privkey, &privkey.PublicKey
}
func ExportRsaPublicKeyAsPemStr(pubkey *rsa.PublicKey) (string, error) {
pubkey_bytes, err := x509.MarshalPKIXPublicKey(pubkey)
if err != nil {
return "", err
}
pubkey_pem := pem.EncodeToMemory(
&pem.Block{
Type: "RSA PUBLIC KEY",
Bytes: pubkey_bytes,
},
)
return string(pubkey_pem), nil
}
func ExportRsaPrivateKeyAsPemStr(privkey *rsa.PrivateKey) string {
privkey_bytes, _ := x509.MarshalPKCS8PrivateKey(privkey)
privkey_pem := pem.EncodeToMemory(
&pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: privkey_bytes,
},
)
return string(privkey_pem)
}
java用私钥解密
这里使用java8 进行解密:
public static void main(String[] args) throws Exception {
RsaTools tools = new RsaTools();
// 读取待解密文件内容
byte[] deString = Files.readAllBytes(Paths.get("reqStr.txt"));
String result = tools.decrypt(new String(deString, StandardCharsets.UTF_8));
System.out.println(result);
}
// 从文件加载私钥
public PrivateKey readPrivateKey(File file) throws Exception {
KeyFactory factory = KeyFactory.getInstance("RSA");
try (FileReader keyReader = new FileReader(file);
PemReader pemReader = new PemReader(keyReader)) {
PemObject pemObject = pemReader.readPemObject();
byte[] content = pemObject.getContent();
PKCS8EncodedKeySpec privKeySpec = new PKCS8EncodedKeySpec(content);
return factory.generatePrivate(privKeySpec);
}
}
// 利用私钥解密
public String decrypt(String deStr) throws Exception {
Cipher decryptCipher = Cipher.getInstance("RSA");
PrivateKey privateKey = readPrivateKey(new File("pri.key"));
decryptCipher.init(Cipher.DECRYPT_MODE, privateKey);
// 密文需要使用Base64解码
byte[] decryptedMessageBytes = decryptCipher.doFinal(Base64.getDecoder().decode(deStr));
String decryptedMessage = new String(decryptedMessageBytes, StandardCharsets.UTF_8);
return decryptedMessage;
}
总结
本文介绍了如何生成RSA密钥对,并利用相关标准生成密钥文件,最后通过实例展示如何实现跨语言应用。