前言
为了提高用户登陆的安全性,公司准备整理一份相对安全的登陆模式。
想法
主流加密算法
主流加密算法
-
(一)对称加密AES ,其特点是:算法简单,加密速度快;
-
(二)非对称加密方式,代表是RSA加密算法,其特点–采用的一对秘钥机制(即加解密秘钥不同),公钥加密、私钥解密,管理简单,缺点是解密速度慢。
最终方式
具体过程是先由接收方创建RSA密钥对,接收方通过Internet发送RSA公钥到发送方,同时保存RSA私钥。而发送方创建AES密钥。并用该 AES密钥加密待传送的明文数据,同时用接受的RSA公钥加密AES密钥,最后把用RSA公钥加密后的AES密钥同密文一起通过Internet传输发送 到接收方。当接收方收到这个被加密的AES密钥和密文后,首先调用接收方保存的RSA私钥,并用该私钥解密加密的AES密钥,得到AES密钥。最后用该 AES密钥解密密文得到明文。
基本流程
请求:
- 服务器端(server)生成密钥对
- server给client自己的公钥
- client生成AES密钥(aesKey)
- client使用自己的RSA私钥(privateKey)对请求明文数据(params)进行数字签名
- 将签名加入到请求参数中,然后转换为json格式
- client使用aesKey对json数据进行加密得到密文(data)
- client使用sever的RSA公钥对aesKey进行加密(encryptkey)
- 分别将data和encryptkey作为参数传输给服务器端
服务器端进行请求响应时将上面流程反过来即可
使用
- 安装crypto-js
npm install crypto-js
npm install jsencrypt
- AES加密工具类
import CryptoJS from 'crypto-js'
import { JSEncrypt } from 'jsencrypt'
/*** 创建密钥* @returns AES密钥*/
export function createAesKey() {const expect = 16let str = Math.random().toString(36).substr(2)while (str.length < expect) {str += Math.random().toString(36).substr(2)}str = str.substr(0, 16)return str
}
/*** AES加密* @param {*} word 加密字段* @param {*} keyStr AES密钥* @returns */
export function AESencrypt(word, keyStr) {keyStr = keyStr ? keyStr : 'abcdefgabcdefg12';var key = CryptoJS.enc.Utf8.parse(keyStr); //Latin1 w8m31+Yy/Nw6thPsMpO5fg==var srcs = CryptoJS.enc.Utf8.parse(word);var encrypted = CryptoJS.DES.encrypt(srcs, key, {mode: CryptoJS.mode.ECB,padding: CryptoJS.pad.Pkcs7});return encrypted.ciphertext.toString();
}
/*** RSA加密算法* @param {*} pas * @returns */
export function RSAencrypt(pas,publickey) {let jse = new JSEncrypt();jse.setPublicKey(publickey);return jse.encrypt(pas)}
- 服务端
RSA工具类
import org.apache.commons.codec.binary.Base64;import javax.crypto.Cipher;
import java.security.*;
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 KEY_ALGORITHM = "RSA";public static final String SIGNATURE_ALGORITHM = "MD5withRSA";private static final String PUBLIC_KEY = "RSAPublicKey";private static final String PRIVATE_KEY = "RSAPrivateKey";public static byte[] decryptBASE64(String key) {return Base64.decodeBase64(key);}public static String encryptBASE64(byte[] bytes) {return Base64.encodeBase64String(bytes);}/*** 用私钥对信息生成数字签名** @param data 加密数据* @param privateKey 私钥* @return* @throws Exception*/public static String sign(byte[] data, String privateKey) throws Exception {// 解密由base64编码的私钥byte[] keyBytes = decryptBASE64(privateKey);// 构造PKCS8EncodedKeySpec对象PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);// KEY_ALGORITHM 指定的加密算法KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);// 取私钥匙对象PrivateKey priKey = keyFactory.generatePrivate(pkcs8KeySpec);// 用私钥对信息生成数字签名Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);signature.initSign(priKey);signature.update(data);return encryptBASE64(signature.sign());}public static PrivateKey strToPrivateKey(String privateKey) throws Exception {// 解密由base64编码的私钥byte[] keyBytes = decryptBASE64(privateKey);// 构造PKCS8EncodedKeySpec对象PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);// KEY_ALGORITHM 指定的加密算法KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);// 取私钥匙对象return keyFactory.generatePrivate(pkcs8KeySpec);}/*** 校验数字签名** @param data 加密数据* @param publicKey 公钥* @param sign 数字签名* @return 校验成功返回true 失败返回false* @throws Exception*/public static boolean verify(byte[] data, String publicKey, String sign)throws Exception {// 解密由base64编码的公钥byte[] keyBytes = decryptBASE64(publicKey);// 构造X509EncodedKeySpec对象X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);// KEY_ALGORITHM 指定的加密算法KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);// 取公钥匙对象PublicKey pubKey = keyFactory.generatePublic(keySpec);Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);signature.initVerify(pubKey);signature.update(data);// 验证签名是否正常return signature.verify(decryptBASE64(sign));}public static byte[] decryptByPrivateKey(byte[] data, String key) throws Exception{// 对密钥解密byte[] keyBytes = decryptBASE64(key);// 取得私钥PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);Key privateKey = keyFactory.generatePrivate(pkcs8KeySpec);// 对数据解密Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());cipher.init(Cipher.DECRYPT_MODE, privateKey);return cipher.doFinal(data);}/*** 解密<br>* 用私钥解密** @param data* @param key* @return* @throws Exception*/public static byte[] decryptByPrivateKey(String data, String key)throws Exception {return decryptByPrivateKey(decryptBASE64(data),key);}/*** 解密<br>* 用公钥解密** @param data* @param key* @return* @throws Exception*/public static byte[] decryptByPublicKey(byte[] data, String key)throws Exception {// 对密钥解密byte[] keyBytes = decryptBASE64(key);// 取得公钥X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);Key publicKey = keyFactory.generatePublic(x509KeySpec);// 对数据解密Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());cipher.init(Cipher.DECRYPT_MODE, publicKey);return cipher.doFinal(data);}/*** 加密<br>* 用公钥加密** @param data* @param key* @return* @throws Exception*/public static byte[] encryptByPublicKey(String data, String key)throws Exception {// 对公钥解密byte[] keyBytes = decryptBASE64(key);// 取得公钥X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);Key publicKey = keyFactory.generatePublic(x509KeySpec);// 对数据加密Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());cipher.init(Cipher.ENCRYPT_MODE, publicKey);return cipher.doFinal(data.getBytes());}/*** 加密<br>* 用私钥加密** @param data* @param key* @return* @throws Exception*/public static byte[] encryptByPrivateKey(byte[] data, String key)throws Exception {// 对密钥解密byte[] keyBytes = decryptBASE64(key);// 取得私钥PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);Key privateKey = keyFactory.generatePrivate(pkcs8KeySpec);// 对数据加密Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());cipher.init(Cipher.ENCRYPT_MODE, privateKey);return cipher.doFinal(data);}/*** 取得私钥** @param keyMap* @return* @throws Exception*/public static String getPrivateKey(Map<String, Key> keyMap)throws Exception {Key key = (Key) keyMap.get(PRIVATE_KEY);return encryptBASE64(key.getEncoded());}/*** 取得公钥** @param keyMap* @return* @throws Exception*/public static String getPublicKey(Map<String, Key> keyMap)throws Exception {Key key = keyMap.get(PUBLIC_KEY);return encryptBASE64(key.getEncoded());}/*** 初始化密钥** @return* @throws Exception*/public static Map<String, Key> initKey() throws NoSuchAlgorithmException {KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM);keyPairGen.initialize(1024);KeyPair keyPair = keyPairGen.generateKeyPair();Map<String, Key> keyMap = new HashMap(2);keyMap.put(PUBLIC_KEY, keyPair.getPublic());// 公钥keyMap.put(PRIVATE_KEY, keyPair.getPrivate());// 私钥return keyMap;}
}
DES工具类
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.util.Locale;public class DESUtil {/*** DES解密算法* @param content 解密字段* @param key 解密密钥* @return*/public static String decryptedDES(String content,String key) {try {Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding");cipher.init(Cipher.DECRYPT_MODE, generateKey(key));byte[] buf = cipher.doFinal(hexStr2Bytes(content));return new String(buf, "utf-8");} catch (Throwable e) {e.printStackTrace();}return null;}private static SecretKey generateKey(String secretKey)throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException {SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");DESKeySpec keySpec = new DESKeySpec(secretKey.getBytes());keyFactory.generateSecret(keySpec);return keyFactory.generateSecret(keySpec);}public static byte[] hexStr2Bytes(String src) {src = src.trim().replace(" ", "").toUpperCase(Locale.US);int m = 0, n = 0;int iLen = src.length() / 2;byte[] ret = new byte[iLen];for (int i = 0; i < iLen; i++) {m = i * 2 + 1;n = m + 1;ret[i] = (byte) (Integer.decode("0x" + src.substring(i * 2, m) + src.substring(m, n)) & 0xFF);}return ret;}
}
解密算法
前端入参
encryptedWords DES加密后的报文
encryptedKey RSA算法加密过的DES密钥
import com.bull3d.core.tool.utils.RedisUtil;
import com.bull3d.system.user.cache.CacheNames;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Component;/*** @Author: zhangshl* @Date: 2021/4/10 17:21* @Modify: linyuchi* 重新编写解密算法。 2021/04/26*/
@Component
@AllArgsConstructor
public class DecryptUtil {private RedisUtil redisUtil;public String decrypt(String encryptedWords, String encryptedKey){// 解密aes密钥String privateKey = String.valueOf(redisUtil.get(CacheNames.SRA_KRY_PRIVATE));if (null == privateKey){return "";}try {String decrypt = new String(RSAUtil.decryptByPrivateKey(encryptedKey,privateKey));return DESUtil.decryptedDES(encryptedWords,decrypt);} catch (Exception e) {e.printStackTrace();return "";}}
}
前端获取RSA密钥,我其实在服务端设计密钥对放在redis服务器中。过期时间为1天,如果过期则从新生成。
/*** 获取RSA秘钥*/@ApiOperation(value = "获取RSA秘钥")@GetMapping("/auth/rsa-key")public R<String> rsaKey(){String key = authService.getRsaKey();return R.data(key);}