什么是CBC模式
CBC模式的全称是:Cipher Block Chaining模式(密文分组链接模式)。 在CBC模式中,首先将明文分组与前一个密文分组进行XOR运算,然后再进行加密。
CBC模式的加解密
基于CBC的数据块的加密和解密迭代过程如上图所示,每一个数据块的加密和解密过程都依赖上一个数据块。一旦有一个数据块出现错误将会出现“雪崩效应”。
初始化向量
当加密第一个明文分组时,由于不存在“前一个密文分组”,因此需要事先准备一个长度为一个分组的比特序列来代替“前一个密文分组”,这个比特序列称为初始化向量(Initialization Vector),通常缩写为IV,一般来说,每次加密时都会随机产生一个不同的比特序列来作为初始化向量。
Feistel网络
如上图所示Feistel网络实现对于单个数据块的加密。Feistel迭代开始前将64bit数据块拆分为左右32比bit,然后进行如上图所示的迭代过程,总共迭代16次。每一次迭代的子密钥是不同的。每次迭代过程都是对右半部分数据块采用轮函数处理(加密)。
子密钥的生成
子密钥的生成如上图所示,用户输入的是64bit的密钥(8个字符)首先做一次ip置换将64bit的密钥置换为56bit的密钥。56bit的密钥再进行一次PC-1置换后拆分为左右28bit的密钥。进行16轮迭代,产生16个子密钥。每次迭代将左右28bit密钥做左移1位的运算,然后再进行 PC-2的置换,组合再一起后得到ki。
子轮函数的实现
轮函数的实现主要是进行了 ebox的置换处理和sbox的置换处理:
- ebox 将32bit 的R block 通过扩展置换为48bit的R block,然后与当前迭代的子密钥Ki做XOR 运算,最后拆分为6*8的矩阵。
- sbox 取 R block每一行的6个bit做运算,得到 sbox的坐标,取到sbox的值后做位移运算得到加密后的新的R block的行,迭代8此后得到最后的加密结果。
总体来说子密钥的生成的实现逻辑和轮函数的实现逻辑较为复杂具体可以参考我的代码实现。
Go语言实现
代码语言:javascript复制package main
import (
"bytes"
"crypto/cipher"
"crypto/des"
"encoding/hex"
"fmt"
)
func main() {
//明文
src := []byte("ReganYue")
//秘钥
key := []byte("87654321")
//加密
encrypt_msg := EncryptDES(src, key)
fmt.Println("encrypt_msg = ", hex.EncodeToString(encrypt_msg))
//解密
decrypt_msg := DecryptDES(encrypt_msg, key)
fmt.Println("decrypt_msg = ", string(decrypt_msg))
}
//加密
func EncryptDES(src, key []byte) []byte {
//通过秘钥创建加密块
block, err := des.NewCipher(key)
if err != nil {
panic(err)
}
//获取每个块的大小
length := block.BlockSize()
//对明文进行填充
//src = utils.PaddingText(src, length)
src = ZeroPadding(src, length)
iv := []byte("12345678")
//创建CBC加密模式
blockMode := cipher.NewCBCEncrypter(block, iv)
dst := make([]byte, len(src))
blockMode.CryptBlocks(dst, src)
return dst
}
//解密
func DecryptDES(src, key []byte) []byte {
block, err := des.NewCipher(key)
if err != nil {
panic(err)
}
iv := []byte("12345678")
//创建CBC解密模式
blockMode := cipher.NewCBCDecrypter(block, iv)
dst := make([]byte, len(src))
//解密
blockMode.CryptBlocks(dst, src)
//return utils.UnPaddingText(dst)
return ZeroUnPadding(dst)
}
//填充最后一个分组
//src:待填充的明文
//blockSize:分组大小
func PaddingText(src []byte, blockSize int) []byte {
//求出最后一个分组需要填充的字节数
padding := blockSize - len(src)%blockSize//3
// [3,3,3]
padText := bytes.Repeat([]byte{byte(padding)}, padding)
//将填充数据拼接到明文后面
nextText := append(src, padText...)
return nextText
}
//去除尾部填充数据
func UnPaddingText(src []byte) []byte {
len := len(src)
number := int(src[len-1])
newText := src[:len-number]
return newText
}
//ciphertext:待填充的明文 blocksize:每个块的大小
func ZeroPadding(ciphertext []byte, blocksize int) []byte {
//计算需要填充几个字节
padding := blocksize - len(ciphertext)%blocksize
padtext := bytes.Repeat([]byte{0}, padding)
return append(ciphertext, padtext...)
}
//去除尾部填充数据
func ZeroUnPadding(origdata []byte) []byte {
return bytes.TrimRightFunc(origdata, func (r rune) bool {
return r == rune(0)
})
}
/*
加密之后: 0101
明文分组: 1011
1110
1110
0101
1011
*/