(1)CBC模式的原理

       这种模式是先将明文切分成若干小段,然后每一小段与初始块(IV向量)或者上一段的密文段进行异或运算后,再与密钥进行加密,第一个数据块进行加密之前需要用初始化向量IV进行异或操作。  

aes C标准代码 aescbc_密码学

(此图和以上原理参考于网络)

(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)以解密方的密钥和向量为正确的标准,加密方的密钥正确,但向量不正确

aes C标准代码 aescbc_算法_02

2)以加密方的密钥和向量为正确的标准,解密方的密钥正确,但向量不正确

aes C标准代码 aescbc_aes C标准代码_03

以上两种异常情况,使用CBC模式都可以得到正确的解密后的结果,还有其他更多的异常情况等,在这里不再叙述,这不是go语言的缺陷,而是AES算法本身的缺陷,其他诸如Java,Python等都有此漏洞,也无法通过语言层面来改进这个漏洞,所以使用AES算法要多加小心!

(3)CBC字节反转攻击法(常用的破解cbc模式的攻击方法)

      问题出在异或加密这里,在讲解字节反转攻击前先了解下异或加密。异或 xor 符号表示为 ^ ,计算机中 两个数字异或,相同为0,不同为1。 1^1=0 0^1=1 如果是字母异或加密,a^b,那么首先转化为ascii编码,然后二进制,对每一位进行异或得到的结果转为十进制,在ascii编码出来。

      异或有一个特性,任意值与自己本身做异或运算的结果都是0,任意值与0做异或运算的结果都是自己。本身a^b=乱七八糟,a^a则为空,但是a^a^任意字母=任意字母。

aes C标准代码 aescbc_加密解密_04