1.介紹
高級(jí)加密標(biāo)準(zhǔn)(英語:Advanced Encryption Standard茧泪,縮寫:AES),又稱Rijndael加密法(荷蘭語發(fā)音:[?r?inda?l],音似英文的“Rhine doll”),是美國(guó)聯(lián)邦政府采用的一種區(qū)塊加密標(biāo)準(zhǔn)训桶。這個(gè)標(biāo)準(zhǔn)用來替代原先的DES躏精,已經(jīng)被多方分析且廣為全世界所使用。經(jīng)過五年的甄選流程苦锨,高級(jí)加密標(biāo)準(zhǔn)由美國(guó)國(guó)家標(biāo)準(zhǔn)與技術(shù)研究院(NIST)于2001年11月26日發(fā)布于FIPS PUB 197,并在2002年5月26日成為有效的標(biāo)準(zhǔn)∨棵冢現(xiàn)在舟舒,高級(jí)加密標(biāo)準(zhǔn)已然成為對(duì)稱密鑰加密中最流行的[算法之一。
該算法為比利時(shí)密碼學(xué)家Joan Daemen和Vincent Rijmen所設(shè)計(jì)嗜憔,結(jié)合兩位作者的名字秃励,以Rijndael為名投稿高級(jí)加密標(biāo)準(zhǔn)的甄選流程。
2. 電碼本模式(ECB
)
2.1 加密
a.代碼
// 加密
func AesEncryptByECB(data, key string) string {
// 判斷key長(zhǎng)度
keyLenMap := map[int]struct{}{16: {}, 24: {}, 32: {}}
if _,ok := keyLenMap[len(key)]; !ok {
panic("key長(zhǎng)度必須是 16吉捶、24夺鲜、32 其中一個(gè)")
}
// 密鑰和待加密數(shù)據(jù)轉(zhuǎn)成[]byte
originByte := []byte(data)
keyByte := []byte(key)
// 創(chuàng)建密碼組,長(zhǎng)度只能是16呐舔、24币励、32字節(jié)
block, _ := aes.NewCipher(keyByte)
// 獲取密鑰長(zhǎng)度
blockSize := block.BlockSize()
// 補(bǔ)碼
originByte = PKCS7Padding(originByte, blockSize)
// 創(chuàng)建保存加密變量
encryptResult := make([]byte, len(originByte))
// CEB是把整個(gè)明文分成若干段相同的小段,然后對(duì)每一小段進(jìn)行加密
for bs, be := 0, blockSize; bs < len(originByte); bs, be = bs+blockSize, be+blockSize {
block.Encrypt(encryptResult[bs:be], originByte[bs:be])
}
return base64.StdEncoding.EncodeToString(encryptResult)
}
// 補(bǔ)碼
func PKCS7Padding(originByte []byte, blockSize int) []byte {
// 計(jì)算補(bǔ)碼長(zhǎng)度
padding := blockSize - len(originByte)%blockSize
// 生成補(bǔ)碼
padText := bytes.Repeat([]byte{byte(padding)}, padding)
// 追加補(bǔ)碼
return append(originByte, padText...)
}
b.測(cè)試
package crypto
import (
"52lu/go-study-example/package/crypto"
"fmt"
"strings"
"testing"
)
// 加密
func TestECBEncrypt(t *testing.T) {
key := strings.Repeat("a", 16)
data := "hello word"
s := crypto.AesEncryptByECB(data, key)
fmt.Printf("加密密鑰: %v \n", key)
fmt.Printf("加密數(shù)據(jù): %v \n", data)
fmt.Printf("加密結(jié)果: %v \n", s)
}
/** 輸出
=== RUN TestECBEncrypt
加密密鑰: aaaaaaaaaaaaaaaa
加密數(shù)據(jù): hello word
加密結(jié)果: mMAsLF/fPBfUrP0mPqZm1w==
--- PASS: TestECBEncrypt (0.00s)
PASS
*/
2.2 解密
a.代碼
// 解密
func AesDecryptByECB(data, key string) string {
// 判斷key長(zhǎng)度
keyLenMap := map[int]struct{}{16: {}, 24: {}, 32: {}}
if _,ok := keyLenMap[len(key)]; !ok {
panic("key長(zhǎng)度必須是 16珊拼、24食呻、32 其中一個(gè)")
}
// 反解密碼base64
originByte, _ := base64.StdEncoding.DecodeString(data)
// 密鑰和待加密數(shù)據(jù)轉(zhuǎn)成[]byte
keyByte := []byte(key)
// 創(chuàng)建密碼組,長(zhǎng)度只能是16、24仅胞、32字節(jié)
block, _ := aes.NewCipher(keyByte)
// 獲取密鑰長(zhǎng)度
blockSize := block.BlockSize()
// 創(chuàng)建保存解密變量
decrypted := make([]byte, len(originByte))
for bs, be := 0, blockSize; bs < len(originByte); bs, be = bs+blockSize, be+blockSize {
block.Decrypt(decrypted[bs:be], originByte[bs:be])
}
// 解碼
return string(PKCS7UNPadding(decrypted))
}
// 解碼
func PKCS7UNPadding(originDataByte []byte) []byte {
length := len(originDataByte)
unpadding := int(originDataByte[length-1])
return originDataByte[:(length-unpadding)]
}
b.測(cè)試
// 解密
func TestECBDecrypt(t *testing.T) {
key := strings.Repeat("a", 16)
data := "mMAsLF/fPBfUrP0mPqZm1w=="
s := crypto.AesDecryptByECB(data, key)
fmt.Printf("解密密鑰: %v \n", key)
fmt.Printf("解密數(shù)據(jù): %v \n", data)
fmt.Printf("解密結(jié)果: %v \n", s)
}
/** 輸出
=== RUN TestECBDecrypt
解密密鑰: aaaaaaaaaaaaaaaa
解密數(shù)據(jù): mMAsLF/fPBfUrP0mPqZm1w==
解密結(jié)果: hello word
--- PASS: TestECBDecrypt (0.00s)
PASS
*/
3. 密碼分組鏈模式(CBC
)
3.1 加密
a.代碼
// AES加密
func AesEncryptByCBC(str, key string) string {
// 判斷key長(zhǎng)度
keyLenMap := map[int]struct{}{16: {}, 24: {}, 32: {}}
if _,ok := keyLenMap[len(key)]; !ok {
panic("key長(zhǎng)度必須是 16每辟、24、32 其中一個(gè)")
}
// 待加密字符串轉(zhuǎn)成byte
originDataByte := []byte(str)
// 秘鑰轉(zhuǎn)成[]byte
keyByte := []byte(key)
// 創(chuàng)建一個(gè)cipher.Block接口干旧。參數(shù)key為密鑰渠欺,長(zhǎng)度只能是16、24莱革、32字節(jié)
block, _ := aes.NewCipher(keyByte)
// 獲取秘鑰長(zhǎng)度
blockSize := block.BlockSize()
// 補(bǔ)碼填充
originDataByte = PKCS7Padding(originDataByte, blockSize)
// 選用加密模式
blockMode := cipher.NewCBCEncrypter(block, keyByte[:blockSize])
// 創(chuàng)建數(shù)組峻堰,存儲(chǔ)加密結(jié)果
encrypted := make([]byte, len(originDataByte))
// 加密
blockMode.CryptBlocks(encrypted, originDataByte)
// []byte轉(zhuǎn)成base64
return base64.StdEncoding.EncodeToString(encrypted)
}
// 補(bǔ)碼
func PKCS7Padding(originByte []byte, blockSize int) []byte {
// 計(jì)算補(bǔ)碼長(zhǎng)度
padding := blockSize - len(originByte)%blockSize
// 生成補(bǔ)碼
padText := bytes.Repeat([]byte{byte(padding)}, padding)
// 追加補(bǔ)碼
return append(originByte, padText...)
}
b.測(cè)試
package crypto
import (
"52lu/go-study-example/package/crypto"
"fmt"
"strings"
"testing"
)
// AES加密
func TestAesEncryptByCBC(t *testing.T) {
key := strings.Repeat("a", 16)
fmt.Printf("key: %v 長(zhǎng)度: %d \n", key, len(key))
text := "abc"
fmt.Printf("帶加密文案: %v \n", text)
encrypt := crypto.AesEncryptByCBC(text, key)
fmt.Printf("加密結(jié)果: %v \n", encrypt)
}
/** 輸出
=== RUN TestAesEncryptByCBC
key: aaaaaaaaaaaaaaaa 長(zhǎng)度: 16
帶加密文案: abc
加密結(jié)果: rMX6r9x+PnTOhfgDH4jjXg==
--- PASS: TestAesEncryptByCBC (0.00s)
PASS
*/
3.2 解密
a.代碼
// 解密
func AesDecryptByCBC(encrypted,key string) string {
// 判斷key長(zhǎng)度
keyLenMap := map[int]struct{}{16: {}, 24: {}, 32: {}}
if _,ok := keyLenMap[len(key)]; !ok {
panic("key長(zhǎng)度必須是 16、24盅视、32 其中一個(gè)")
}
// encrypted密文反解base64
decodeString, _ := base64.StdEncoding.DecodeString(encrypted)
// key 轉(zhuǎn)[]byte
keyByte := []byte(key)
// 創(chuàng)建一個(gè)cipher.Block接口捐名。參數(shù)key為密鑰,長(zhǎng)度只能是16闹击、24镶蹋、32字節(jié)
block, _ := aes.NewCipher(keyByte)
// 獲取秘鑰塊的長(zhǎng)度
blockSize := block.BlockSize()
// 選擇加密模式
blockMode := cipher.NewCBCDecrypter(block, keyByte[:blockSize])
// 創(chuàng)建數(shù)組,存儲(chǔ)解密結(jié)果
decodeResult := make([]byte, blockSize)
// 解密
blockMode.CryptBlocks(decodeResult,decodeString)
// 解碼
padding := PKCS7UNPadding(decodeResult)
return string(padding)
}
// 解碼
func PKCS7UNPadding(originDataByte []byte) []byte {
length := len(originDataByte)
unpadding := int(originDataByte[length-1])
return originDataByte[:(length-unpadding)]
}
b.測(cè)試
package crypto
import (
"52lu/go-study-example/package/crypto"
"fmt"
"strings"
"testing"
)
// AES解密
func TestAesDecryptByCBC(t *testing.T) {
key := strings.Repeat("a", 16)
fmt.Printf("key: %v 長(zhǎng)度: %d \n", key, len(key))
text := "rMX6r9x+PnTOhfgDH4jjXg=="
fmt.Printf("待解密文案: %v \n", text)
decrypt := crypto.AesDecryptByCBC(text, key)
fmt.Printf("解密結(jié)果: %v \n", decrypt)
}
/** 輸出
=== RUN TestAesDecryptByCBC
key: aaaaaaaaaaaaaaaa 長(zhǎng)度: 16
待解密文案: rMX6r9x+PnTOhfgDH4jjXg==
解密結(jié)果: abc
--- PASS: TestAesDecryptByCBC (0.00s)
PASS
*/
4. 計(jì)算器模式(CTR
)
4.1 加密
a.代碼
// 加密,分別返回 hex格式和base64 結(jié)果
func AesEncryptByCTR(data, key string) (string,string) {
// 判斷key長(zhǎng)度
keyLenMap := map[int]struct{}{16: {}, 24: {}, 32: {}}
if _,ok := keyLenMap[len(key)]; !ok {
panic("key長(zhǎng)度必須是 16赏半、24贺归、32 其中一個(gè)")
}
// 轉(zhuǎn)成byte
dataByte := []byte(data)
keyByte := []byte(key)
// 創(chuàng)建block
block, err := aes.NewCipher(keyByte)
if err != nil {
panic(fmt.Sprintf("NewCipher error:%s",err))
}
blockSize := block.BlockSize()
// 創(chuàng)建偏移量iv,取秘鑰前16個(gè)字符
iv := []byte(key[:blockSize])
// 補(bǔ)碼
padding := PKCS7Padding(dataByte, blockSize)
// 加密模式
stream := cipher.NewCTR(block, iv)
// 定義保存結(jié)果變量
out := make([]byte,len(padding))
stream.XORKeyStream(out,padding)
// 處理加密結(jié)果
hexRes := fmt.Sprintf("%x",out)
base64Res := base64.StdEncoding.EncodeToString(out)
return hexRes,base64Res
}
// 補(bǔ)碼
func PKCS7Padding(originByte []byte, blockSize int) []byte {
// 計(jì)算補(bǔ)碼長(zhǎng)度
padding := blockSize - len(originByte)%blockSize
// 生成補(bǔ)碼
padText := bytes.Repeat([]byte{byte(padding)}, padding)
// 追加補(bǔ)碼
return append(originByte, padText...)
}
b.測(cè)試
package crypto
import (
"52lu/go-study-example/package/crypto"
"fmt"
"strings"
"testing"
)
// 測(cè)試AES-CTR加密
func TestAesEncryptByCTR(t *testing.T) {
key := strings.Repeat("a", 16)
data := "hello word"
hex, base64 := crypto.AesEncryptByCTR(data, key)
fmt.Printf("加密key: %v \n", key)
fmt.Printf("加密key長(zhǎng)度: %v \n", len(key))
fmt.Printf("加密數(shù)據(jù): %v \n", data)
fmt.Printf("加密結(jié)果(hex): %v \n", hex)
fmt.Printf("加密結(jié)果(base64): %v \n", base64)
}
/** 輸出
=== RUN TestAesEncryptByCTR
加密key: aaaaaaaaaaaaaaaa
加密key長(zhǎng)度: 16
加密數(shù)據(jù): hello word
加密結(jié)果(hex): 39edaa2b2402fbd2a026ef1458b81b55
加密結(jié)果(base64): Oe2qKyQC+9KgJu8UWLgbVQ==
--- PASS: TestAesEncryptByCTR (0.00s)
PASS
*/
4.2 解密
a.代碼
// 解密
func AesDecryptByCTR(dataBase64,key string) string {
// 判斷key長(zhǎng)度
keyLenMap := map[int]struct{}{16: {}, 24: {}, 32: {}}
if _,ok := keyLenMap[len(key)]; !ok {
panic("key長(zhǎng)度必須是 16、24断箫、32 其中一個(gè)")
}
// dataBase64轉(zhuǎn)成[]byte
decodeStringByte, err := base64.StdEncoding.DecodeString(dataBase64)
if err != nil {
panic(fmt.Sprintf("base64 DecodeString error: %s",err))
}
// 創(chuàng)建block
block, err := aes.NewCipher([]byte(key))
if err != nil {
panic(fmt.Sprintf("NewCipher error: %s",err))
}
blockSize := block.BlockSize()
// 創(chuàng)建偏移量iv,取秘鑰前16個(gè)字符
iv := []byte(key[:blockSize])
// 創(chuàng)建Stream
stream := cipher.NewCTR(block, iv)
// 聲明變量
out := make([]byte,len(decodeStringByte))
// 解密
stream.XORKeyStream(out,decodeStringByte)
// 解密加密結(jié)果并返回
return string(PKCS7UNPadding(out))
}
b.測(cè)試
package crypto
import (
"52lu/go-study-example/package/crypto"
"fmt"
"strings"
"testing"
)
// 測(cè)試AES-CTR解密
func TestAesDecryptByCTR(t *testing.T) {
key := strings.Repeat("a", 16)
data := "Oe2qKyQC+9KgJu8UWLgbVQ=="
res := crypto.AesDecryptByCTR(data, key)
fmt.Printf("解密key: %v \n", key)
fmt.Printf("解密數(shù)據(jù): %v \n", data)
fmt.Printf("解密結(jié)果: %v \n", res)
}
/** 輸出
=== RUN TestAesDecryptByCTR
解密key: aaaaaaaaaaaaaaaa
解密數(shù)據(jù): Oe2qKyQC+9KgJu8UWLgbVQ==
解密結(jié)果: hello word
--- PASS: TestAesDecryptByCTR (0.00s)
PASS
*/
5. CFB拂酣、OFB
和CTR
模式一樣,只需要修改加密模式即可,查看具體源碼 https://github.com/52lu/go-study-example
// CFB
...
stream := cipher.NewCFBDecrypter(block, iv)
...
// OFB
...
stream := cipher.NewOFB(block, iv)
...
不理解的點(diǎn): 在學(xué)習(xí)使用中,發(fā)現(xiàn)CFB/OFB/CTR 在加密很短的字符串時(shí)仲义,發(fā)現(xiàn)加密結(jié)果一致婶熬。
func TestAesEncryptByOFB(t *testing.T) {
key := strings.Repeat("a", 16)
data := "123"
_, base64 := crypto.AesEncryptByOFB(data, key)
_, base642 := crypto.AesEncryptByCTR(data, key)
_, base643 := crypto.AesEncryptByCFB(data, key)
fmt.Printf("加密key: %v \n", key)
fmt.Printf("加密key長(zhǎng)度: %v \n", len(key))
fmt.Printf("加密數(shù)據(jù): %v \n", data)
fmt.Printf("加密結(jié)果(OFB): %v \n", base64)
fmt.Printf("加密結(jié)果(CTR): %v \n", base642)
fmt.Printf("加密結(jié)果(CFB): %v \n", base643)
}
/** 輸出
=== RUN TestAesEncrypt
加密key: aaaaaaaaaaaaaaaa
加密key長(zhǎng)度: 16
加密數(shù)據(jù): 123
加密結(jié)果(OFB): YLr1SkYvgbDfT+QfU7MQXg==
加密結(jié)果(CTR): YLr1SkYvgbDfT+QfU7MQXg==
加密結(jié)果(CFB): YLr1SkYvgbDfT+QfU7MQXg==
--- PASS: TestAesEncrypt (0.00s)
PASS
6.AES-GCM
GCM
全稱為Galois/Counter Mode
,可以看出 G
是指 GMAC
埃撵,C
是指 CTR
赵颅。它在 CTR
加密的基礎(chǔ)上增加 GMAC
的特性,解決了 CTR
不能對(duì)加密消息進(jìn)行完整性校驗(yàn)的問題暂刘。
6.1 加密
// 加密(GCM 不需要補(bǔ)碼)
func AesEncryptByGCM(data, key string) string {
block, err := aes.NewCipher([]byte(key))
if err != nil {
panic(fmt.Sprintf("NewCipher error:%s", err))
}
gcm, err := cipher.NewGCM(block)
if err != nil {
panic(fmt.Sprintf("NewGCM error:%s", err))
}
// 生成隨機(jī)因子(這里固定取密鑰指定位數(shù))
//nonce := make([]byte, gcm.NonceSize())
//if _,err := io.ReadFull(rand.Reader,nonce); err != nil {
// panic(fmt.Sprintf("make rand nonce error:%s", err))
//}
nonceStr := key[:gcm.NonceSize()]
nonce := []byte(nonceStr)
fmt.Printf("nonceStr = %v \n", nonceStr)
seal := gcm.Seal(nonce, nonce, []byte(data), nil)
return base64.StdEncoding.EncodeToString(seal)
}
6.2 解密
// 解密(GCM 不需要解碼)
func AesDecryptByGCM(data, key string) string {
// 反解base64
dataByte,err := base64.StdEncoding.DecodeString(data)
if err != nil {
panic(fmt.Sprintf("base64 DecodeString error:%s", err))
}
block, err := aes.NewCipher([]byte(key))
if err != nil {
panic(fmt.Sprintf("NewCipher error:%s", err))
}
gcm, err := cipher.NewGCM(block)
if err != nil {
panic(fmt.Sprintf("NewGCM error:%s", err))
}
nonceSize := gcm.NonceSize()
if len(dataByte) < nonceSize {
panic("dataByte to short")
}
nonce, ciphertext := dataByte[:nonceSize], dataByte[nonceSize:]
open, err := gcm.Open(nil, nonce, ciphertext, nil)
if err != nil {
panic(fmt.Sprintf("gcm Open error:%s", err))
}
return string(open)
}
6.3 測(cè)試
package crypto
import (
"52lu/go-study-example/package/crypto"
"fmt"
"strings"
"testing"
)
func TestAesGCM(t *testing.T) {
key := strings.Repeat("a",16)
data := "hello word!"
// 加密
gcm := crypto.AesEncryptByGCM(data, key)
fmt.Printf("密鑰key: %s \n",key)
fmt.Printf("加密數(shù)據(jù): %s \n",data)
fmt.Printf("加密結(jié)果: %s \n",gcm)
// 解密
byGCM := crypto.AesDecryptByGCM(gcm, key)
fmt.Printf("解密結(jié)果: %s \n",byGCM)
}
/** 輸出
=== RUN TestAesGCM
nonceStr = aaaaaaaaaaaa
密鑰key: aaaaaaaaaaaaaaaa
加密數(shù)據(jù): hello word!
加密結(jié)果: YWFhYWFhYWFhYWFhhi5dsHDfOdUFfno08BMWWI4iESBd0CF6zE9C
解密結(jié)果: hello word!
--- PASS: TestAesGCM (0.00s)
PASS
*/