1. 背景
近期项目在对接第三方产品,传输过程中涉及到数据加密,数据加密流程为:
- 发送数据DES加密
- DES加密后的数据进行base64编码
- 发送,接受数据
- 接受读取的数据进行base64解码
- base64解码完的数据机型DES解密
由于采用golang对接,文档且无说明情况下,默认采用CBC模式加解密,导致很长时间对接不上。
后通过对方Java Demo代码查看得知采用ECB加密模式,Java默认DES算法使用DES/ECB/PKCS5Padding工作方式,在GO语言中因为ECB的脆弱性,DES的ECB模式是故意不放出来的,但实际情况中有时我们并不需要那么安全。
DES介绍
DES 使用一个 56 位的密钥以及附加的 8 位奇偶校验位,产生最大 64 位的分组大小。这是一个迭代的分组密码,使用称为 Feistel 的技术,其中将加密的文本块分成两半。使用子密钥对其中一半应用循环功能,然后将输出与另一半进行"异或"运算;接着交换这两半,这一过程会继续下去,但最后一个循环不交换。DES 使用 16 个循环,使用异或,置换,代换,移位操作四种基本运算。
DES常见加密模式
CBC(加密分组链接模式)
密文分组链接方式,这是golang和.NET封装的DES算法的默认模式,它比较麻烦,加密步骤如下:
- 首先将数据按照8个字节一组进行分组得到D1D2......Dn(若数据不是8的整数倍,就涉及到数据补位了)
- 第一组数据D1与向量I异或后的结果进行DES加密得到第一组密文C1(注意:这里有向量I的说法,ECB模式下没有使用向量I)
- 第二组数据D2与第一组的加密结果C1异或以后的结果进行DES加密,得到第二组密文C2
- 之后的数据以此类推,得到Cn
- 按顺序连为C1C2C3......Cn即为加密结果。
-
数据补位一般有NoPadding和PKCS7Padding(JAVA中是PKCS5Padding)填充方式,PKCS7Padding和PKCS5Padding实际只是协议不一样,根据相关资料说明:PKCS5Padding明确定义了加密块是8字节,PKCS7Padding加密快可以是1-255之间。但是封装的DES算法默认都是8字节,所以可以认为他们一样。数据补位实际是在数据不满8字节的倍数,才补充到8字节的倍数的填充过程。
-
NoPadding填充方式:算法本身不填充,比如.NET的padding提供了有None,Zeros方式,分别为不填充和填充0的方式。
- PKCS7Padding(PKCS5Padding)填充方式:为.NET和JAVA的默认填充方式,对加密数据字节长度对8取余为r,如r大于0,则补8-r个字节,字节为8-r的值;如果r等于0,则补8个字节8。比如:
加密字符串为为AAA,则补位为AAA55555;加密字符串为BBBBBB,则补位为BBBBBB22;加密字符串为CCCCCCCC,则补位为CCCCCCCC88888888。
ECB(电子密码本模式)
电子密本方式,这是JAVA封装的DES算法的默认模式,就是将数据按照8个字节一段进行DES加密或解密得到一段8个字节的密文或者明文,最后一段不足8个字节,则补足8个字节(注意:这里就涉及到数据补位了)进行计算,之后按照顺序将计算所得的数据连在一起即可,各段数据之间互不影响。
CFB(加密反馈模式)
加密反馈模式克服了需要等待8个字节才能加密的缺点,它采用了分组密码作为流密码的密钥流生成器;
OFB(输出反馈模式)
与CFB模式不同之处在于,加密位移寄存器与密文无关了,仅与加密key和加密算法有关;
做法是不再把密文输入到加密移位寄存器,而是把输出的分组密文(Oi)输入到一位寄存器;
DES加密之golang的CBC和ECB模式代码实现
CBC和ECB模式加密
func DesECBEncrypt(data,key []byte)([]byte,error) { block,err := des.NewCipher(key) if err != nil { return nil,err } bs := block.BlockSize() data = PKCS5Padding(data,bs) if len(data)%bs != 0 { return nil,errors.New("Need a multiple of the blocksize") } out := make([]byte,len(data)) dst := out for len(data) > 0 { block.Encrypt(dst,data[:bs]) data = data[bs:] dst = dst[bs:] } return out,nil } func DesCBCEncrypt(origData,key []byte) ([]byte,err } origData = PKCS5Padding(origData,block.BlockSize()) // origData = ZeroPadding(origData,block.BlockSize()) blockMode := cipher.NewCBCEncrypter(block,key) crypted := make([]byte,len(origData)) // 根据CryptBlocks方法的说明,如下方式初始化crypted也可以 // crypted := origData blockMode.CryptBlocks(crypted,origData) return crypted,nil } func PKCS5Padding(ciphertext []byte,blockSize int) []byte { padding := blockSize - len(ciphertext)%blockSize padtext := bytes.Repeat([]byte{byte(padding)},padding) return append(ciphertext,padtext...) }
CBC和ECB模式解密
func DesECBDecrypt(data,err } bs := block.BlockSize() if len(data)%bs != 0 { return nil,errors.New("crypto/cipher: input not full blocks") } out := make([]byte,len(data)) dst := out for len(data) > 0 { block.Decrypt(dst,data[:bs]) data = data[bs:] dst = dst[bs:] } out = PKCS5UnPadding(out) return out,nil } func DesCBCDecrypt(crypted,err } blockMode := cipher.NewCBCDecrypter(block,key) //origData := make([]byte,len(crypted)) origData := crypted blockMode.CryptBlocks(origData,crypted) //origData = PKCS5UnPadding(origData) origData = PKCS5UnPadding(origData) return origData,nil } func PKCS5UnPadding(origData []byte) []byte { length := len(origData) unpadding := int(origData[length-1]) return origData[:(length - unpadding)] }