RSA非对称加解密算法在实际开发中广泛使用,简单就目前前后端加解密常用方式小结一下。
RSA是什么:RSA公钥加密算法是1977年由Ron Rivest、Adi Shamirh和LenAdleman在(美国麻省理工学院)开发的。RSA取名来自开发他们三者的名字。RSA是目前最有影响力的公钥加密算法,它能够抵抗到目前为止已知的所有密码攻击,已被ISO推荐为公钥数据加密标准。目前该加密方式广泛用于网上银行、数字签名等场合。RSA算法基于一个十分简单的数论事实:将两个大素数相乘十分容易,但那时想要对其乘积进行因式分解却极其困难,因此可以将乘积公开作为加密密钥。
OpenSSL是什么:众多的密码算法、公钥基础设施标准以及SSL协议,或许这些有趣的功能会让你产生实现所有这些算法和标准的想法。果真如此,在对你表示敬佩的同时,还是忍不住提醒你:这是一个令人望而生畏的过程。这个工作不再是简单的读懂几本密码学专著和协议文档那么简单,而是要理解所有这些算法、标准和协议文档的每一个细节,并用你可能很熟悉的C语言字符一个一个去实现这些定义和过程。我们不知道你将需要多少时间来完成这项有趣而可怕的工作,但肯定不是一年两年的问题。OpenSSL就是由Eric A. Young和Tim J. Hudson两位绝世大好人自1995年就开始编写的集合众多安全算法的算法集合。通过命令或者开发库,我们可以轻松实现标准的公开算法应用。
生成密钥
1、生成ras私钥
$ openssl genrsa -out private_key.pem 1024
输出
Generating RSA private key, 1024 bit long modulus (2 primes)
…+++++
…+++++
e is 65537 (0x010001)
2、生成公钥
$ openssl rsa -in private_key.pem -pubout -out public_key.pem
输出
writing RSA key
3、把私钥转为PKCS#8编码,因为JDK的加密组件JCE中需要读取PKCS#8格式
openssl pkcs8 -topk8 -in private_key.pem -out pkcs8_private_key.pem -nocrypt
生成的证书pem文件内容(以下是私钥的):
-----BEGIN RSA PRIVATE KEY-----
BASE64 ENCODED DATA
-----END RSA PRIVATE KEY-----
Java部分
使用JDK的JCE组件(Java Cryptography Extension)
import javax.crypto.Cipher;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
public class RSAUtils {
public static String encrypt(String publicKeyText, String source) throws Exception {
java.security.spec.X509EncodedKeySpec x509KeySpec = new java.security.spec.X509EncodedKeySpec(java.util.Base64.getDecoder().decode(publicKeyText));
java.security.KeyFactory keyFactory = java.security.KeyFactory.getInstance("RSA");
java.security.PublicKey publicKey = keyFactory.generatePublic(x509KeySpec);
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] b = source.getBytes();
byte[] b1 = cipher.doFinal(b);
return Base64.getEncoder().encodeToString(b1);
}
public static String decrypt(String privateKeyText, String cryptoText) throws Exception {
Base64.Decoder decoder = java.util.Base64.getDecoder();
PKCS8EncodedKeySpec priPKCS8 = new PKCS8EncodedKeySpec(decoder.decode(privateKeyText));
KeyFactory keyf = KeyFactory.getInstance("RSA");
PrivateKey privateKey = keyf.generatePrivate(priPKCS8);
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] b1 = decoder.decode(cryptoText);
byte[] b = cipher.doFinal(b1);
return new String(b);
}
public static void main(String[] args) throws Exception {
String priKeyText =
"MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAM+RE49WU9V/jnmg" +
"z3Snhi2sqnwXyLmpv7gQ/VLQ66FgdJw+ONi//rXY0Y7tyOW7H5vgrhM6BBmixhBc" +
"HoGaJwsOL6G0nIs9B9+sGMJQqupPOvGl51G/Svd5tBFT+pf8eawjhBCt2KXC5/9J" +
"StgvrKriz6UP9Gxf9XCDAmKdHu9BAgMBAAECgYBiR8giK3cAdShJ8Nmp25SN1d7X" +
"aQ+S0YxEpE6+Qsv/GkO8oz2Za7c39VSsP2zLUgHSdd0Wn3bHiKoNnwCWzx3CpKEx" +
"UoQxXcAmVkZOksYsEDN6mb9cdDFHGxtNLsuA0HSE0VkvZwVSAU7O6W5v0mAt25Ei" +
"eHywZqXapS/UYcmSXQJBAPTNrpIc+Ir90ulhzJUDhXB4ip60Mq8yU83DGng7A9Cd" +
"Nwa/U5rLmdXPPzCg/X4iNvw4QDtpz5QiQKVI5PcJK38CQQDZD2cJ7zPGCFVKGvck" +
"oGqAb02/sujIZB1LpWTC+gxni7sUhMeh6lCNGnbrI10brtUeYG7sQiOHhcsFIUkI" +
"3EU/AkBqiDe4DjN7CQqH1a+aCXZAg7SPbAvCgjxGBum1+LioSEJyBUD01DWV/iKZ" +
"nvWxtmIHUNNjB6D17hID270IZE0xAkAkGC2YN1Cys5wLMKS8UHilVtzk7clL2WoN" +
"tDknOxFo/pFjfhj8UcyHSklQHJdVDSOfY4XOf8kMtjDmb3yHnqldAkEAjC3FHa/8" +
"3uuRgH8QMk3hQWSCLcI/b7ccdE1xUU7SJ9PSkMx6zhe0n+pPIHsaPSdzk0Tk03w+" +
"o6yDfIfIH6td4A==";
String pubKeyText = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDPkROPVlPVf455oM90p4YtrKp8" +
"F8i5qb+4EP1S0OuhYHScPjjYv/612NGO7cjlux+b4K4TOgQZosYQXB6BmicLDi+h" +
"tJyLPQffrBjCUKrqTzrxpedRv0r3ebQRU/qX/HmsI4QQrdilwuf/SUrYL6yq4s+l" +
"D/RsX/VwgwJinR7vQQIDAQAB";
String s = encrypt(pubKeyText, "1231");
System.out.println(s);
String a = decrypt(priKeyText, s);
System.out.println(a);
}
}
需要注意的是很多示例中还是使用sun.misc.BASE64Decoder 工具,尽量使用最新的java.util.Base64 ,并且注意去掉头尾的"-----BEGIN ……" 行,中间的Base64编码部分的字符去掉回车换行部分。或者需要改用java.util.Base64#getMimeDecoder 进行解码。
公钥的格式都是X.509格式的,但私钥默认存储的格式为PKCS#1,可以借助Bouncy Castle转化PCS#1格式为PKCS#8格式,从而获取私钥。
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.70</version>
</dependency>
RSAPrivateKeyStructure asn1PrivKey = new RSAPrivateKeyStructure((ASN1Sequence) ASN1Sequence.fromByteArray(priKeyData));
RSAPrivateKeySpec rsaPrivKeySpec = new RSAPrivateKeySpec(asn1PrivKey.getModulus(), asn1PrivKey.getPrivateExponent());
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PrivateKey priKey = keyFactory.generatePrivate(rsaPrivKeySpec);
Javascript部分
目前多数使用的组件是 jsencrypt ,注意的是改组件对私钥的格式PKCS#1和PKCS#8都支持,本示例的私钥是PKCS#1的,与Java部分的示例为同一对公死钥,可以互相加解密。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>test</title>
<script src="https://cdn.bootcdn.net/ajax/libs/jsencrypt/3.2.1/jsencrypt.js"></script>
<script type="text/javascript">
var encrypt = new JSEncrypt();
var pubKey = `
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDPkROPVlPVf455oM90p4YtrKp8
F8i5qb+4EP1S0OuhYHScPjjYv/612NGO7cjlux+b4K4TOgQZosYQXB6BmicLDi+h
tJyLPQffrBjCUKrqTzrxpedRv0r3ebQRU/qX/HmsI4QQrdilwuf/SUrYL6yq4s+l
D/RsX/VwgwJinR7vQQIDAQAB
-----END PUBLIC KEY-----`
var priKey = `
-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQDPkROPVlPVf455oM90p4YtrKp8F8i5qb+4EP1S0OuhYHScPjjY
v/612NGO7cjlux+b4K4TOgQZosYQXB6BmicLDi+htJyLPQffrBjCUKrqTzrxpedR
v0r3ebQRU/qX/HmsI4QQrdilwuf/SUrYL6yq4s+lD/RsX/VwgwJinR7vQQIDAQAB
AoGAYkfIIit3AHUoSfDZqduUjdXe12kPktGMRKROvkLL/xpDvKM9mWu3N/VUrD9s
y1IB0nXdFp92x4iqDZ8Als8dwqShMVKEMV3AJlZGTpLGLBAzepm/XHQxRxsbTS7L
gNB0hNFZL2cFUgFOzulub9JgLduRInh8sGal2qUv1GHJkl0CQQD0za6SHPiK/dLp
YcyVA4VweIqetDKvMlPNwxp4OwPQnTcGv1Oay5nVzz8woP1+Ijb8OEA7ac+UIkCl
SOT3CSt/AkEA2Q9nCe8zxghVShr3JKBqgG9Nv7LoyGQdS6VkwvoMZ4u7FITHoepQ
jRp26yNdG67VHmBu7EIjh4XLBSFJCNxFPwJAaog3uA4zewkKh9Wvmgl2QIO0j2wL
woI8Rgbptfi4qEhCcgVA9NQ1lf4imZ71sbZiB1DTYweg9e4SA9u9CGRNMQJAJBgt
mDdQsrOcCzCkvFB4pVbc5O3JS9lqDbQ5JzsRaP6RY34Y/FHMh0pJUByXVQ0jn2OF
zn/JDLYw5m98h56pXQJBAIwtxR2v/N7rkYB/EDJN4UFkgi3CP2+3HHRNcVFO0ifT
0pDMes4XtJ/qTyB7Gj0nc5NE5NN8PqOsg3yHyB+rXeA=
-----END RSA PRIVATE KEY-----`
encrypt.setPublicKey(pubKey)
s = encrypt.encrypt('1231231312')
console.log( s)
encrypt.setPrivateKey(priKey)
console.log(encrypt.decrypt(s))
</script>
</head>
<body>
</body>
</html>
在前端的工程中可能会使用npm安装
npm install jsencrypt
使用时
import JSEncrypt from 'jsencrypt'
function encrypt(pubKey, srcText) {
let encryptor = new JSEncrypt()
encryptor.setPublicKey(publicKey)
return encryptor.encrypt(srcText)
}
function decrypt(priKey, cryptoText){
let decrypt = new JSEncrypt();
decrypt.setPrivateKey(privateKey);
return decrypt.decrypt(cryptoText);
}
补充知识
.PEM:用ASCLL(BASE64)编码的证书;
? PEM格式通常用于数字证书认证机构(Certificate Authorities,CA),扩展名为**.pem, .crt, .cer, and .key**。内容为Base64编码的ASCII码文件,有类似"-----BEGIN CERTIFICATE-----" 和 "-----END CERTIFICATE-----"的头尾标记。服务器认证证书,中级认证证书和私钥都可以储存为PEM格式(认证证书其实就是公钥)。Apache和类似的服务器使用PEM格式证书。
.DER
DER格式与PEM不同之处在于其使用二进制而不是Base64编码的ASCII。扩展名为**.der**,但也经常使用**.cer**用作扩展名,所有类型的认证证书和私钥都可以存储为DER格式。Java使其典型使用平台。
转换方式
? 可以使用OpenSSL命令行工具在不同证书格式之间的转换
PEM to DER
openssl x509 -outform der -in certificate.pem -out certificate.der
DER to PEM
openssl x509 -inform der -in certificate.cer -out certificate.pem
.CER:存放公钥,没有私钥;
.CER文件就是一个X.509证书,可以直接查看
openssl x509 -in test.cer -text
有些情况下,不支持直接查看.CER文件,需要先将其转换为.PEM文件
openssl x509 -in test.cer -out test.pem
openssl x509 -in test.pem -text
.PFX:存放公钥和私钥
.PFX文件是一个PKCS#12格式的文件,不支持直接 查看,需要先将其转换为.PEM格式的文件
openssl x509 -in test.pfx -out test.pem -nodes
openssl x509 -in test.pem -text
PEM to PFX
openssl pkcs12 -export -out certificate.pfx -inkey privateKey.key -in certificate.crt -certfile CACert.crt
(pem 后缀的证书都是base64编码;der 后缀的证书都是二进制格式;crt .cer 后缀的文件都是证书文件(编码方式不一定,有可能是.pem,也有可能是.der);.pfx 主要用于windows平台,浏览器可以使用,也是包含证书和私钥,获取私钥需要密码才可以)
PFX to PEM
openssl pkcs12 -in certificate.pfx -out certificate.cer -nodes
(PFX转PEM后certificate.cer 文件包含认证证书和私钥,需要把它们分开存储才能使用。)
|