AES 前后端加解密方案
AES 前后端加解密方案
背景
最近有一个需求:后端对敏感数据进行加密传输给前端,由前端解密后进行回显。在讨论之后,定下了AES加解密方案
概念
AES: 密码学中的高级加密标准(Advanced Encryption Standard,AES),又称Rijndael加密法,是美国联邦政府采用的一种区块加密标准,是最为常见的对称加密算法
密码说明
AES算法主要有四种操作处理,分别是:
密钥轮加(Add Round Key)
字节代换层(SubBytes)
行位移层(Shift Rows)
列混淆层(Mix Column)
主要是讲使用方案,所以这里不说太多废话了,对算法感兴趣的同学移步这里, 讲的非常详细,不过文章里的代码是使用C语言写的,为此找到了github上aes.js
的源码,感兴趣的同学移步这里
前端实现
现在简单说一下前端的实现:
我先找到了github上的源码,看了一下大概800行的样子。本来打算直接改吧改吧,封装成一个加解密的工具方法,直接扔在utils目录里的。本来也很成功的改好了,本地加解密试了一下,效果也很不错。根据github链接上的readme文档说明,封装了如下函数:
// 省略了改完的aes.js的代码。。。 // 加密 text 需要加密的文本 key 密钥 const toAESBytes = (text, key) => { const textBytes = aesjs.utils.utf8.toBytes(text); const aesCtr = new aesjs.ModeOfOperation.ctr(key, new aesjs.Counter(5)); const encryptedBytes = aesCtr.encrypt(textBytes); const encryptedHex = aesjs.utils.hex.fromBytes(encryptedBytes); console.log('加密后的文本:', encryptedHex); return encryptedHex; }; // 解密 const fromAESBytes = (text, key) => { const encryptedBytes = aesjs.utils.hex.toBytes(text); const aesCtr = new aesjs.ModeOfOperation.ctr(key, new aesjs.Counter(5)); const decryptedBytes = aesCtr.decrypt(encryptedBytes); const decryptedText = aesjs.utils.utf8.fromBytes(decryptedBytes); console.log('解密后的文本:', decryptedText); return decryptedText; } 复制代码
但是这个方法在和后端对接的时候出现了一点偏差,死活也不能将后端加密后的数据成功解密。于是又向后端同学请教了一下,发现原因如下:
在AES加解密算法中,除了加解密的密文,也就是key需要一样之外,还有几样东西也非常重要:
AES 的算法模式需要保持一致
关于算法模式,主要有以下几种:
1. 电码本模式 Electronic Codebook Book (ECB); 2. 密码分组链接模式 Cipher Block Chaining (CBC) 3. 计算器模式Counter (CTR) 4. 密码反馈模式(Cipher FeedBack (CFB) 5. 输出反馈模式Output FeedBack (OFB) 复制代码
这么看的话,我上面的demo应该使用的就是计算器模式了!关于算法模式的介绍,感兴趣的同学请移步这里
补码方式保持一致
关于补码方式,我查到的以下几种:
1. PKCS5Padding PKCS7Padding的子集,块大小固定为8字节 2. PKCS7Padding 假设数据长度需要填充n(n>0)个字节才对齐,那么填充n个字节,每个字节都是n;如果数据本身就已经对齐了,则填充一块长度为块大小的数据,每个字节都是块大小。 3. ZeroPadding 数据长度不对齐时使用0填充,否则不填充 复制代码
密钥长度保持一致
AES算法一共有三种密钥长度:128、192、256。这个前后端的密钥长度确实是保持一致的。
加密结果编码方式保持一致
一般情况下,AES加密结果有两种编码方式:base64 和 16进制
所以到底是哪里出了问题呢?后端同学好心发给了我他后端的代码:
/** * aes 加密 Created by xingxiping on 2017/9/20. */ public class AesUtils { private static final String CIPHER_ALGORITHM = "AES"; // optional value AES/DES/DESede private AesUtils(){ } /** * 加密 * * @param content * 源内容 * @param key * 加密密钥 * @return */ public static String encrypt(String content, String key) throws Exception { Cipher cipher = getCipher(key, Cipher.ENCRYPT_MODE); byte[] byteContent = content.getBytes(StandardCharsets.UTF_8); byte[] result = cipher.doFinal(byteContent); return Base64Utils.encode(result); } /** * 解密 * * @param content * 内容 * @param key * 解密密钥 * @return */ public static byte[] decrypt(String content, String key) throws Exception { Cipher cipher = getCipher(key, Cipher.DECRYPT_MODE); byte[] bytes = Base64Utils.decode(content); bytes = cipher.doFinal(bytes); return bytes; } private static Cipher getCipher(String key, int cipherMode) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException { Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(), "AES"); cipher.init(cipherMode, secretKey); return cipher; } } 复制代码
破案了,后端老哥对加密后的结果进行了base64编码,然后我又仔细去看了一下aes.js源码,根本没有找到base64的影子啊!
于是在查找一翻资料以后,决定使用crypto-j
,使用 Crypto-JS 可以非常方便地在 JavaScript 进行 MD5、SHA1、SHA2、SHA3、RIPEMD-160 哈希散列,进行 AES、DES、Rabbit、RC4、Triple DES 加解密。真是方便呀,老规矩,感兴趣的同学可以移步这里
以下是我又一轮的解决步骤:
npm install crypto-js
在utils目录下新建一个文件aes.js
封装如下代码:
// aes 解密 import CryptoJS from 'crypto-js'; // 解密 encryptedStr待解密字符串 pass 密文 export const aesDecode = (encryptedStr, pass) => { const key = CryptoJS.enc.Utf8.parse(pass); // 通过密钥获取128位的key const encryptedHexStr = CryptoJS.enc.Base64.parse(encryptedStr); // 解码base64编码结果 const encryptedBase64Str = CryptoJS.enc.Base64.stringify(encryptedHexStr); const decryptedData = CryptoJS.AES.decrypt(encryptedBase64Str, key, { mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7 }); return decryptedData.toString(CryptoJS.enc.Utf8); } 复制代码
然后就可以正常调用了!
最后,终于成功解密!
一点点小感悟
在日常工作中真的很少使用算法,对称加密在学校里听起来好像非常简单的样子,但是真的应用到生活中,特别是安全领域,还是非常复杂的。哎,学无止境吧~
作者:溜溜球形废物
链接:https://juejin.cn/post/6951368041590423582