【AES 算法】实现服务端 Java 加密,前端 JS 解密

目录

  • 背景
  • AES CBC 加解密算法
  • 代码实现
    • Java 生成 key 和 iv
    • Java 加密 & 解密
    • JavaScript 解密
  • 小结

背景

我们的游戏充值平台马上要到货一批充值码,需要入库。之前充值码发奖相关的需求都是我做的,但在存储充值码的时候没有加密,是明文存储的。

现在的需求是,数据库中的充值码需要密文存储。这就涉及到:

  • 提供一个新增充值码记录的接口,请求参数为明文,使用 Java 加密后把充值码密文存入数据库;
  • 发送充值码邮件时,需要使用 Java 解密充值码,给用户发送明文;
  • 前端展示充值码时,服务端传送密文,前端使用 JavaScript 解密,给用户展示明文;(纯属多此一举,因为 HTTPS 通信本来就是加密的。但业务方坚持要这样做。)

AES CBC 加解密算法

这是我第一次做加密相关的需求。一开始(几周前吧)图省事,想着在 StackOverflow 上搜一下,一两行代码就搞定了,不就是个加密嘛。后来发现,怎么都这么复杂啊,一直搜到我身心俱疲,也没找到简单的方法。

这回我静下心来,好好读了一篇介绍 AES 的文章,终于大体上搞明白了。

参考链接:Java AES Encryption and Decryption

简单来说就是,AES 分为很多模式,但大家基本上都用 CBC。

在 CBC 模式下,除了秘钥 key 之外,为了增强安全性,还需要一个 iv。(最基础的 ECB 模式不需要 iv,只需要 key,但该模式不提倡使用)。

key 有 128、192、256 位三种选择,iv 固定是 128 位,因为加密块固定是 128 位,需要加密的信息需要先分成 128 位大小的块,如果最后一块不足 128 位需要填充到 128 位(padding)。实际上并不需要用户自己填充,指定参数就行。

代码实现

Java 生成 key 和 iv

生成一个新的 key(默认 128 位):

public static SecretKey generateKey(int n) throws NoSuchAlgorithmException {
    KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
    keyGenerator.init(n);
    SecretKey key = keyGenerator.generateKey();
    return key;
}

生成一个新的 iv

public static IvParameterSpec generateIv() {
    byte[] iv = new byte[16];
    new SecureRandom().nextBytes(iv);
    return new IvParameterSpec(iv);
}

当然,生成之后最终是要用字符串的格式保存和传送 keyiv 的。下面使用 base64 格式保存:

// 使用上面的方法生成 key 并转换为 base64 格式
SecretKey key = EncryptUtils.generateKey(128);
String keyBase64 = Base64.getEncoder().encodeToString(key.getEncoded());

// 使用上面的方法生成 iv 并转换为 base64 格式
IvParameterSpec ivParameterSpec = EncryptUtils.generateIv();
String ivBase64 = Base64.getEncoder().encodeToString(ivParameterSpec.getIV());
        

Java 加密 & 解密

首先,可以把 base64 格式的 keyiv 转换回 Java 中的类型(参考链接:Converting Secret Key into a String and Vice Versa)

代码如下:

// 导入 key
byte[] keyBytes = Base64.getDecoder().decode(keyBase64);
SecretKey key = new SecretKeySpec(keyBytes, 0, keyBytes.length, "AES");

// 导入 iv
byte[] ivBytes = Base64.getDecoder().decode(ivBase64);
IvParameterSpec iv = new IvParameterSpec(ivBytes);

之后就是用 keyiv 来进行加密和解密了:

String algorithm = "AES/CBC/PKCS5Padding";

// 加密,input 是要加密的明文,返回的是一个 base64 格式的密文:
public static String encrypt(String algorithm, String input, SecretKey key,
    IvParameterSpec iv) throws NoSuchPaddingException, NoSuchAlgorithmException,
    InvalidAlgorithmParameterException, InvalidKeyException,
    BadPaddingException, IllegalBlockSizeException {
    
    Cipher cipher = Cipher.getInstance(algorithm);
    cipher.init(Cipher.ENCRYPT_MODE, key, iv);
    byte[] cipherText = cipher.doFinal(input.getBytes());
    return Base64.getEncoder()
        .encodeToString(cipherText);
}

// 解密,cipherText 是 base64格式的密文
public static String decrypt(String algorithm, String cipherText, SecretKey key,
    IvParameterSpec iv) throws NoSuchPaddingException, NoSuchAlgorithmException,
    InvalidAlgorithmParameterException, InvalidKeyException,
    BadPaddingException, IllegalBlockSizeException {
    
    Cipher cipher = Cipher.getInstance(algorithm);
    cipher.init(Cipher.DECRYPT_MODE, key, iv);
    byte[] plainText = cipher.doFinal(Base64.getDecoder()
        .decode(cipherText));
    return new String(plainText);
}

当然,实际使用时还是会做一些封装什么的,比如我把 cipher 单独存起来了。但大体就是这样了。

JavaScript 解密

JS 这边使用 CryptoJS 这个库来解密。

我没找到介绍 JS AES 加解密的特别好的文章,CryptoJS 的文档我感觉写的也不是很好。后来一路磕磕绊绊,看了好多个 StackOverflow 和 JSFiddle 之类的,花了得有俩小时,终于找到了解密方法:

参考链接:AES encryption using Java and decryption using Javascript

// 从 base64 格式导入 key 和 iv
var key = CryptoJS.enc.Base64.parse('nlCdv7/wqRIsf1iWzqz96Q==');
var iv = CryptoJS.enc.Base64.parse('n9CvQB/1quXtItsdhnel2g==');

function decrypt(encrypted) {
  var cipherParams = CryptoJS.lib.CipherParams.create({
  	// 从 base64 格式导入密文
    ciphertext: CryptoJS.enc.Base64.parse(encrypted)
  });
  // 解密
  return CryptoJS.AES.decrypt(cipherParams, key, {
    iv: iv,
    padding: CryptoJS.pad.Pkcs7,
    mode: CryptoJS.mode.CBC
  }).toString(CryptoJS.enc.Utf8);
}

小结

用 Java 进行 AES CBC 加密/解密还是比较简单的,之前只因我太急躁,错误地留下了“这件事很难”的印象。

先把加密的基本原理和流程搞清楚,再做就好多了。

你可能感兴趣的:(Java,java,加密解密,javascript)