這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
接著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。另外定義了兩個我們需要特別關注的函數,即
1 func NewCipher(key []byte) (cipher.Block, error)
2 func NewTripleDESCipher(key []byte) (cipher.Block, error)
他們都是用來獲得一個cipher.Block。從名字可以很容易知道,DES使用NewCipher,3DES使用NewTripleDESCipher。參數都是密鑰(key)
2、crypto/cipher包
那麼,cipher這個包是幹嘛用的呢?它實現了標準的塊加密模式。我們看一下cipher.Block
1 type Block interface {
2 // BlockSize returns the cipher's block size.
3 BlockSize() int
4
5 // Encrypt encrypts the first block in src into dst.
6 // Dst and src may point at the same memory.
7 Encrypt(dst, src []byte)
8
9 // Decrypt decrypts the first block in src into dst.
10 // Dst and src may point at the same memory.
11 Decrypt(dst, src []byte)
12 }
這是一個介面
對稱式加密,按塊方式,我們經常見到CBC、ECB之類的,這些是加密模式。可以參考:DES加密模式詳解 http://linux.bokee.com/6956594.html
Go中定義了一個介面BlockMode代表各種模式
1 type BlockMode interface {
2 // BlockSize returns the mode's block size.
3 BlockSize() int
4
5 // CryptBlocks encrypts or decrypts a number of blocks. The length of
6 // src must be a multiple of the block size. Dst and src may point to
7 // the same memory.
8 CryptBlocks(dst, src []byte)
9 }
該包還提供了擷取BlockMode執行個體的兩個方法
1 func NewCBCDecrypter(b Block, iv []byte) BlockMode
2 func NewCBCEncrypter(b Block, iv []byte) BlockMode
即一個CBC加密,一個CBC解密
對於按流方式加密的,定義了一個介面:
1 type Stream interface {
2 // XORKeyStream XORs each byte in the given slice with a byte from the
3 // cipher's key stream. Dst and src may point to the same memory.
4 XORKeyStream(dst, src []byte)
5 }
同樣也提供了擷取實現該介面的執行個體
這裡,我們只討論CBC模式
3、加密解密
1)DES
DES加密代碼如下:
1 func DesEncrypt(origData, key []byte) ([]byte, error) {
2 block, err := des.NewCipher(key)
3 if err != nil {
4 return nil, err
5 }
6 origData = PKCS5Padding(origData, block.BlockSize())
7 // origData = ZeroPadding(origData, block.BlockSize())
8 blockMode := cipher.NewCBCEncrypter(block, key)
9 crypted := make([]byte, len(origData))
10 // 根據CryptBlocks方法的說明,如下方式初始化crypted也可以
11 // crypted := origData
12 blockMode.CryptBlocks(crypted, origData)
13 return crypted, nil
14 }
以上代碼使用DES加密(des.NewCipher),加密模式為CBC(cipher.NewCBCEncrypter(block, key)),填充方式PKCS5Padding,該函數的代碼如下:
1 func PKCS5Padding(ciphertext []byte, blockSize int) []byte {
2 padding := blockSize - len(ciphertext)%blockSize
3 padtext := bytes.Repeat([]byte{byte(padding)}, padding)
4 return append(ciphertext, padtext...)
5 }
可見,資料長度剛好是block size的整數倍時,也進行了填充,如果不進行填充,unpadding會搞不定。
另外,為了方便,初始向量直接使用key充當了。
DES解密代碼如下:
1 func DesDecrypt(crypted, key []byte) ([]byte, error) {
2 block, err := des.NewCipher(key)
3 if err != nil {
4 return nil, err
5 }
6 blockMode := cipher.NewCBCDecrypter(block, key)
7 origData := make([]byte, len(crypted))
8 // origData := crypted
9 blockMode.CryptBlocks(origData, crypted)
10 origData = PKCS5UnPadding(origData)
11 // origData = ZeroUnPadding(origData)
12 return origData, nil
13 }
可見,解密無非是調用cipher.NewCBCDecrypter,最後unpadding,其他跟加密幾乎一樣。相應的PKCS5UnPadding:
1 func ZeroUnPadding(origData []byte) []byte {
2 length := len(origData)
3 // 去掉最後一個位元組 unpadding 次
4 unpadding := int(origData[length-1])
5 return origData[:(length - unpadding)]
6 }
2)、3DES
加密代碼:
1 // 3DES加密
2 func TripleDesEncrypt(origData, key []byte) ([]byte, error) {
3 block, err := des.NewTripleDESCipher(key)
4 if err != nil {
5 return nil, err
6 }
7 origData = PKCS5Padding(origData, block.BlockSize())
8 // origData = ZeroPadding(origData, block.BlockSize())
9 blockMode := cipher.NewCBCEncrypter(block, key[:8])
10 crypted := make([]byte, len(origData))
11 blockMode.CryptBlocks(crypted, origData)
12 return crypted, nil
13 }
對比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:
1 func NewCBCDecrypter(b Block, iv []byte) BlockMode {
2 if len(iv) != b.BlockSize() {
3 panic("cipher.NewCBCDecrypter: IV length must equal block size")
4 }
5 return (*cbcDecrypter)(newCBC(b, iv))
6 }
此處之所有用panic而不是返回error,個人猜測,是由於目前發布的版本,該方法沒有返回error,修改方法簽名會導致相容性問題,因此用panic了。
解密代碼:
1 // 3DES解密
2 func TripleDesDecrypt(crypted, key []byte) ([]byte, error) {
3 block, err := des.NewTripleDESCipher(key)
4 if err != nil {
5 return nil, err
6 }
7 blockMode := cipher.NewCBCDecrypter(block, key[:8])
8 origData := make([]byte, len(crypted))
9 // origData := crypted
10 blockMode.CryptBlocks(origData, crypted)
11 origData = PKCS5UnPadding(origData)
12 // origData = ZeroUnPadding(origData)
13 return origData, nil
14 }
三、和其他語言互動:加解密
這次,我寫了PHP、Java的版本,具體代碼放在github上。這裡說明一下,Java中,預設模式是ECB,且沒有用”\0″填充的情況,只有NoPadding和PKCS5Padding;而PHP中(mcrypt擴充),預設填充方式是”\0″,而且,當資料長度剛好是block size的整數倍時,預設不會填充”\0″,這樣,如果資料剛好是block size的整數倍且結尾字元是”\0″,會有問題。
綜上,跨語言加密解密,應該使用PKCS5Padding填充。