AES加解密(golang <--> crypto-js)
AES(Advanced Encryption Standard) 是一种对称加密算法,是比 DES 更好的对称加密算法类。
使用AES,在前后端之间传送密码等相关数据时,能简单高效的提高安全性。
前端的 AES(javascript/typescript)
前端的加密库一般用 crypto-js。crypto-js 支持很多加密和hash算法,使用 AES算法很简单。
var CryptoJS = require("crypto-js");
var secretKey = "len16 secret key"
// Encrypt
var ciphertext = CryptoJS.AES.encrypt('mysqlpassword', secretKey).toString();
// Decrypt
var bytes = CryptoJS.AES.decrypt(ciphertext, secretKey);
var originalText = bytes.toString(CryptoJS.enc.Utf8);
console.log(originalText); // 'mysqlpassword'
注意:_secretKey_的长度是16或者16 的倍数。
后端的 AES (golang)
对于golang来说,标准库中就有 AES 加密的算法。
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"errors"
)
const sKey = "len16 secret key"
//pkcs7Padding 填充
func pkcs7Padding(data []byte, blockSize int) []byte {
//判断缺少几位长度。最少1,最多 blockSize
padding := blockSize - len(data)%blockSize
//补足位数。把切片[]byte{byte(padding)}复制padding个
padText := bytes.Repeat([]byte{byte(padding)}, padding)
return append(data, padText...)
}
//pkcs7UnPadding 填充的反向操作
func pkcs7UnPadding(data []byte) ([]byte, error) {
length := len(data)
if length == 0 {
return nil, errors.New("加密字符串错误!")
}
//获取填充的个数
unPadding := int(data[length-1])
return data[:(length - unPadding)], nil
}
//AesEncrypt 加密
func AesEncrypt(data []byte) ([]byte, error) {
key := []byte(sKey)
//创建加密实例
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
//判断加密块的大小
blockSize := block.BlockSize()
//填充
encryptBytes := pkcs7Padding(data, blockSize)
//初始化加密数据接收切片
crypted := make([]byte, len(encryptBytes))
//使用cbc加密模式
blockMode := cipher.NewCBCEncrypter(block, key[:blockSize])
//执行加密
blockMode.CryptBlocks(crypted, encryptBytes)
return crypted, nil
}
//AesDecrypt 解密
func AesDecrypt(data []byte) ([]byte, error) {
key := []byte(sKey)
//创建实例
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
//获取块的大小
blockSize := block.BlockSize()
//使用cbc
blockMode := cipher.NewCBCDecrypter(block, key[:blockSize])
//初始化解密数据接收切片
crypted := make([]byte, len(data))
//执行解密
blockMode.CryptBlocks(crypted, data)
//去除填充
crypted, err = pkcs7UnPadding(crypted)
if err != nil {
return nil, err
}
return crypted, nil
}
测试代码如下:
import (
"encoding/base64"
"testing"
)
func TestCrypto(t *testing.T) {
s := []byte("mysqlpassword")
ss, err := AesEncrypt(s)
if err != nil {
t.Fatal(err)
}
mw := base64.StdEncoding.EncodeToString(ss)
t.Logf("加密后: %s\n", mw)
mwb, _ := base64.StdEncoding.DecodeString(mw)
s, err = AesDecrypt(mwb)
if err != nil {
t.Fatal(err)
}
t.Logf("解密后: %s\n", s)
}
AES 前后端交互
上面的示例,前端和后端分别运行没有问题。
但是,前端加密,后端解密;或者后端加密,前端解密的时候,就会出现问题。
虽然同样用的是 AES 加密算法,上面加密和解密的过程在细节上还有些区别。
- golang在加密过程中使用了 pkcs7 来填充不足的位数,反之在解密时做了反向的填充操作。
- golang使用了CBC模式
补充:AES加密过程中一般有2种模式(ECB和CBC)
- ECB:电码本模式(Electronic Codebook Book)。将整个明文分成若干段相同的小段,然后对每一小段进行加密。
- CBC:密码分组链接模式(Cipher Block Chaining)。先将明文切分成若干小段,然后每一小段与初始块或者上一段的密文段进行异或运算后,再与密钥进行加密。
所以,将前端部分的加解密代码封装,改造如下:
const CryptoJS = require('crypto-js');
const sKey = CryptoJS.enc.Utf8.parse('len16 secret key');
export function EncryptAES(s: string): string {
// key 和 iv 使用同一个值
const encrypted = CryptoJS.AES.encrypt(s, sKey, {
iv: sKey,
mode: CryptoJS.mode.CBC, // CBC算法
padding: CryptoJS.pad.Pkcs7, //使用pkcs7 进行padding 后端需要注意
});
return encrypted.toString();
}
export function DecryptAES(s: string): string {
// key 和 iv 使用同一个值
const decrypted = CryptoJS.AES.decrypt(s, sKey, {
iv: sKey,
mode: CryptoJS.mode.CBC, // CBC算法
padding: CryptoJS.pad.Pkcs7, //使用pkcs7 进行padding 后端需要注意
});
return decrypted.toString(CryptoJS.enc.Utf8);
}
这样改造之后,前端加密之后后端可以解密,后端加密之后,前端也可以解密。
总结
AES算法的使用没有什么难度,但是AES本身在加解密的过程中存在不同的参数。
因此,我们在不同的语言之间进行加解密时,一定要注意保持2种语言的AES算法在使用时,用的是相同的参数。