AES 前后端加解密方案

吐槽君 分类:javascript

AES 前后端加解密方案

背景

最近有一个需求:后端对敏感数据进行加密传输给前端,由前端解密后进行回显。在讨论之后,定下了AES加解密方案

概念

AES: 密码学中的高级加密标准(Advanced Encryption Standard,AES),又称Rijndael加密法,是美国联邦政府采用的一种区块加密标准,是最为常见的对称加密算法

密码说明

AES算法主要有四种操作处理,分别是:

  1. 密钥轮加(Add Round Key)
  2. 字节代换层(SubBytes)
  3. 行位移层(Shift Rows)
  4. 列混淆层(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 加解密。真是方便呀,老规矩,感兴趣的同学可以移步这里

以下是我又一轮的解决步骤:

  1. npm install crypto-js

  2. 在utils目录下新建一个文件aes.js

  3. 封装如下代码:

    // 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);
    }
     
  4. 然后就可以正常调用了!

最后,终于成功解密!

一点点小感悟

在日常工作中真的很少使用算法,对称加密在学校里听起来好像非常简单的样子,但是真的应用到生活中,特别是安全领域,还是非常复杂的。哎,学无止境吧~

感谢大家的阅读!

本文参考:

  1. 密码学基础:AES加密算法;
  2. 高级加密标准
  3. AES加密算法的详细介绍与实现
  4. github.com/ricmoo/aes-…
  5. github.com/brix/crypto…

回复

我来回复
  • 暂无回复内容