RSA
RSA加密算法是最常用的非对称加密算法。非对称加密算法的特点就是加密秘钥和解密秘钥不同,秘钥分为公钥和私钥,用私钥加密的明文,只能用公钥解密;用公钥加密的明文,只能用私钥解密。RSA是第一个比较完善的公开密钥算法,它既能用于加密,也能用于数字签名。RSA的安全基于大数分解的难度。其公钥和私钥是一对大素数(100到200位十进制数或更大)的函数。从一个公钥和密文恢复出明文的难度,等价于分解两个大素数之积(这是公认的数学难题)。
注意点
-
RSA加密算法对于加密数据的长度是有要求的。一般来说,明文长度小于等于密钥长度(Bytes)-11。解决这个问题需要对较长的明文进行分段加解密,这个上面的代码已经实现了。
-
一旦涉及到双方开发,语言又不相同,不能够采用同一个工具的时候,切记要约定以下内容。
- 约定双方的BASE64编码
- 约定双方分段加解密的方式。我踩的坑也主要是这里,不仅仅是约定大家分段的大小,更重要的是分段加密后的拼装方式。doFinal方法加密完成后得到的仍然是byte[],因为最终呈现的是编码后的字符串,所以你可以分段加密,分段编码和分段加密,一次编码两种方式(上面的代码采用的是后一种,也推荐采用这一种)。相信我不是所有人的脑回路都一样的,尤其是当他采用的开发语言和你不通时。
-
RSA加解密中必须考虑到的密钥长度、明文长度和密文长度问题。明文长度需要小于密钥长度,而密文长度则等于密钥长度。因此当加密内容长度大于密钥长度时,有效的RSA加解密就需要对内容进行分段。
这是因为,RSA算法本身要求加密内容也就是明文长度m必须0<m<密钥长度n。如果小于这个长度就需要进行padding,因为如果没有padding,就无法确定解密后内容的真实长度,字符串之类的内容问题还不大,以0作为结束符,但对二进制数据就很难,因为不确定后面的0是内容还是内容结束符。而只要用到padding,那么就要占用实际的明文长度,于是实际明文长度需要减去padding字节长度。我们一般使用的padding标准有NoPPadding、OAEPPadding、PKCS1Padding等,其中PKCS#1建议的padding就占用了11个字节。
这样,对于1024长度的密钥。128字节(1024bits)-减去11字节正好是117字节,但对于RSA加密来讲,padding也是参与加密的,所以,依然按照1024bits去理解,但实际的明文只有117字节了。
所以如果要对任意长度的数据进行加密,就需要将数据分段后进行逐一加密,并将结果进行拼接。同样,解码也需要分段解码,并将结果进行拼接。
若生成1024bit的密钥,则RSA最大解密密文大小为128字节,RSA最大加密明文大小为117字节
后端java加密解密
# RSAUtil.java
import org.bouncycastle.util.encoders.Base64;
import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;
public class RSAUtil {
public static final String CHARSET = "UTF-8";
public static final String RSA_ALGORITHM = "RSA";
public static Map<String, String> createKeys(int keySize) {
//为RSA算法创建一个KeyPairGenerator对象
KeyPairGenerator kpg;
try {
kpg = KeyPairGenerator.getInstance(RSA_ALGORITHM);
} catch (NoSuchAlgorithmException e) {
throw new IllegalArgumentException("No such algorithm-->[" + RSA_ALGORITHM + "]");
}
//初始化KeyPairGenerator对象,密钥长度
kpg.initialize(keySize);
//生成密匙对
KeyPair keyPair = kpg.generateKeyPair();
//得到公钥
Key publicKey = keyPair.getPublic();
String publicKeyStr = Base64.toBase64String(publicKey.getEncoded());
//得到私钥
Key privateKey = keyPair.getPrivate();
String privateKeyStr = Base64.toBase64String(privateKey.getEncoded());
Map<String, String> keyPairMap = new HashMap<String, String>();
keyPairMap.put("publicKey", publicKeyStr);
keyPairMap.put("privateKey", privateKeyStr);
return keyPairMap;
}
/**
* 得到公钥
*
* @param publicKey 密钥字符串(经过base64编码)
* @throws Exception
*/
public static RSAPublicKey getPublicKey(String publicKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
//通过X509编码的Key指令获得公钥对象
KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(Base64.decode(publicKey));
RSAPublicKey key = (RSAPublicKey) keyFactory.generatePublic(x509KeySpec);
return key;
}
/**
* 得到私钥
*
* @param privateKey 密钥字符串(经过base64编码)
* @throws Exception
*/
public static RSAPrivateKey getPrivateKey(String privateKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
//通过PKCS#8编码的Key指令获得私钥对象
KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(Base64.decode(privateKey));
RSAPrivateKey key = (RSAPrivateKey) keyFactory.generatePrivate(pkcs8KeySpec);
return key;
}
/**
* 公钥加密
*
* @param data
* @param publicKey
* @return
*/
public static String publicEncrypt(String data, RSAPublicKey publicKey) {
try {
Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
return Base64.toBase64String(rsaSplitCodec(cipher, Cipher.ENCRYPT_MODE, data.getBytes(CHARSET), publicKey.getModulus().bitLength()));
} catch (Exception e) {
throw new RuntimeException("加密字符串[" + data + "]时遇到异常", e);
}
}
/**
* 私钥解密
*
* @param data
* @param privateKey
* @return
*/
public static String privateDecrypt(String data, RSAPrivateKey privateKey) {
try {
Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, privateKey);
return new String(rsaSplitCodec(cipher, Cipher.DECRYPT_MODE, Base64.decode(data), privateKey.getModulus().bitLength()), CHARSET);
} catch (Exception e) {
throw new RuntimeException("解密字符串[" + data + "]时遇到异常", e);
}
}
/**
* 私钥加密
*
* @param data
* @param privateKey
* @return
*/
public static String privateEncrypt(String data, RSAPrivateKey privateKey) {
try {
Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, privateKey);
return Base64.toBase64String(rsaSplitCodec(cipher, Cipher.ENCRYPT_MODE, data.getBytes(CHARSET), privateKey.getModulus().bitLength()));
} catch (Exception e) {
throw new RuntimeException("加密字符串[" + data + "]时遇到异常", e);
}
}
/**
* 公钥解密
*
* @param data
* @param publicKey
* @return
*/
public static String publicDecrypt(String data, RSAPublicKey publicKey) {
try {
Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, publicKey);
return new String(rsaSplitCodec(cipher, Cipher.DECRYPT_MODE, Base64.decode(data), publicKey.getModulus().bitLength()), CHARSET);
} catch (Exception e) {
throw new RuntimeException("解密字符串[" + data + "]时遇到异常", e);
}
}
private static byte[] rsaSplitCodec(Cipher cipher, int opmode, byte[] datas, int keySize) {
int maxBlock = 0;
if (opmode == Cipher.DECRYPT_MODE) {
maxBlock = keySize / 8;
} else {
maxBlock = keySize / 8 - 11;
}
ByteArrayOutputStream out = new ByteArrayOutputStream();
int offSet = 0;
byte[] buff;
byte[] resultDatas = null;
int i = 0;
try {
while (datas.length > offSet) {
if (datas.length - offSet > maxBlock) {
buff = cipher.doFinal(datas, offSet, maxBlock);
} else {
buff = cipher.doFinal(datas, offSet, datas.length - offSet);
}
out.write(buff, 0, buff.length);
i++;
offSet = i * maxBlock;
}
resultDatas = out.toByteArray();
} catch (Exception e) {
throw new RuntimeException("加解密阀值为[" + maxBlock + "]的数据时发生异常", e);
} finally {
try {
if (out != null) out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return resultDatas;
}
}
# main-test
public static void main(String[] args) throws Exception {
Map<String, String> keyMap = RSAUtil.createKeys(1024);
String publicKey = keyMap.get("publicKey");
String privateKey = keyMap.get("privateKey");
System.out.println("公钥: \n\r" + publicKey);
System.out.println("私钥: \n\r" + privateKey);
String data = "skyyemperor";
System.out.println("\r明文:\r\n" + data);
System.out.println("\r明文大小:\r\n" + data.getBytes().length);
String encodedData = RSAUtil.publicEncrypt(data, RSAUtil.getPublicKey(publicKey));
System.out.println("密文:\r\n" + encodedData);
String decodedData = RSAUtil.privateDecrypt(encodedData, RSAUtil.getPrivateKey(privateKey));
System.out.println("解密后文字: \r\n" + decodedData);
}
# 输出
公钥:
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCjY04JQYjOG0Ka6GRaykb66WYz9vOBPPyshIukIWr0H9TIW/3K4Snmk2Y6ACiKvk5IpvxVW277dyJojnp1+qBE/cC4np/FZ1rKkIAYbFa8eIk+DDF2u4IRVfFi8IzJI8wT2R2TqIgsyYpOERpggONxvY+R1JYGXtG/iwPNEE7zFwIDAQAB
私钥:
MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAKNjTglBiM4bQproZFrKRvrpZjP284E8/KyEi6QhavQf1Mhb/crhKeaTZjoAKIq+Tkim/FVbbvt3ImiOenX6oET9wLien8VnWsqQgBhsVrx4iT4MMXa7ghFV8WLwjMkjzBPZHZOoiCzJik4RGmCA43G9j5HUlgZe0b+LA80QTvMXAgMBAAECgYAPAr+XoBq2fNCotLMLmEvfH3RRT93dQMIiA2dk4+EUnpwI8FKnwfn8ggxBReRzorpEeHr0SJswpShXHMfpcOYqatLqhW9pBqBqw2mX51s0XEU/1l0eliStyjunSK9vWJu5/SHIkl6RtDG0+RfmgxYza3qgrISCkUEx46ujE3eOKQJBANma8zg+FHzmlh8i1tcWE9d8rMYizQKXTkIiUQoQc2jJvGGTjSV4/SmcuIZrMIs9unVsX5wVKSY3Hs1tmkSLH9UCQQDAN2a9gOwOfX/rVTT1FU1zW+a1XvBVVDWKCDwFaHpLROY0lU4l5Y7VDtN0ywLDfQweVyWAb45GhHhP02Rzz6k7AkEAve78ikPNeDOAJw8uvLAdg4HkAFFR5ggRG7J+T62KPpWPIxA6K9IODusnONSIm5C2llWQoljqANwtu5sGAvv4PQJAZsC3AXLbvdtE+K6yApoCrzpfmHFKFLNRc5p3S4TQa1BHp7Bw+D1BH5AvZe3oakx8n/OCLqhz7CqAjlu5BVfrZQJAZM+JMTBaRH03mgjQ0/HWFC8gL1J1CN9CjOIHrBwMxLaZEmbuxlfSH0kfYXtAxoGHVJSRNdpJAbAmcg9MUCHEFg==
明文:
skyyemperor
明文大小:
11
密文:
T2gUe8GOWNsnWJ2f2AFxXRbPJ6REwnf6GZ/4Ulf4aTdFCvfQv5wKHV3EUWtBwoqw5CvFadBN3eDPbxGGH4qi3L1MeDyYzhRTlmOYk6kP1mRTOGozh3oH0cbb5Lju6m4qK4Pj4blCXyNY2fAVuraCpAnpU/RtNZdG3Ys4ILP7nQo=
解密后文字:
skyyemperor
前端JS加密
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>RSA_ENCRYPT</title>
<script src="https://passport.cnblogs.com/scripts/jsencrypt.min.js"></script>
<script type="text/javascript">
function encrypt(content) {
var publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCW7U3OQ7mwYJFj05slM5/NVT0ufxyYajSUGhFeAQbV2FfDHUeA02bWpj0guuUpEkJcy6rrLi31ZLa5fX2YvBvShCiOo87bKANkOCxE7O4cpRmJl3lzSoGZHtt+FaxnLhi3VbJv4RmZQyo7tv1vN6kxy0Y7FwKL0Qjhl889+X6cTQIDAQAB";
var encrypt = new JSEncrypt();
encrypt.setPublicKey(publicKey);
var data = encrypt.encrypt(content);
console.log(data);
return data;
}
</script>
</head>
<body>
<button onclick="encrypt('abcd1234')">加密</button>
</body>
</html>
AES
后端java加密解密
import java.nio.charset.StandardCharsets;
import java.util.UUID;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
/**
* AES工具类,密钥必须是16位字符串
*/
public class AESUtil {
/**
* 偏移量,必须是16位字符串
*/
private static final String IV_STRING = "pian-yi-lianghhh";
/**
* 默认的密钥(必须为16位)
*/
private static final String DEFAULT_KEY = "251c2d5sd2f23sd7";
/**
* 产生随机密钥(这里产生密钥必须是16位)
*/
public static String generateKey() {
String key = UUID.randomUUID().toString();
key = key.replace("-", "").substring(0, 16);// 替换掉-号
return key;
}
public static String encryptData(String key, String content) {
byte[] encryptedBytes = new byte[0];
try {
byte[] byteContent = content.getBytes("UTF-8");
// 注意,为了能与 iOS 统一
// 这里的 key 不可以使用 KeyGenerator、SecureRandom、SecretKey 生成
byte[] enCodeFormat = key.getBytes();
SecretKeySpec secretKeySpec = new SecretKeySpec(enCodeFormat, "AES");
byte[] initParam = IV_STRING.getBytes();
IvParameterSpec ivParameterSpec = new IvParameterSpec(initParam);
// 指定加密的算法、工作模式和填充方式
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
encryptedBytes = cipher.doFinal(byteContent);
// 同样对加密后数据进行 base64 编码
return Base64Util.encodeByteToString(encryptedBytes);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static String decryptData(String key, String content) {
try {
// base64 解码
byte[] encryptedBytes = Base64Util.decodeStringToByte(content);
byte[] enCodeFormat = key.getBytes();
SecretKeySpec secretKey = new SecretKeySpec(enCodeFormat, "AES");
byte[] initParam = IV_STRING.getBytes();
IvParameterSpec ivParameterSpec = new IvParameterSpec(initParam);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secretKey, ivParameterSpec);
byte[] result = cipher.doFinal(encryptedBytes);
return new String(result, StandardCharsets.UTF_8);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static void main(String[] args) {
String key = "F431E6FF9051DA07";
String content = "skyyemperor";
String encryptData = AESUtil.encryptData(key, content);
System.out.println("aes加密后: " + encryptData);
String decryptData = AESUtil.decryptData(key, encryptData);
System.out.println("aes解密后: " + decryptData);
}
}
前端加密解密
var iv = CryptoJS.enc.Utf8.parse("pian-yi-lianghhh"); //加密向量
function AESEnc(key,content){
var key = CryptoJS.enc.Utf8.parse(key); //加密密钥
var srcs = CryptoJS.enc.Utf8.parse(content);
var encrypted = CryptoJS.AES.encrypt(srcs, key, { iv: iv,mode:CryptoJS.mode.CBC});
return encrypted.toString();
}
function AESDec(key,content){
var key = CryptoJS.enc.Utf8.parse(key); //加密密钥
var decrypted = CryptoJS.AES.decrypt(content, key, { iv: iv,mode:CryptoJS.mode.CBC});
return decrypted.toString(CryptoJS.enc.Utf8);
}
function getKey(){
return uuid(16,16);
}
function uuid(len, radix) {
var chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('');
var uuid = [], i;
radix = radix || chars.length;
if (len) {
// Compact form
for (i = 0; i < len; i++) uuid[i] = chars[0 | Math.random()*radix];
} else {
// rfc4122, version 4 form
var r;
// rfc4122 requires these characters
uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-';
uuid[14] = '4';
// Fill in random data. At i==19 set the high bits of clock sequence as
// per rfc4122, sec. 4.1.5
for (i = 0; i < 36; i++) {
if (!uuid[i]) {
r = 0 | Math.random()*16;
uuid[i] = chars[(i === 19) ? (r & 0x3) | 0x8 : r];
}
}
}
return uuid.join('');
}
加密算法应用场景
为什么要结合使用这两种算法
如果不清楚非对称算法和对称算法,也许你会问,为什么要结合使用这两种算法,单纯使用一种算法不行吗?这就要结合不同的场景和需求了。
客户端传输重要信息给服务端,服务端返回的信息不需加密的情况
客户端传输重要信息给服务端,服务端返回的信息不需加密,例如绑定银行卡的时候,需要传递用户的银行卡号,手机号等重要信息,客户端这边就需要对这些重要信息进行加密,使用RSA公钥加密,服务端使用RSA解密,然后返回一些普通信息,比如状态码code,提示信息msg,提示操作是成功还是失败。这种场景下,仅仅使用RSA加密是可以的。
客户端传输重要信息给服务端,服务端返回的信息需加密的情况
客户端传输重要信息给服务端,服务端返回的信息需加密,例如客户端登录的时候,传递用户名和密码等资料,需要进行加密,服务端验证登录信息后,返回令牌token需要进行加密,客户端解密后保存。此时就需要结合这两种算法了。至于整个流程是怎样的,在下面会慢慢通过例子向你介绍,因为如果一开始就这么多文字类的操作,可能会让读者感到一头雾水。
RSA算法+AES算法的使用
举一个简单的例子来说明一下吧,例如实名认证功能,需要传递用户真实姓名和身份证号,对于这种重要信息,需要进行加密处理。
客户端使用RSA + AES对重要信息进行加密
客户端加密过程主要分为以下三个步骤:
1.客户端随机产生AES的密钥;
2.对身份证信息(重要信息)进行AES加密;
3.通过使用RSA对AES密钥进行公钥加密。
这样在传输的过程中,即时加密后的AES密钥被别人截取,对其也无济于事,因为他并不知道RSA的私钥,无法解密得到原本的AES密钥,就无法解密用AES加密后的重要信息。
服务端使用RSA + AES对重要信息进行解密
服务端解密过程主要分为以下两个步骤:
1.对加密后的AES密钥进行RSA私钥解密,拿到密钥原文;
2.对加密后的重要信息进行AES解密,拿到原始内容。
现实开发中,服务端有时也需要向客户端传递重要信息,比如登录的时候,返回token给客户端,作为令牌,这个令牌就需要进行加密,原理也是差不多的,比上面多一个步骤而已,就是将解密后的AES密钥,对将要传递给客户端的数据token进行AES加密,返回给客户端,由于客户端和服务端都已经拿到同一把AES钥匙,所以客户端可以解密服务端返回的加密后的数据。如果客户端想要将令牌进行保存,则需要使用自己定义的默认的AES密钥进行加密后保存,需要使用的时候传入默认密钥和密文,解密后得到原token。
上面提及到客户端加密,服务端返回数据不加密的情况,上面说到仅仅使用RSA是可以,但是还是建议同时使用这两种算法,即产生一个AES密钥,使用RSA对该密钥进行公钥加密,对重要信息进行AES加密,服务端通过RSA私钥解密拿到AES密钥,再对加密后的重要信息进行解密。如果仅仅使用RSA,服务端只通过RSA解密,这样会对于性能会有所影响,原因是RSA的解密耗时约等于AES解密数据的100倍,所以如果每个重要信息都只通过RSA加密和解密,则会影响服务端系统的性能,所以建议两种算法一起使用。
Comments | 0 条评论