Go加密解密之DES
接着RSA加密解密,我们继续来看看DES的加密解密
一、DES简介
DES(Data Encryption Standard)是对称加密算法,也就是加密和解密用相同的密钥。其入口参数有三个:key、data、mode。key为加密解密使用的密钥,data为加密解密的数据,mode为其工作模式。当模式为加密模式时,明文按照64位进行分组,形成明文组,key用于对数据加密,当模式为解密模式时,key用于对数据解密。实际运用中,密钥只用到了64位中的56位,这样才具有高的安全性。DES 的常见变体是三重 DES,使用 168 位的密钥对资料进行三次加密的一种机制;它通常(但非始终)提供极其强大的安全性。如果三个 56 位的子元素都相同,则三重 DES 向后兼容 DES。
DES加密,涉及到加密模式和填充方式,所以,和其他语言加解密时,应该约定好加密模式和填充方式。(模式定义了Cipher如何应用加密算法。改变模式可以容许一个块加密程序变为流加密程序。)
关于分组加密:分组密码每次加密一个数据分组,这个分组的位数可以是随意的,一般选择64或者128位。另一方面,流加密程序每次可以加密或解密一个字节的数据,这就使它比流加密的应用程序更为有用。
在用DES加密解密时,经常会涉及到一个概念:块(block,也叫分组),模式(比如cbc),初始向量(iv),填充方式(padding,包括none,用’\0’填充,pkcs5padding或pkcs7padding)。多语言加密解密交互时,需要确定好这些。比如这么定:
采用3DES、CBC模式、pkcs5padding,初始向量用key充当;另外,对于zero padding,还得约定好,对于数据长度刚好是block size的整数倍时,是否需要额外填充。
二、Go DES加密解密
1、crypto/des包
Go中crypto/des包实现了 Data Encryption Standard (DES) and the Triple Data Encryption Algorithm (TDEA)。查看该包文档,发现相当简单:
定义了DES块大小(8bytes),定义了一个KeySizeError。另外定义了两个我们需要特别关注的函数,即
func NewCipher(key []byte) (cipher.Block, error) func NewTripleDESCipher(key []byte) (cipher.Block, error)
他们都是用来获得一个cipher.Block。从名字可以很容易知道,DES使用NewCipher,3DES使用NewTripleDESCipher。参数都是密钥(key)
2、crypto/cipher包
那么,cipher这个包是干嘛用的呢?它实现了标准的块加密模式。我们看一下cipher.Block
type Block interface { // BlockSize returns the cipher's block size. BlockSize() int // Encrypt encrypts the first block in src into dst. // Dst and src may point at the same memory. Encrypt(dst, src []byte) // Decrypt decrypts the first block in src into dst. // Dst and src may point at the same memory. Decrypt(dst, src []byte) }
这是一个接口
对称加密,按块方式,我们经常见到CBC、ECB之类的,这些是加密模式。可以参考:DES加密模式详解 http://linux.bokee.com/6956594.html
Go中定义了一个接口BlockMode代表各种模式
type BlockMode interface { // BlockSize returns the mode's block size. BlockSize() int // CryptBlocks encrypts or decrypts a number of blocks. The length of // src must be a multiple of the block size. Dst and src may point to // the same memory. CryptBlocks(dst, src []byte) }
该包还提供了获取BlockMode实例的两个方法
func NewCBCDecrypter(b Block, iv []byte) BlockMode func NewCBCEncrypter(b Block, iv []byte) BlockMode
即一个CBC加密,一个CBC解密
对于按流方式加密的,定义了一个接口:
type Stream interface { // XORKeyStream XORs each byte in the given slice with a byte from the // cipher's key stream. Dst and src may point to the same memory. XORKeyStream(dst, src []byte) }
同样也提供了获取实现该接口的实例
这里,我们只讨论CBC模式
3、加密解密
1)DES
DES加密代码如下:
func DesEncrypt(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 }
以上代码使用DES加密(des.NewCipher),加密模式为CBC(cipher.NewCBCEncrypter(block, key)),填充方式PKCS5Padding,该函数的代码如下:
func PKCS5Padding(ciphertext []byte, blockSize int) []byte { padding := blockSize - len(ciphertext)%blockSize padtext := bytes.Repeat([]byte{byte(padding)}, padding) return append(ciphertext, padtext...) }
可见,数据长度刚好是block size的整数倍时,也进行了填充,如果不进行填充,unpadding会搞不定。
另外,为了方便,初始向量直接使用key充当了(实际项目中,最好别这么做)。
DES解密代码如下:
func DesDecrypt(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 = ZeroUnPadding(origData) return origData, nil }
可见,解密无非是调用cipher.NewCBCDecrypter,最后unpadding,其他跟加密几乎一样。相应的PKCS5UnPadding:
func PKCS5UnPadding(origData []byte) []byte { length := len(origData) // 去掉最后一个字节 unpadding 次 unpadding := int(origData[length-1]) return origData[:(length - unpadding)] }
2)、3DES
加密代码:
// 3DES加密 func TripleDesEncrypt(origData, key []byte) ([]byte, error) { block, err := des.NewTripleDESCipher(key) if err != nil { return nil, err } origData = PKCS5Padding(origData, block.BlockSize()) // origData = ZeroPadding(origData, block.BlockSize()) blockMode := cipher.NewCBCEncrypter(block, key[:8]) crypted := make([]byte, len(origData)) blockMode.CryptBlocks(crypted, origData) return crypted, nil }
对比DES,发现只是换了NewTripleDESCipher。不过,需要注意的是,密钥长度必须24byte,否则直接返回错误。关于这一点,PHP中却不是这样的,只要是8byte以上就行;而Java中,要求必须是24byte以上,内部会取前24byte(相当于就是24byte)。
另外,初始化向量长度是8byte(目前各个语言都是如此,不是8byte会有问题)。然而,如果你用的Go是1.0.3(或以下),iv可以不等于8byte。其实,在cipher.NewCBCEncrypter方法中有注释:
The length of iv must be the same as the Block’s block size.
可是代码中的实现却没有做判断。不过,go tips中修正了这个问题,如果iv不等于block size(des为8),则直接panic。所以,对于加解密,一定要测试,保证iv等于block size,否则可能会panic:
func NewCBCDecrypter(b Block, iv []byte) BlockMode { if len(iv) != b.BlockSize() { panic("cipher.NewCBCDecrypter: IV length must equal block size") } return (*cbcDecrypter)(newCBC(b, iv)) }
此处之所有用panic而不是返回error,个人猜测,是由于目前发布的版本,该方法没有返回error,修改方法签名会导致兼容性问题,因此用panic了。
解密代码:
// 3DES解密 func TripleDesDecrypt(crypted, key []byte) ([]byte, error) { block, err := des.NewTripleDESCipher(key) if err != nil { return nil, err } blockMode := cipher.NewCBCDecrypter(block, key[:8]) origData := make([]byte, len(crypted)) // origData := crypted blockMode.CryptBlocks(origData, crypted) origData = PKCS5UnPadding(origData) // origData = ZeroUnPadding(origData) return origData, nil }
三、和其他语言交互:加解密
这次,我写了PHP、Java的版本,具体代码放在github上。这里说明一下,Java中,默认模式是ECB,且没有用”\0″填充的情况,只有NoPadding和PKCS5Padding;而PHP中(mcrypt扩展),默认填充方式是”\0″,而且,当数据长度刚好是block size的整数倍时,默认不会填充”\0″,这样,如果数据刚好是block size的整数倍且结尾字符是”\0″,会有问题。
综上,跨语言加密解密,应该使用PKCS5Padding填充。
完整代码 myblog_article_code
相关文章

10 thoughts on “Go加密解密之DES”
哈哈,正在看这个,很好的资料
刚好在找java和go语言相互可用的加解密方案,节省了不少时间,谢谢。
很不错的文章, 特别是 padding部分, 我没有在golang docs中找到任何补pad相关的知识, 感谢!
还有, 关于跨语言的Padding支持介绍, 也很有借鉴意义.
另外, 提点建议, 可能有人会把你的文章当做sample写进实际项目里, 所以 用key做iv的方法, 最好修改一下, 比如, 像golang docs里介绍的那样, 把随机生成的iv放在头部.
感谢建议。已经加了一个特别提示。
完整代码des.go中
ZeroUnPadding
的实现是有问题的func ZeroUnPadding(origData []byte) []byte {
length := len(origData)
unpadding := int(origData[length-1])
return origData[:(length - unpadding)]
}
可以改成
func ZeroUnPadding(origData []byte) []byte {
return bytes.TrimFunc(origData,
func(r rune) bool {
return r == rune(0)
})
}
呃~格式不对,没找到删除或修改的地方,麻烦楼主删除上一条
des.go中
ZeroUnPadding
的实现是有问题的func ZeroUnPadding(origData []byte) []byte {
length := len(origData)
unpadding := int(origData[length-1])
return origData[:(length - unpadding)]
}
可以改成
func ZeroUnPadding(origData []byte) []byte {
return bytes.TrimFunc(origData,
func(r rune) bool {
return r == rune(0)
})
}
已修正,感谢反馈。当时可能直接拷贝 PKCS5UnPadding 了。
不过,你这里最好使用 bytes.TrimRightFunc
博主您好,咨询你几个问题。我如果用python调用你的go生成的动态链接库,python传入byte貌似不行。
Спасибо за информацию!!!!!