AES(Advanced Encryption Standard) 是一種對稱加密演算法,是比 DES 更好的對稱加密演算法類。 使用AES,在前後端之間傳送密碼等相關數據時,能簡單高效的提高安全性。 前端的 AES(javascript/typescript) 前端的加密庫一般用 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演算法在使用時,用的是相同的參數。