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, error) {
block, err := des.NewCipher(key)
if err != nil {
return nil, 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, key []byte)([]byte, error) {
block, err := des.NewCipher(key)
if err != nil {
return nil, 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, key []byte) ([]byte, error) {
block, err := des.NewCipher(key)
if err != nil {
return nil, 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)]
}
总结
本文转自asd1123509133 51CTO博客,原文链接:
http://blog.51cto.com/lisea/2065657,如需转载请自行联系原作者