本文將介紹比特序列運算中的異或運算,同時簡單介紹DES、3DES、AES等對稱加密算法,最後給出對應的Golang加密代碼。算法
首先咱們要明白二個概念,一個是計算機編碼,咱們都知道計算機操做的對象並非文字圖像,而是有0和1排列組成的比特序列;一個是異或運算(XOR), 二個不一樣的比特位異或運算結果是1,不一樣的比特位異或運算結果是0。安全
假設咱們將01001100這個比特序列成爲A,將10101010這個比特序列成爲B,那麼A與B的異或運算結果就以下所示:網絡
因爲二個相同的數進行異或運算的結果是0,所以若是將A與B異或的結果再與B進行異或預算,則結果就會變回A。 app
可能你已經發現了,上面的計算和加密、解密的步驟很是類似。函數
實際上,主要選擇一個合適的B,僅僅使用異或運算就能夠實現一個高強度的密碼。測試
DES(Data EncryptionStandard) 是1977年美國聯邦信息處理標準(FIPS)中所採用的一種對稱密碼(FIPS46.3)。DES一直以來被美國以及其餘國家的政府和銀行等普遍使用。然而,隨着計算機的進步,如今DES已經可以被暴力破解,強度大不如前了。ui
RSA公司舉辦過破解DES密鑰的比賽(DESChallenge),咱們能夠看一看RSA公司官方公佈的比賽結果:編碼
因爲DES的密文能夠在短期內被破譯,所以除了用它來解密之前的密文之外,如今咱們不該該再使用DES了。加密
DES是一種將64比特的明文加密成64比特的密文的對稱密碼算法, 它的密鑰長度是56比特。儘管從規格上來講,DES的密鑰長度是64比特,但因爲每隔7比特會設置一個用於錯誤檢查的比特,所以實質上其密鑰長度是56比特。spa
DES是以64比特的明文(比特序列)爲一個單位來進行加密的,這個64比特的單位稱爲分組。通常來講,以分組爲單位進行處理的密碼算法稱爲分組密碼(blockcipher),DES就是分組密碼的一種。
DES每次只能加密64比特的數據,若是要加密的明文比較長,就須要對DES加密進行迭代(反覆),而迭代的具體方式就稱爲模式(mode)(後續文章詳細講解分組密碼的模式)。
大B -> bit
小b -> byte
祕鑰長度(56bit + 8bit)/8 = 8byte
加密 - CBC分組模式
- 建立並返回一個使用DES算法的cipher.Block接口
- 祕鑰長度爲64bit, 即 64/8 = 8字節(byte)
- 對最後一個明文分組進行數據填充
- DES是以64比特的明文(比特序列)爲一個單位來進行加密的
- 最後一組不夠64bit, 則須要進行數據填充( 參考第三章)
- 建立一個密碼分組爲連接模式的, 底層使用DES加密的BlockMode接口
- 加密連續的數據塊
解密
- 建立並返回一個使用DES算法的cipher.Block接口
- 建立一個密碼分組爲連接模式的, 底層使用DES解密的BlockMode接口
- 數據塊解密
- 去掉最後一組的填充數據
在Go中使用DES須要導入的包:
import (
"crypto/des"
"crypto/cipher"
"fmt"
"bytes"
)
複製代碼
DES加密代碼:
// src -> 要加密的明文
// key -> 祕鑰, 大小爲: 8byte
func DesEncrypt_CBC(src, key []byte) []byte{
// 1. 建立並返回一個使用DES算法的cipher.Block接口
block, err := des.NewCipher(key)
// 2. 判斷是否建立成功
if err != nil{
panic(err)
}
// 3. 對最後一個明文分組進行數據填充
src = PKCS5Padding(src, block.BlockSize())
// 4. 建立一個密碼分組爲連接模式的, 底層使用DES加密的BlockMode接口
// 參數iv的長度, 必須等於b的塊尺寸
tmp := []byte("helloAAA")
blackMode := cipher.NewCBCEncrypter(block, tmp)
// 5. 加密連續的數據塊
dst := make([]byte, len(src))
blackMode.CryptBlocks(dst, src)
fmt.Println("加密以後的數據: ", dst)
// 6. 將加密數據返回
return dst
}
複製代碼
DES解密代碼
// src -> 要解密的密文
// key -> 祕鑰, 和加密祕鑰相同, 大小爲: 8byte
func DesDecrypt_CBC(src, key []byte) []byte {
// 1. 建立並返回一個使用DES算法的cipher.Block接口
block, err := des.NewCipher(key)
// 2. 判斷是否建立成功
if err != nil{
panic(err)
}
// 3. 建立一個密碼分組爲連接模式的, 底層使用DES解密的BlockMode接口
tmp := []byte("helloAAA")
blockMode := cipher.NewCBCDecrypter(block, tmp)
// 4. 解密數據
dst := src
blockMode.CryptBlocks(src, dst)
// 5. 去掉最後一組填充的數據
dst = PKCS5UnPadding(dst)
// 6. 返回結果
return dst
}
複製代碼
最後一個分組添加填充數據和移除添加數據代碼
// 使用pks5的方式填充
func PKCS5Padding(ciphertext []byte, blockSize int) []byte{
// 1. 計算最後一個分組缺多少個字節
padding := blockSize - (len(ciphertext)%blockSize)
// 2. 建立一個大小爲padding的切片, 每一個字節的值爲padding
padText := bytes.Repeat([]byte{byte(padding)}, padding)
// 3. 將padText添加到原始數據的後邊, 將最後一個分組缺乏的字節數補齊
newText := append(ciphertext, padText...)
return newText
}
// 刪除pks5填充的尾部數據
func PKCS5UnPadding(origData []byte) []byte{
// 1. 計算數據的總長度
length := len(origData)
// 2. 根據填充的字節值獲得填充的次數
number := int(origData[length-1])
// 3. 將尾部填充的number個字節去掉
return origData[:(length-number)]
}
複製代碼
測試函數
func DESText() {
// 加密
key := []byte("11111111")
result := DesEncrypt_CBC([]byte("牀前明月光, 疑是地上霜. 舉頭望明月, 低頭思故鄉."), key)
fmt.Println(base64.StdEncoding.EncodeToString(result))
// 解密
result = DesDecrypt_CBC(result, key)
fmt.Println("解密以後的數據: ", string(result))
}
複製代碼
重要的函數說明
生成一個底層使用DES加/解密的Block接口對象
函數對應的包: import "crypto/des"
func NewCipher(key []byte) (cipher.Block, error) - 參數 key: des對稱加密使用的密碼, 密碼長度爲64bit, 即8byte - 返回值 cipher.Block: 建立出的使用DES加/解密的Block接口對象 複製代碼
建立一個密碼分組爲CBC模式, 底層使用b加密的BlockMode接口對象
函數對應的包: import "crypto/cipher"
func NewCBCEncrypter(b Block, iv []byte) BlockMode - 參數 b: 使用des.NewCipher函數建立出的Block接口對象 - 參數 iv: 事先準備好的一個長度爲一個分組長度的比特序列, 每一個分組爲64bit, 即8byte - 返回值: 獲得的BlockMode接口對象 複製代碼
使用cipher包的BlockMode接口對象對數據進行加/解密
接口對應的包: import "crypto/cipher"
type BlockMode interface {
// 返回加密字節塊的大小
BlockSize() int
// 加密或解密連續的數據塊,src的尺寸必須是塊大小的整數倍,src和dst可指向同一內存地址
CryptBlocks(dst, src []byte)
}
接口中的 CryptBlocks(dst, src []byte) 方法:
- 參數 dst: 傳出參數, 存儲加密或解密運算以後的結果
- 參數 src: 傳入參數, 須要進行加密或解密的數據切片(字符串)
複製代碼
建立一個密碼分組爲CBC模式, 底層使用b解密的BlockMode接口對象
函數對應的包: import "crypto/cipher"
func NewCBCDecrypter(b Block, iv []byte) BlockMode - 參數 b: 使用des.NewCipher函數建立出的Block接口對象 - 參數 iv: 事先準備好的一個長度爲一個分組長度的比特序列, 每一個分組爲64bit, 即8byte, 該序列的值須要和NewCBCEncrypter函數的第二個參數iv值相同 - 返回值: 獲得的BlockMode接口對象 複製代碼
自定義函數介紹
對稱加密加密須要對數據進行分組, 保證每一個分組的數據長度相等, 若是最後一個分組長度不夠, 須要進行填充
func PKCS5Padding(ciphertext []byte, blockSize int) []byte - 參數 ciphertext: 須要加密的原始數據 - 參數 blockSize: 每一個分組的長度, 跟使用的加密算法有關係 * des:64bit, 8byte * 3des:64bit, 8byte * aes: 128bit, 16byte 複製代碼
如今DES已經能夠在現實的時間內被暴力破解,所以咱們須要一種用來替代DES的分組密碼,三重DES就是出於這個目的被開發出來的。
三重DES(triple-DES)是爲了增長DES的強度,將DES重複3次所獲得的一種密碼算法,一般縮寫爲3DES。
三重DES的加解密機制以下圖所示:
加->解->加 -> 目的是爲了兼容DES(若是三個祕鑰都同樣)
3des祕鑰長度24字節 = 1234567a 1234567b 1234567a
明文: 10
祕鑰1: 2
祕鑰2: 3
祕鑰3: 4
加密算法: 明文+祕鑰
解密算法: 密文-祕鑰
10+2-3+4
明文通過三次DES處理才能變成最後的密文,因爲DES密鑰的長度實質上是56比特,所以三重DES的密鑰長度就是56×3=168比特, 加上用於錯誤檢測的標誌位8x3, 共192bit。
從上圖咱們能夠發現,三重DES並非進行三次DES加密(加密-->加密-->加密),而是加密-->解密-->加密的過程。在加密算法中加人解密操做讓人感受很難以想象,實際上這個方法是IBM公司設計出來的,目的是爲了讓三重DES可以兼容普通的DES。
當三重DES中全部的密鑰都相同時,三重DES也就等同於普通的DES了。這是由於在前兩步加密-->解密以後,獲得的就是最初的明文。所以,之前用DES加密的密文,就能夠經過這種方式用三重DES來進行解密。也就是說,三重DES對DES具有向下兼容性。
若是密鑰1和密鑰3使用相同的密鑰,而密鑰2使用不一樣的密鑰(也就是隻使用兩個DES密鑰),這種三重DES就稱爲DES-EDE2。EDE表示的是加密(Encryption) -->解密(Decryption)-->加密(Encryption)這個流程。
密鑰一、密鑰二、密鑰3所有使用不一樣的比特序列的三重DES稱爲DES-EDE3。
儘管三重DES目前還被銀行等機構使用,但其處理速度不高,並且在安全性方面也逐漸顯現出了一些問題。
加密 - CBC分組模式
- 建立並返回一個使用3DES算法的cipher.Block接口
- 祕鑰長度爲64bit*3=192bit, 即 192/8 = 24字節(byte)
- 對最後一個明文分組進行數據填充
- 3DES是以64比特的明文(比特序列)爲一個單位來進行加密的
- 最後一組不夠64bit, 則須要進行數據填充( 參考第三章)
- 建立一個密碼分組爲連接模式的, 底層使用3DES加密的BlockMode接口
- 加密連續的數據塊
解密
- 建立並返回一個使用3DES算法的cipher.Block接口
- 建立一個密碼分組爲連接模式的, 底層使用3DES解密的BlockMode接口
- 數據塊解密
- 去掉最後一組的填充數據
3DES加密代碼
// 3DES加密
func TripleDESEncrypt(src, key []byte) []byte {
// 1. 建立並返回一個使用3DES算法的cipher.Block接口
block, err := des.NewTripleDESCipher(key)
if err != nil{
panic(err)
}
// 2. 對最後一組明文進行填充
src = PKCS5Padding(src, block.BlockSize())
// 3. 建立一個密碼分組爲連接模式, 底層使用3DES加密的BlockMode模型
blockMode := cipher.NewCBCEncrypter(block, key[:8])
// 4. 加密數據
dst := src
blockMode.CryptBlocks(dst, src)
return dst
}
複製代碼
3DES解密代碼
// 3DES解密
func TripleDESDecrypt(src, key []byte) []byte {
// 1. 建立3DES算法的Block接口對象
block, err := des.NewTripleDESCipher(key)
if err != nil{
panic(err)
}
// 2. 建立密碼分組爲連接模式, 底層使用3DES解密的BlockMode模型
blockMode := cipher.NewCBCDecrypter(block, key[:8])
// 3. 解密
dst := src
blockMode.CryptBlocks(dst, src)
// 4. 去掉尾部填充的數據
dst = PKCS5UnPadding(dst)
return dst
}
複製代碼
重要的函數說明
生成一個底層使用3DES加/解密的Block接口對象
函數對應的包: import "crypto/des"
func NewTripleDESCipher(key []byte) (cipher.Block, error) - 參數 key: 3des對稱加密使用的密碼, 密碼長度爲(64*3)bit, 即(8*3)byte - 返回值 cipher.Block: 建立出的使用DES加/解密的Block接口對象 複製代碼
建立一個密碼分組爲CBC模式, 底層使用b加密的BlockMode接口對象
函數對應的包: import "crypto/cipher"
func NewCBCEncrypter(b Block, iv []byte) BlockMode - 參數 b: 使用des.NewTripleDESCipher 函數建立出的Block接口對象 - 參數 iv: 事先準備好的一個長度爲一個分組長度的比特序列, 每一個分組爲64bit, 即8byte - 返回值: 獲得的BlockMode接口對象 複製代碼
使用cipher包的BlockMode接口對象對數據進行加/解密
接口對應的包: import "crypto/cipher"
type BlockMode interface {
// 返回加密字節塊的大小
BlockSize() int
// 加密或解密連續的數據塊,src的尺寸必須是塊大小的整數倍,src和dst可指向同一內存地址
CryptBlocks(dst, src []byte)
}
接口中的 CryptBlocks(dst, src []byte) 方法:
- 參數 dst: 傳出參數, 存儲加密或解密運算以後的結果
- 參數 src: 傳入參數, 須要進行加密或解密的數據切片(字符串)
複製代碼
建立一個密碼分組爲CBC模式, 底層使用b解密的BlockMode接口對象
函數對應的包: import "crypto/cipher"
func NewCBCDecrypter(b Block, iv []byte) BlockMode - 參數 b: 使用des.NewTripleDESCipher 函數建立出的Block接口對象 - 參數 iv: 事先準備好的一個長度爲一個分組長度的比特序列, 每一個分組爲64bit, 即8byte, 該序列的值須要和NewCBCEncrypter函數的第二個參數iv值相同 - 返回值: 獲得的BlockMode接口對象 複製代碼
AES(Advanced Encryption Standard)是取代其前任標準(DES)而成爲新標準的一種對稱密碼算法。全世界的企業和密碼學家提交了多個對稱密碼算法做爲AES的候選,最終在2000年從這些候選算法中選出了一種名爲Rijndael的對稱密碼算法,並將其肯定爲了AES。
Rijndael的分組長度爲128比特,密鑰長度能夠以32比特爲單位在128比特到256比特的範圍內進行選擇(不過在AES的規格中,密鑰長度只有12八、192和256比特三種)。
在go提供的接口中祕鑰長度只能是16字節
和DES—樣,AES算法也是由多輪所構成的,下圖展現了每一輪的大體計算步驟。DES使用Feistel網絡做爲其基本結構,而AES沒有使用Feistel網絡,而是使用了SPN Rijndael的輸人分組爲128比特,也就是16字節。首先,須要逐個字節地對16字節的輸入數據進行SubBytes處理。所謂SubBytes,就是以每一個字節的值(0~255中的任意值)爲索引,從一張擁有256個值的替換表(S-Box)中查找出對應值的處理,也是說,將一個1字節的值替換成另外一個1字節的值。
SubBytes以後須要進行ShiftRows處理,即將SubBytes的輸出以字節爲單位進行打亂處理。從下圖的線咱們能夠看出,這種打亂處理是有規律的。
ShiftRows以後須要進行MixCo1umns處理,即對一個4字節的值進行比特運算,將其變爲另一個4字節值。
最後,須要將MixColumns的輸出與輪密鑰進行XOR,即進行AddRoundKey處理。到這裏,AES的一輪就結東了。實際上,在AES中須要重複進行10 ~ 14輪計算。
經過上面的結構咱們能夠發現輸入的全部比特在一輪中都會被加密。和每一輪都只加密一半輸人的比特的Feistel網絡相比,這種方式的優點在於加密所須要的輪數更少。此外,這種方式還有一個優點,即SubBytes,ShiftRows和MixColumns能夠分別按字節、行和列爲單位進行並行計算。
下圖展現了AES中一輪的解密過程。從圖中咱們能夠看出,SubBytes、ShiftRows、MixColumns分別存在反向運算InvSubBytes、InvShiftRows、InvMixColumns,這是由於AES不像Feistel網絡同樣可以用同一種結構實現加密和解密。
加密 - CBC分組模式
- 建立並返回一個使用AES算法的cipher.Block接口
- 祕鑰長度爲128bit, 即 128/8 = 16字節(byte)
- 對最後一個明文分組進行數據填充
- AES是以128比特的明文(比特序列)爲一個單位來進行加密的
- 最後一組不夠128bit, 則須要進行數據填充( 參考第三章)
- 建立一個密碼分組爲連接模式的, 底層使用AES加密的BlockMode接口
- 加密連續的數據塊
解密
- 建立並返回一個使用AES算法的cipher.Block接口
- 建立一個密碼分組爲連接模式的, 底層使用AES解密的BlockMode接口
- 數據塊解密
- 去掉最後一組的填充數據
AES加密代碼
// AES加密
func AESEncrypt(src, key []byte) []byte{
// 1. 建立一個使用AES加密的塊對象
block, err := aes.NewCipher(key)
if err != nil{
panic(err)
}
// 2. 最後一個分組進行數據填充
src = PKCS5Padding(src, block.BlockSize())
// 3. 建立一個分組爲連接模式, 底層使用AES加密的塊模型對象
blockMode := cipher.NewCBCEncrypter(block, key[:block.BlockSize()])
// 4. 加密
dst := src
blockMode.CryptBlocks(dst, src)
return dst
}
複製代碼
AES解密
// AES解密
func AESDecrypt(src, key []byte) []byte{
// 1. 建立一個使用AES解密的塊對象
block, err := aes.NewCipher(key)
if err != nil{
panic(err)
}
// 2. 建立分組爲連接模式, 底層使用AES的解密模型對象
blockMode := cipher.NewCBCDecrypter(block, key[:block.BlockSize()])
// 3. 解密
dst := src
blockMode.CryptBlocks(dst, src)
// 4. 去掉尾部填充的字
dst = PKCS5UnPadding(dst)
return dst
}
複製代碼
重要的函數說明
生成一個底層使用AES加/解密的Block接口對象
函數對應的包: import "crypto/aes"
func NewCipher(key []byte) (cipher.Block, error) - 參數 key: aes對稱加密使用的密碼, 密碼長度爲128bit, 即16byte - 返回值 cipher.Block: 建立出的使用AES加/解密的Block接口對象 複製代碼
建立一個密碼分組爲CBC模式, 底層使用b加密的BlockMode接口對象
函數對應的包: import "crypto/cipher"
func NewCBCEncrypter(b Block, iv []byte) BlockMode - 參數 b: 使用aes.NewCipher函數建立出的Block接口對象 - 參數 iv: 事先準備好的一個長度爲一個分組長度的比特序列, 每一個分組爲64bit, 即8byte - 返回值: 獲得的BlockMode接口對象 複製代碼
使用cipher包的BlockMode接口對象對數據進行加/解密
接口對應的包: import "crypto/cipher"
type BlockMode interface {
// 返回加密字節塊的大小
BlockSize() int
// 加密或解密連續的數據塊,src的尺寸必須是塊大小的整數倍,src和dst可指向同一內存地址
CryptBlocks(dst, src []byte)
}
接口中的 CryptBlocks(dst, src []byte) 方法:
- 參數 dst: 傳出參數, 存儲加密或解密運算以後的結果
- 參數 src: 傳入參數, 須要進行加密或解密的數據切片(字符串)
複製代碼
建立一個密碼分組爲CBC模式, 底層使用b解密的BlockMode接口對象
函數對應的包: import "crypto/cipher"
func NewCBCDecrypter(b Block, iv []byte) BlockMode - 參數 b: 使用des.NewCipher函數建立出的Block接口對象 - 參數 iv: 事先準備好的一個長度爲一個分組長度的比特序列, 每一個分組爲128bit, 即16byte, 該序列的值須要和NewCBCEncrypter函數的第二個參數iv值相同 - 返回值: 獲得的BlockMode接口對象 複製代碼
前面介紹了DES、三重DES和AES等對稱密碼,那麼咱們到底應該使用哪種對稱密碼算法呢?
- 從此最好不要將DES用於新的用途,由於隨着計算機技術的進步,如今用暴力破解法已經可以在現實的時間內完成對DES的破譯。可是,在某些狀況下也須要保持與舊版本軟件的兼容性。
- 出於兼容性的因素三重DES在從此還會使用一段時間,但會逐漸被AES所取代。
- 從此你們應該使用的算法是AES(Rijndael),由於它安全、快速,並且可以在各類平臺上工做。此外,因爲全世界的密碼學家都在對AES進行不斷的驗證,所以即使萬一發現它有什麼缺陷,也會馬上告知全世界並修復這些缺陷。
通常來講,咱們不該該使用任何自制的密碼算法,而是應該使用AES。由於AES在其選定過程當中,通過了全世界密碼學家所進行的高品質的驗證工做,而對於自制的密碼算法則很難進行這樣的驗證。
歡迎與我交流