在项目中,为了保证数据的安全,我们常常会对传递的数据进行加密。常用的加密算法有对称加密(AES)和非对称加密(RSA)。
AES AES加密算法 对称加密就是指,加密和解密使用同一个密钥的加密方式;加密计算量小、速度块,适合对大量数据进行加密的场景。AES取代DES成为加密标准(微信小程序加密传输就是用这个加密算法的)
。
AES加密的五个关键词
AES采用分组密码体制,即AES加密会首先把明文切成一段一段的,而且每段数据的长度要求必须是128位16个字节,如果最后一段不够16个字节了,就需要用Padding来把这段数据填满16个字节,然后分别对每段数据进行加密,最后再把每段加密数据拼起来形成最终的密文。
Padding就是用来把不满16个字节的分组数据填满16个字节用的,它有三种模式PKCS5、PKCS7和NOPADDING。解密端需要使用和加密端同样的Padding模式,才能准确的识别有效数据和填充数据。开发通常采用PKCS7 Padding模式。
初始向量IV的作用是使加密更加安全可靠,我们使用AES加密时需要主动提供初始向量,而且只需要提供一个初始向量就够了,后面每段数据的加密向量都是前面一段的密文。初始向量IV的长度规定为128位16个字节,初始向量的来源为随机生成。
开发通常采用128位16个字节的密钥,我们使用AES加密时需要主动提供密钥,而且只需要提供一个密钥就够了,每段数据加密使用的都是这一个密钥,密钥来源为随机生成。
AES一共有四种加密模式,分别是ECB(电子密码本模式)、CBC(密码分组链接模式)、CFB、OFB,一般使用的是ECB、CBC模式
注意事项
服务端和我们客户端必须使用一样的密钥和初始向量IV。
服务端和我们客户端必须使用一样的加密模式。
服务端和我们客户端必须使用一样的Padding模式。以上三条有一个不满足,双方就无法完成互相加解密。
RSA RSA加密算法 RSA加密是一种非对称加密。可以在不直接传递密钥的情况下,完成解密。这能够确保信息的安全性,避免了直接传递密钥所造成的被破解的风险。 RSA是由一对密钥来进行加解密的过程,分别称为公钥和私钥。两者之间有数学相关,该加密算法的原理就是对一极大整数做因数分解的困难性来保证安全性。通常个人保存私钥,公钥是公开的(可能同时多人持有)。
RSA签名 加密和签名都是为了安全性考虑,但略有不同。常有人问加密和签名是用私钥还是公钥?其实都是对加密和签名的作用有所混淆。简单的说,加密是为了防止信息被泄露,而签名是为了防止信息被篡改
。这里举2个例子说明。
第一个场景 B(客户端)
要给A(服务器)
传递一条重要消息。
RSA的加密过程如下:
A生成一对密钥(公钥和私钥),私钥不公开,A自己保留。公钥为公开的,任何人可以获取。
A传递自己的公钥给B,B用A的公钥对消息进行加密。
A接收到B加密的消息,利用A自己的私钥对消息进行解密。
在整个过程中,只有2次传递过程,第一次是A传递公钥给B,第二次是B用公钥加密消息传递给A,即使都被敌方截获,也没有危险性,因为只有A的私钥才能对消息进行解密
,防止了消息内容的泄露。
第二个场景 A(服务器)
收到B(客户端)
发的消息后,需要进行回复“收到”
。
RSA签名的过程如下:
A生成一对密钥(公钥和私钥),私钥不公开,A自己保留。公钥为公开的,任何人可以获取。
A用自己的私钥对消息加签,形成签名,并将加签的消息和消息(加密后的信息)
本身一起传递给B。
B收到消息后,在获取A的公钥进行验签,如果验签出来的内容与消息本身一致,证明消息是A回复的。
在整个过程中,只有2次传递过程,第一次是A传递加签的消息和消息(加密后的信息)
本身给B,第二次是B获取A的公钥,即使都被敌方截获,也没有危险性,因为只有A的私钥才能对消息进行签名;即使知道了消息内容(加密后的信息)
,也无法伪造带签名的回复给B,防止了消息内容的篡改。
存在的缺陷 综合两个场景你会发现:
第一个场景虽然被截获的消息没有泄露,但是黑客可以利用截获的公钥,将假信息进行加密,然后传递给A。
第二个场景虽然截获的消息不能被篡改,但是消息的内容可以利用公钥解密来获得,并不能防止泄露。
所以在实际应用中,要根据情况灵活使用,可以同时使用加密和签名,也可以A和B都有一套自己的公钥和私钥(这里的A、B指服务端,前端的话不安全
),当A要给B发送消息时,先用B的公钥对消息加密,再对加密的消息使用A的私钥来加签名,达到既不泄露也不被篡改,更能保证消息的安全性。
项目详情 依赖文件 <?xml version="1.0" encoding="UTF-8" ?> <project xmlns ="http://maven.apache.org/POM/4.0.0" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" > <parent > <artifactId > spring-boot-project</artifactId > <groupId > cn.goitman</groupId > <version > 0.0.1-SNAPSHOT</version > </parent > <modelVersion > 4.0.0</modelVersion > <artifactId > rsa-encrypt-demo</artifactId > <dependencies > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency > <dependency > <groupId > cn.shuibo</groupId > <artifactId > rsa-encrypt-body-spring-boot</artifactId > <version > 1.0.1.RELEASE</version > </dependency > <dependency > <groupId > org.projectlombok</groupId > <artifactId > lombok</artifactId > <optional > true</optional > </dependency > </dependencies > </project >
配置文件 server: port: 8080 rsa: encrypt: open: true showLog: true publicKey: privateKey:
RSA公私钥生成工具在后续工具类中
引导类 package cn.goitman;import cn.shuibo.annotation.EnableSecurity;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication @EnableSecurity public class RsaEncryptApplication { public static void main (String[] args) { SpringApplication.run(RsaEncryptApplication.class, args); } }
实体类 package cn.goitman.pojo;import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor;@Data @NoArgsConstructor @AllArgsConstructor public class Consumer { private String name; private String passWord; }
工具类 package cn.goitman.util;import org.apache.commons.codec.binary.Base64;import sun.misc.BASE64Decoder;import sun.misc.BASE64Encoder;import javax.crypto.Cipher;import javax.crypto.spec.IvParameterSpec;import javax.crypto.spec.SecretKeySpec;import java.io.ByteArrayOutputStream;import java.security.*;import java.security.interfaces.RSAPrivateKey;import java.security.interfaces.RSAPublicKey;import java.security.spec.PKCS8EncodedKeySpec;import java.security.spec.X509EncodedKeySpec;import java.util.HashMap;import java.util.Map;public class CreateRsaSecrteKeyUtil { private static final String PUBLIC_KEY = "RSAPublicKey" ; private static final String PRIVATE_KEY = "RSAPrivateKey" ; public static final String RSA_ALGORITHM = "RSA" ; public static final String RSA_PADDING_ALGORITHM = "RSA/ECB/PKCS1Padding" ; public static final String SIGNATURE_ALGORITHM = "MD5withRSA" ; private static final int MAX_ENCRYPT_BLOCK = 117 ; private static final int MAX_DECRYPT_BLOCK = 128 ; public static final String UTF_8 = "UTF-8" ; public static final String AES_ALGORITHM = "AES" ; public static final String AES_PADDING_ALGORITHM = "AES/CBC/PKCS5Padding" ; private final static String iv = "初始向量" ; public static Map<String, Object> initKey () throws Exception { KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(RSA_ALGORITHM); keyPairGen.initialize(1024 ); KeyPair keyPair = keyPairGen.generateKeyPair(); RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate(); Map<String, Object> keyMap = new HashMap <String, Object>(2 ); keyMap.put(PUBLIC_KEY, publicKey); keyMap.put(PRIVATE_KEY, privateKey); return keyMap; } public static String getPublicKey (Map<String, Object> keyMap) throws Exception { Key key = (Key) keyMap.get(PUBLIC_KEY); return encryptBASE64(key.getEncoded()); } public static String getPrivateKey (Map<String, Object> keyMap) throws Exception { Key key = (Key) keyMap.get(PRIVATE_KEY); return encryptBASE64(key.getEncoded()); } public static byte [] decryptBASE64(String key) throws Exception { return (new BASE64Decoder ()).decodeBuffer(key); } public static String encryptBASE64 (byte [] key) throws Exception { return (new BASE64Encoder ()).encodeBuffer(key); } public static String encryptRSADate (String data, String publicKey) { try { byte [] dataByte = data.getBytes(); byte [] keyBytes = decryptBASE64(publicKey); X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec (keyBytes); KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM); Key publicK = keyFactory.generatePublic(x509KeySpec); Cipher cipher = Cipher.getInstance(RSA_PADDING_ALGORITHM); cipher.init(Cipher.ENCRYPT_MODE, publicK); int inputLen = dataByte.length; ByteArrayOutputStream out = new ByteArrayOutputStream (); int offSet = 0 ; byte [] cache; int i = 0 ; while (inputLen - offSet > 0 ) { if (inputLen - offSet > MAX_ENCRYPT_BLOCK) { cache = cipher.doFinal(dataByte, offSet, MAX_ENCRYPT_BLOCK); } else { cache = cipher.doFinal(dataByte, offSet, inputLen - offSet); } out.write(cache, 0 , cache.length); i++; offSet = i * MAX_ENCRYPT_BLOCK; } byte [] encryptedData = out.toByteArray(); out.close(); return encryptBASE64(encryptedData); } catch (Exception e) { return e.getMessage(); } } public static String decryptRSADate (String data, String privateKey) { try { byte [] dataByte = decryptBASE64(data); byte [] keyBytes = decryptBASE64(privateKey); PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec (keyBytes); KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM); Key privateK = keyFactory.generatePrivate(pkcs8KeySpec); Cipher cipher = Cipher.getInstance(RSA_PADDING_ALGORITHM); cipher.init(Cipher.DECRYPT_MODE, privateK); int inputLen = dataByte.length; ByteArrayOutputStream out = new ByteArrayOutputStream (); int offSet = 0 ; byte [] cache; int i = 0 ; while (inputLen - offSet > 0 ) { if (inputLen - offSet > MAX_DECRYPT_BLOCK) { cache = cipher .doFinal(dataByte, offSet, MAX_DECRYPT_BLOCK); } else { cache = cipher .doFinal(dataByte, offSet, inputLen - offSet); } out.write(cache, 0 , cache.length); i++; offSet = i * MAX_DECRYPT_BLOCK; } byte [] decryptedData = out.toByteArray(); out.close(); return new String (decryptedData); } catch (Exception e) { return e.getMessage(); } } public static String sign (String data, String privateKey) { try { byte [] keyBytes = decryptBASE64(privateKey); PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec (keyBytes); KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM); PrivateKey privateK = keyFactory.generatePrivate(keySpec); Signature sig = Signature.getInstance(SIGNATURE_ALGORITHM); sig.initSign(privateK); sig.update(data.getBytes(UTF_8)); return encryptBASE64(sig.sign()); } catch (Exception e) { return e.getMessage(); } } public static boolean verify (String data, String sign, String publicKey) { try { byte [] keyBytes = decryptBASE64(publicKey); X509EncodedKeySpec keySpec = new X509EncodedKeySpec (keyBytes); KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM); PublicKey publicK = keyFactory.generatePublic(keySpec); Signature sig = Signature.getInstance(SIGNATURE_ALGORITHM); sig.initVerify(publicK); sig.update(data.getBytes(UTF_8)); return sig.verify(decryptBASE64(sign)); } catch (Exception e) { e.printStackTrace(); } return false ; } public static String encryptAES (String context, String key) { try { byte [] decode = context.getBytes(UTF_8); Key secretKeySpec = new SecretKeySpec (key.getBytes(UTF_8), AES_ALGORITHM); Cipher cipher = Cipher.getInstance(AES_PADDING_ALGORITHM); IvParameterSpec ivp = new IvParameterSpec (iv.getBytes()); cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivp); return Base64.encodeBase64String(cipher.doFinal(decode)); } catch (Exception e) { return e.getMessage(); } } public static String decryptAES (String context, String key) { try { byte [] decode = Base64.decodeBase64(context); Key secretKeySpec = new SecretKeySpec (key.getBytes(UTF_8), AES_ALGORITHM); Cipher cipher = Cipher.getInstance(AES_PADDING_ALGORITHM); IvParameterSpec ivp = new IvParameterSpec (iv.getBytes()); cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivp); return new String (cipher.doFinal(decode), UTF_8); } catch (Exception e) { return e.getMessage(); } } public static String randomKey (int lenght) { String str = "" ; char [] c = null ; for (short i = '0' ; i <= '9' ; i++) { str += (char ) i; } for (short i = 'A' ; i <= 'Z' ; i++) { str += (char ) i; } for (short i = 'a' ; i <= 'z' ; i++) { str += (char ) i; } c = new char [lenght]; for (int i = 0 ; i < c.length; i++) { int index = (int ) (Math.random() * str.length()); c[i] = str.charAt(index); } System.out.println("密钥:" + new String (c) + "\n" ); return new String (c); } public static void main (String[] args) { Map<String, Object> keyMap; try { keyMap = initKey(); String publicKey = getPublicKey(keyMap); System.out.println("公钥:" + publicKey + "\n" ); String privateKey = getPrivateKey(keyMap); System.out.println("私钥:" + privateKey + "\n" ); System.out.println("===================先加密再加签===================" ); String str = "签名内容!" ; String encryptData = encryptRSADate(str, publicKey); System.out.println("公钥加密:" + encryptData + "\n" ); String sign = sign(encryptData, privateKey); System.out.println("加签后:" + sign + "\n" ); boolean verify = verify(encryptData, sign, publicKey); System.out.println("验签情况:" + verify + "\n" ); String decryptData = decryptRSADate("WXpNTk5yTU90NHJiQWwyZlBzNnJKdC9ZWk80YUJyWiswS1orUjR1clYxa2JuVVJRNnN0eUh4ekNVQlM0cFUxK3R6dU1PYVlsZkZQM2ZHaHB4VXNPbFRRbXZwWithUjVVZWtLVWRic2hqV0FweUpkNDZaLy9XTnVLMk9uYmhTMGdWMkZIK1NHUlc4L0dnR2ZPdmpOVjJrYmkzS25aQWdkUFRKT3NwTlowQ0owPQ==" , privateKey); System.out.println("私钥解密:" + decryptData); System.out.println(randomKey(16 )); } catch (Exception e) { e.printStackTrace(); } } }
由于RSA的特性,使用1024位密钥长度
生产的公私钥(75行代码
)时,分段最大加密和最大解密长度分别需为:117(理论小于117都可以,但加密慢)、128(MAX_DECRYPT_BLOCK应等于密钥长度/8(1byte=8bit),此为固定值,否则报此类错:Data must not be longer than 128 bytes)两者必须配套使用
;如果采用2048
,最大加密和最大解密则为:245、256
,当加解密长度不匹配时,程序就会抛出如下异常
Exception in thread "main" javax.crypto.BadPaddingException: Decryption error at sun.security.rsa.RSAPadding.unpadV15(Unknown Source) at sun.security.rsa.RSAPadding.unpad(Unknown Source) at com.sun.crypto.provider.RSACipher.doFinal(RSACipher.java:363) at com.sun.crypto.provider.RSACipher.engineDoFinal(RSACipher.java:389) at javax.crypto.Cipher.doFinal(Cipher.java:2223) .....
控制层类 package cn.goitman.controller;import cn.goitman.pojo.Consumer;import cn.goitman.util.CreateRsaSecrteKeyUtil;import cn.shuibo.annotation.Decrypt;import cn.shuibo.annotation.Encrypt;import com.alibaba.fastjson.JSON;import org.springframework.beans.factory.annotation.Value;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestBody;import org.springframework.web.bind.annotation.RestController;import javax.servlet.http.HttpServletRequest;@RestController public class RsaCotroller { @Value("${rsa.encrypt.privateKey}") private String privateKey; @Value("${rsa.encrypt.publicKey}") private String publicKey; @Encrypt @GetMapping("/encrypt") public String encrypt () { Consumer consumer = new Consumer ("注解加密用户" , "注解加密密码" ); return consumer.toString(); } @Decrypt @PostMapping("/decrypt") public String decrypt (@RequestBody Consumer consumer) { return consumer.toString(); } @GetMapping("/encryption") public String encryption () { Consumer consumer = new Consumer ("手工加密用户" , "手工加密密码" ); return CreateRsaSecrteKeyUtil.encryptRSADate(JSON.toJSONString(consumer), publicKey); } @PostMapping("/decryption") public String decryption (@RequestBody String str) { Consumer consumer = JSON.parseObject(CreateRsaSecrteKeyUtil.decryptRSADate(str, privateKey), Consumer.class); return consumer.toString(); } @GetMapping("/signature") public String signature () { Consumer consumer = new Consumer ("手工加密加签" , "手工加密加签" ); String encryptDate = CreateRsaSecrteKeyUtil.encryptRSADate(JSON.toJSONString(consumer), publicKey); String sign = CreateRsaSecrteKeyUtil.sign(encryptDate, privateKey); System.out.println("密文:" + encryptDate + "\n" ); System.out.println("加签:" + sign); return sign + "&" + encryptDate; } @PostMapping("/verify") public String verify (@RequestBody String str) { String[] data = str.split("&" ); boolean verify = CreateRsaSecrteKeyUtil.verify(data[1 ], data[0 ], publicKey); if (verify) { Consumer consumer = JSON.parseObject(CreateRsaSecrteKeyUtil.decryptRSADate(data[1 ], privateKey), Consumer.class); return "验签成功:" + consumer.toString(); } else { return "验签失败!" ; } } @GetMapping("/encodeAES") public String encodeAES () { Consumer consumer = new Consumer ("手工加密用户" , "手工加密密码" ); String key = CreateRsaSecrteKeyUtil.randomKey(16 ); String data = CreateRsaSecrteKeyUtil.encryptAES(consumer.toString(), key); try { return data + "&" + key; } catch (Exception e) { e.printStackTrace(); } return "加密失败!" ; } @PostMapping("/decodeAES") public String decodeAES (@RequestBody String str, HttpServletRequest req) { String encodeKey = req.getHeader("encodeKey" ); try { String key = CreateRsaSecrteKeyUtil.decryptRSADate(encodeKey, privateKey).replace("\"" , "" ); return CreateRsaSecrteKeyUtil.decryptAES(str, key); } catch (Exception e) { return "解密失败!" ; } } }
注解加解密不能和手工加解密签一起使用,会有冲突,后面测试章节会介绍
请求页面 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <title > 非对称加解密加验签与对称加解密</title > <script src ="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.js" > </script > <script src ="js/jsencrypt.js" > </script > <script src ="js/jsrsasign-all-min.js" > </script > <script src ="https://cdn.bootcss.com/crypto-js/3.1.9-1/crypto-js.min.js" > </script > </head > <body > <button id ="jiami" onclick ="zhujie1()" > 前端解密,后端 注解 加密</button > <button id ="jiami1" onclick ="zhujie2()" > 前端加密,后端 注解 解密</button > <button id ="jiami3" onclick ="shoudong1()" > 前端解密,后端 手动 加密</button > <button id ="jiami4" onclick ="shoudong2()" > 前端加密,后端 手动 解密</button > <button id ="jiami5" onclick ="jiaqian1()" > 前端验签解密,后端 手动 加密加签</button > <button id ="jiami6" onclick ="jiaqian2()" > 前端加密加签,后端 手动 验签解密</button > <button id ="jiami7" onclick ="aesDecode()" > AES后端加密,前端解密</button > <button id ="jiami8" onclick ="aesEncode()" > AES前端加密,后端解密</button > <script > var PUBLIC_KEY = '' ; var PRIVATE_KEY = '' var ALGORITHM = 'MD5withRSA' ; function RSA_encryption (jsonData ) { var encrypt = new JSEncrypt (); encrypt.setPublicKey (PUBLIC_KEY ); var encrypted = encrypt.encryptLong (JSON .stringify (jsonData)); console .log ('加密前数据:%o' , jsonData); console .log ('加密后数据:%o' , encrypted); console .log ('加密前数据:%o' , jsonData.length + " " +encrypted.length ); return encrypted; } function RSA_decryption (jsonData ) { var decrypt = new JSEncrypt (); decrypt.setPrivateKey (PRIVATE_KEY ); var decrypted = decrypt.decryptLong (jsonData); console .log ('解密前数据:%o' , jsonData);0 console .log ('解密后数据:%o' , decrypted); return decrypted; } function RSA_sign (encryptDate ) { var signature = new KJUR .crypto .Signature ({alg : ALGORITHM , prvkeypem : '-----BEGIN PRIVATE KEY-----' + PRIVATE_KEY + '-----END PRIVATE KEY-----' }); signature.updateString (encryptDate); var sign = hextob64 (signature.sign ()); console .log ('私钥加签数据:%o' , sign); return sign + "&" + encryptDate; } function RSA_verify (encryptDate, sign ) { var signature = new KJUR .crypto .Signature ({alg : ALGORITHM , prvkeypem : '-----BEGIN PUBLIC KEY-----' + PUBLIC_KEY + '-----END PUBLIC KEY-----' }); signature.updateString (encryptDate); var result = signature.verify (b64tohex (sign)); console .log ('加签:%o' , sign); console .log ('验签结果:%o' , result); return result; } function zhujie1 ( ) { $.ajax ({ url : "/encrypt" , type : "GET" , contentType : "application/json;charset=utf-8" , success : function (reslut ) { var res = RSA_decryption (reslut) alert (res); } }) } function zhujie2 ( ) { var str = { "name" : "前端加密用户" , "passWord" : "前端加密密码" }; $.ajax ({ url : "/decrypt" , type : "POST" , contentType : "application/json;charset=utf-8" , data : RSA_encryption (str), success : function (data ) { alert (data); } }) } function shoudong1 ( ) { $.ajax ({ url : "/encryption" , type : "GET" , contentType : "application/json;charset=utf-8" , success : function (reslut ) { var res = RSA_decryption (reslut) alert (res); } }) } function shoudong2 ( ) { var str = { "name" : "加密用户1" , "passWord" : "加密密码1" }; $.ajax ({ url : "/decryption" , type : "POST" , contentType : "application/json;charset=utf-8" , data : RSA_encryption (str), success : function (data ) { alert (data); } }) } function jiaqian1 ( ) { $.ajax ({ url : "/signature" , type : "GET" , contentType : "application/json;charset=utf-8" , success : function (reslut ) { var res = reslut.split ("&" ); var re = RSA_verify (res[1 ], res[0 ]); var encryptDate = JSON .stringify (RSA_decryption (res[1 ])); alert (re + ": " + encryptDate); } }) } function jiaqian2 ( ) { var str = { "name" : "手动解签解密1" , "passWord" : "手动解签解密1" }; var encryptDate = RSA_encryption (str); var sign = RSA_sign (encryptDate) $.ajax ({ url : "/verify" , type : "POST" , contentType : "application/json;charset=utf-8" , data : sign, success : function (data ) { alert (data); } }) } function randomString (length ) { var str = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' ; var result = '' ; for (var i = length; i > 0 ; --i) result += str[Math .floor (Math .random () * str.length )]; return result.toString (); } function encryptAES (context, key ) { var encrypted = '' ; if (typeof (context) == 'string' ) { } else if (typeof (context) == 'object' ) { context = JSON .stringify (context); } var srcs = CryptoJS .enc .Utf8 .parse (context); encrypted = CryptoJS .AES .encrypt (srcs, CryptoJS .enc .Utf8 .parse (key), { iv : CryptoJS .enc .Utf8 .parse ("初始向量" ), mode : CryptoJS .mode .CBC , padding : CryptoJS .pad .Pkcs7 }); return encrypted.toString (); } function decryptAES (context, key ) { var decrypt = CryptoJS .AES .decrypt (context, CryptoJS .enc .Utf8 .parse (key), { iv :CryptoJS .enc .Utf8 .parse ("初始向量" ), mode : CryptoJS .mode .CBC , padding : CryptoJS .pad .Pkcs7 }); var decryptedStr = decrypt.toString (CryptoJS .enc .Utf8 ); return decryptedStr.toString (); } function aesDecode ( ) { $.ajax ({ url : "/encodeAES" , type : "GET" , contentType : "application/json;charset=utf-8" , success : function (reslut ) { var res = reslut.split ("&" ); var key = res[1 ]; var encryptDate = decryptAES (res[0 ], key); alert (encryptDate + " : " + key); } }) } function aesEncode ( ) { var k = randomString (16 ); var str = { "name" : "AES加解密" , "passWord" : "AES加解密" }; var encryptDate = encryptAES (str,k) $.ajax ({ url : "/decodeAES" , type : "POST" , contentType : "application/json;charset=utf-8" , headers : { "encodeKey" : RSA_encryption (k) }, data : encryptDate, success : function (data ) { alert (data); } }) } </script > </body > </html >
注意事项:
第36行加验签算法需跟工具类中的加验签算法一致,否则解签失败
第68、80行加签验签方法中参数prvkeypem,必须传入pem标准格式的秘钥字符串,否则加验签失败;标准的pem格式含有开始标记和结束标记,如本文使用的秘钥:—–BEGIN xxx—–,—–END xxx—–。至于xxx的具体内容不是太重要,代码里自动通过正则清洗掉头和尾标记
第220、231行的iv初始向量必须与CreateRsaSecrteKeyUtil工具类中第64行中定义的iv初始向量一致,否则加解密失败
测试 注解加解密和手工加解密签一起使用产生冲突问题
按照1、2、3的步骤来测试验证下效果,首先请求后端注解解密接口
毫无疑问,参数解密成功,直接返回前端,并弹框显示
第2步,请求后端手动解密接口;咦!接收参数怎么就已经解密啦?来debug
原来第1步调起的解密注解,既然还在工作,喝,好家伙把第2步的请求参数也顺带解密啦,导致工具类解密时报出JSON数据异常
好吧,接着第3步,请求后端手动解签解密接口;我去,数据呢?怎么空字符串啦?
前端明明正常发送请求数据了啊,上debug
还是解密注解造的孽,它无法解密加签的数据(加签算法和加密算法不一样,看工具类详情可知),所以直接报解密失败,并返回个空字符串
结论:只要开启了解密注解,它就会一直保持开启解密状态,从而影响手动解密方法的正常使用
小程序非对称加密 在上请求页面中RSA加解密使用了jsencrypt.js,其中有如下一段代码:
因为微信小程序的js中不能使用window对象,会导致报undefined错误(难道window对象真的在小程序中不存在吗?事实并非如此,有兴趣的可以去研究下,在这不作扩展),所以需换个思路来解决问题,使用wx_rsa.js(源码中已附文件)来实现非对称加密,使用方法如下
JS引用 var RSA = require ('../wx_rsa.js' )
加密 function wx_RSA_encrypto (jsonData ) { var encrypt_rsa = RSA .KEYUTIL .getKey ("-----BEGIN PUBLIC KEY-----公钥-----END PUBLIC KEY-----" ); var encStr = encrypt_rsa.encrypt (jsonData); return RSA .hex2b64 (encStr); }
解密 客户端一般不推荐保存私钥,此为示例
function wx_RSA_decrypto (encStr ) { decrypt_rsa = RSA .KEYUTIL .getKey ("-----BEGIN PRIVATE KEY-----私钥-----END PRIVATE KEY-----" ); encStr = RSA .b64tohex (encStr); var decStr = decrypt_rsa.decrypt (encStr); return decStr; }
至于加签验签可用回上节请求页面中的方法,当然也可用wx_rsa.js中的方法,在此不做扩展,自行研究调用
后述 一般服务端保存私钥,客户端保存公钥,可如下操作
服务端私钥先加密再加签,返回加密信息和加签信息给客户端;客户端取加密信息并加签,与服务端返回的加签信息做对比,如果不一致则信息已被串改
服务端先明文加签再使用私钥加密,返回加密信息和加签信息给客户端;客户端取加密信息先解密再加签,与服务端返回的加签信息做对比,如果不一致则信息已被串改
源码地址:https://github.com/wangdaicong/spring-boot-project/tree/master/rsa-encrypt-demo