這種模式是先將明文切分紅若干小段,而後每一小段與初始塊(IV向量)或者上一段的密文段進行異或運算後,再與密鑰進行加密,第一個數據塊進行加密以前須要用初始化向量IV進行異或操做。
(此圖和以上原理參考於網絡)
鄙人在實際開發API服務中用到了cbc模式的加密算法,但測試過程當中卻發現了此算法有安全漏洞,並且比較容易復現!下面是用於復現的測試代碼:
cbc.go(填充模式PKCS5和PKCS7都適用)算法
package cbc import ( "bytes" "crypto/aes" "crypto/cipher" "encoding/base64" "fmt") func encryptCBC(text []byte, aesKey, aesIV string) (encryptData string, err error) { fmt.Println("encryptCBC-Key:", aesKey) block, err := aes.NewCipher([]byte(aesKey)) if err != nil { fmt.Println("encryptCBC NewCipher ERR:", err.Error()) return "", err } blockSize := block.BlockSize() originData := pad(text, blockSize) fmt.Println("encryptCBC-IV:", aesIV) blockMode := cipher.NewCBCEncrypter(block, []byte(aesIV)) encrypt := make([]byte, len(originData)) blockMode.CryptBlocks(encrypt, originData) encryptData = base64.StdEncoding.EncodeToString(encrypt) return } func pad(cipherText []byte, blockSize int) []byte { padding := blockSize - len(cipherText)%blockSize padText := bytes.Repeat([]byte{byte(padding)}, padding) return append(cipherText, padText...) } func decryptCBC(text, aesKey, aesIV string) (decryptData string, err error) { decodeData, err := base64.StdEncoding.DecodeString(text) if err != nil { return "", err } fmt.Println("decryptCBC-Key:", aesKey) block, err := aes.NewCipher([]byte(aesKey)) if err != nil { fmt.Println("decryptCBC Err:", err) return "", err } fmt.Println("decryptCBC-IV:", aesIV) blockMode := cipher.NewCBCDecrypter(block, []byte(aesIV)) originData := make([]byte, len(decodeData)) blockMode.CryptBlocks(originData, decodeData) decryptData = string(unPad(originData)) return } func unPad(cipherText []byte) []byte { length := len(cipherText) unPadding := int(cipherText[length-1]) return cipherText[:(length - unPadding)] }
cbc_test.go安全
package cbc import "testing" func TestEncryptCBC(t *testing.T) { type args struct { text []byte aesKey string aesIV string } tests := []struct { name string args args wantEncryptData string wantErr bool }{ // 和解密的時候用的密鑰和向量同樣 {name: testing.CoverMode(), args: args{text: []byte("Tokyo"), aesKey: "DFA84B10B7ACDD25", aesIV: "DFA84B10B7ACDD25"}, wantEncryptData: "Tokyo", wantErr: false}, // 和解密的時候用的密鑰相同, 向量不一樣, 這個地方產生了安全漏洞, 由於解密方能夠解密成功! {name: testing.CoverMode(), args: args{text: []byte("Tokyo"), aesKey: "DFA84B10B7ACDD25", aesIV: "DFA84B00B7ACDD25"}, wantEncryptData: "Tokyo", wantErr: false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { gotEncryptData, err := encryptCBC(tt.args.text, tt.args.aesKey, tt.args.aesIV) if (err != nil) != tt.wantErr { t.Errorf("encryptCBC() error = %v, wantErr %v", err, tt.wantErr) return } decryptKey, decryptIV := "DFA84B10B7ACDD25", "DFA84B10B7ACDD25" decryptData, err := decryptCBC(gotEncryptData, decryptKey, decryptIV) if decryptData != tt.wantEncryptData { t.Errorf("encryptCBC() gotEncryptData = %v, want %v", decryptData, tt.wantEncryptData) } else { t.Logf("Successfully, encryptCBC() gotEncryptData = %v, want %v", decryptData, tt.wantEncryptData) } }) } } func TestDecryptCBC(t *testing.T) { encryptKey, encryptIV := "DFA84B10B7ACDD25", "DFA84B10B7ACDD25" gotEncryptData, err := encryptCBC([]byte("Tokyo"), encryptKey, encryptIV) if err != nil { t.Fatal("encryptCBC ERR:", err) } type args struct { text string aesKey string aesIV string } tests := []struct { name string args args wantDecryptData string wantErr bool }{ // 和加密時候用的密鑰和向量同樣 {name: testing.CoverMode(), args: args{text: gotEncryptData, aesKey: "DFA84B10B7ACDD25", aesIV: "DFA84B10B7ACDD25"}, wantDecryptData: "Tokyo", wantErr: false}, // 和加密時候用的密鑰同樣, 但向量不一樣, 這個地方產生了安全漏洞, 向量不一樣卻能夠解密成功! {name: testing.CoverMode(), args: args{text: gotEncryptData, aesKey: "DFA84B10B7ACDD25", aesIV: "DFA84B00B7ACDD25"}, wantDecryptData: "Tokyo", wantErr: false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { gotDecryptData, err := decryptCBC(tt.args.text, tt.args.aesKey, tt.args.aesIV) if (err != nil) != tt.wantErr { t.Errorf("decryptCBC() error = %v, wantErr %v", err, tt.wantErr) return } if gotDecryptData != tt.wantDecryptData { t.Errorf("decryptCBC() gotDecryptData = %v, want %v", gotDecryptData, tt.wantDecryptData) } else { t.Logf("Successfully, decryptCBC() gotDecryptData = %v, want %v", gotDecryptData, tt.wantDecryptData) } }) } }
測試結果以下:網絡
1)以解密方的密鑰和向量爲正確的標準,加密方的密鑰正確,但向量不正確
2)以加密方的密鑰和向量爲正確的標準,解密方的密鑰正確,但向量不正確
以上兩種異常狀況,使用CBC模式均可以獲得正確的解密後的結果,還有其餘更多的異常狀況等,在這裏再也不敘述,這不是go語言的缺陷,而是AES算法自己的缺陷,其餘諸如Java,Python等都有此漏洞,也沒法經過語言層面來改進這個漏洞,因此使用AES算法要多加當心!app
問題出在異或加密這裏,在講解字節反轉攻擊前先了解下異或加密。 異或 xor 符號表示爲 ^ ,計算機中 兩個數字異或,相同爲0,不一樣爲1。 1^1=0 0^1=1 若是是字母異或加密,a^b,那麼首先轉化爲ascii編碼,而後二進制,對每一位進行異或獲得的結果轉爲十進制,在ascii編碼出來。
異或有一個特性,任意值與本身自己作異或運算的結果都是0,任意值與0作異或運算的結果都是本身。自己a^b=亂七八糟,a^a則爲空,可是a^a^任意字母=任意字母。測試
在CBC解密中,如圖A是第一組的密文,B是第二組被解密的密文(未異或),C是明文。C=A^B。那麼B=C^A,且A^B^C=0。若是咱們更改A,A爲咱們可控的密文,C=A^B,若是咱們使A=B^X,B=C^A,因此A=C^A^X,C=C^A^X^B=B^X^B=X。這裏X是咱們須要的任意字符,這即是CBC字節反轉攻擊的核心,這樣一來C的明文就徹底可控了。
(此圖和以上方法參考於網絡)編碼