有關AES的CBC模式加解密安全漏洞的實踐

(1)CBC模式的原理

這種模式是先將明文切分紅若干小段,而後每一小段與初始塊(IV向量)或者上一段的密文段進行異或運算後,再與密鑰進行加密,第一個數據塊進行加密以前須要用初始化向量IV進行異或操做。

image

(此圖和以上原理參考於網絡)

(2)漏洞復現

鄙人在實際開發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)以解密方的密鑰和向量爲正確的標準,加密方的密鑰正確,但向量不正確
image
2)以加密方的密鑰和向量爲正確的標準,解密方的密鑰正確,但向量不正確
image
以上兩種異常狀況,使用CBC模式均可以獲得正確的解密後的結果,還有其餘更多的異常狀況等,在這裏再也不敘述,這不是go語言的缺陷,而是AES算法自己的缺陷,其餘諸如Java,Python等都有此漏洞,也沒法經過語言層面來改進這個漏洞,因此使用AES算法要多加當心!app

(3)CBC字節反轉攻擊法(經常使用的破解cbc模式的攻擊方法)

問題出在異或加密這裏,在講解字節反轉攻擊前先了解下異或加密。
異或 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的明文就徹底可控了。

image
(此圖和以上方法參考於網絡)編碼

相關文章
相關標籤/搜索