密碼學系列 - 對稱加密
本文討論的對稱加密算法主要包括 DES、3DES、AES
DES
明文:64 bit 密文:64 bit 密鑰:56/64 bit(每 7 位插入一個校驗位的時候為 64 bit) 其設計思想充分體現了香農提出的混淆和擴散原則

DES 使用的是 Feistel 結構來加密的,一共需要 16 輪,加密過程如下:
- 將明文進行初始置換(通過置換表)
- 將置換后的數據分為左右 L1 R1 各 32 bit
- 將 48 bit 的子密鑰與 R1 作為輪函數F的輸入
- 將 L1 與輪函數的輸出異或運算,得到 L1密文
- 將 L1 密文與 R1 交換位置,分別作為下一輪的 R2,L2
- 將 2-5 再重復 15 次
- 將 L17 R17 交換位置,并拼接為 64bit 數據
- 將 64bit 數據進行逆初始置換,得到最終密文
需要注意的是:
- 子密鑰在每一輪中都是不一樣的
- 每一輪之間會將左側和右側對調(右側沒有加密)
- 解密的過程就是將輸出用相同的子密鑰再走一遍,如果加密的子密鑰順序是key1 key2 key3,則解密的子密鑰為key3 key2 key1
- 輪函數可以設計為不可逆函數如hash,對解密沒有影響

golang 代碼實戰:
func TestDesEncrypt(t *testing.T) {
key:=[]byte{0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01}
cipherBlock,err:=des.NewCipher(key)
if err!=nil{
t.Error(err)
}
src:=[]byte{0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08}
encrptDst :=make([]byte,len(src))
cipherBlock.Encrypt(encrptDst,src)
t.Log(encrptDst)
plainDst:=make([]byte,len(encrptDst))
cipherBlock.Decrypt(plainDst, encrptDst)
t.Log(plainDst)
}
//out: [206 173 55 61 184 14 171 248]
//out: [1 2 3 4 5 6 7 8]
三重DES
明文:64 bit 密文:64 bit 密鑰:56/64 * 3 bit(加入校驗位的時候為64 bit)
為了增加 DES 的強度,明文經過 3 次 DES 處理后變成最后的密文,因此密鑰長度為 56/64 * 3 bit。3 次 DES 處理并不是簡單的 3 次加密的過程,而是加密、解密、加密,解密的過程相應的就是解密、解密、解密。這樣設計是因為在 3 個密鑰相同時,可以兼容 DES 算法

golang 代碼實戰:
func TestTripleDesEncrypt(t *testing.T) {
key:=[]byte{0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01}
cipherBlock,err:=des.NewTripleDESCipher(key)
if err!=nil{
t.Error(err)
}
src:=[]byte{0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08}
encrptDst :=make([]byte,len(src))
cipherBlock.Encrypt(encrptDst,src)
t.Log(encrptDst)
plainDst:=make([]byte,len(encrptDst))
cipherBlock.Decrypt(plainDst, encrptDst)
t.Log(plainDst)
}
//此處3個密鑰相同,兼容DES
//out: [206 173 55 61 184 14 171 248]
//out: [1 2 3 4 5 6 7 8]
AES
明文:128 bit 密文:128 bit 密鑰:128/192/256 bit (分別需要10/12/14輪)
AES 標準最后評選出的算法是 Rijindale 算法,該算法支持密鑰 128/192/256 bit ,分別需要 10/12/14 輪,本文討論的是 128 bit密鑰。它的加密過程并沒有使用 DES 的 feistel 結構,而是使用了一種新的 SPN 結構,需要 10-14 輪計算,如下圖:

其中每一輪計算過程如下:
- SubBytes(字節替換):以字節大小為索引,與s_box表中字節映射
- ShiftRows(行移位-擴散):從上到下從左到右的順序組成 4 * 4 數組,從 0 行開始,第 n 行向左平移 n 個字節
- MixColums(列混肴-擴散):對每一列進行矩陣運算,共四列
- AddRoundKey(輪密鑰加):與輪密鑰即子密鑰異或運算
需要注意的是:
- 最后一輪沒有列混淆
- 加密時:SubBytes -> ShiftRows -> MixColums -> AddRoundKey 解密時:AddRoundkey -> InvMixColums -> InvShiftRows -> InvSubBytes (Inv代表逆運算)
golang 代碼實戰:
func TestAesEncrypt(t *testing.T){
key:=[]byte{0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01}
cipherBlock,err:=aes.NewCipher(key)
if err!=nil{
t.Error(err)
}
src:=[]byte{0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08}
encrptDst :=make([]byte,len(src))
cipherBlock.Encrypt(encrptDst,src)
t.Log(encrptDst)
plainDst:=make([]byte,len(encrptDst))
cipherBlock.Decrypt(plainDst, encrptDst)
t.Log(plainDst)
}
//out [19 7 34 196 163 153 225 186 223 245 40 131 80 80 70 203]
//out [1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8]
迭代模式
以上討論的三種加密算法都是分組密碼,每次只能處理特定長度的一塊數據,例如 DES 和 3DES 能處理的這塊數據長度為 8 bytes,AES 的為 16 bytes。而我們的日常需要加密的明文基本上都是大于這個長度,這就需要我們將明文的內容進行分組并迭代加密,這個迭代加密的方式就是模式。
ECB 模式
電子密碼本模式(electronic codebook ),最簡單的模式,將明文分組直接作為加密算法的輸入,加密算法的輸出直接作為密文分組。
CBC 模式
密文分組鏈接模式(Cipher Block Chaining),密文之間是鏈狀的,明文分組跟上個密文分組異或之后作為加密算法的輸入,加密算法的輸出作為密文分組。第一個明文分組加密時需要一個初始化向量。
CFB 模式
密文反饋模式(Cipher FeedBack),上一個密文分組作為下一個加密算法的輸入,加密算法的輸出與明文分組異或結果作為密文分組。同樣需要一個初始化向量
OFB 模式
輸出反饋模式(OutPut FeedBack),上一個加密算法的輸出作為下一個加密算法的輸入,明文與加密算法的輸出異或作為密文分組。需要初始化向量
CTR 模式
計數器模式(Counter),將計數器作為加密算法的輸入,加密算法的輸出與明文分組異或作為密文分組,計數器是累加的。需要一個初始的計數器值
以上各種模式,ECB 不推薦使用
golang 代碼實戰:
func TestCBCMode(t *testing.T) {
key:=[]byte{0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01}
cipherBlock,err:=aes.NewCipher(key)
if err!=nil{
t.Error(err)
}
src:=[]byte{0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08}
inv:=[]byte{0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08}
cbcEncrypter:=cipher.NewCBCEncrypter(cipherBlock,inv)
encrptDst :=make([]byte,len(src))
cbcEncrypter.CryptBlocks(encrptDst,src)
t.Log(encrptDst)
plainDst:=make([]byte,len(encrptDst))
cbcDecrypter:=cipher.NewCBCDecrypter(cipherBlock,inv)
cbcDecrypter.CryptBlocks(plainDst,encrptDst)
t.Log(plainDst)
}
//out [182 174 175 250 117 45 192 139 81 99 151 49 118 26 237 0 98 117 59 208 145 166 116 62 43 199 115 70 250 251 56 226]
//out [1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8]
關于本文作者更多信息請查看: https://tpkeep.com
密碼學系列 - 對稱加密
本文討論的對稱加密算法主要包括 DES、3DES、AES
DES
明文:64 bit 密文:64 bit 密鑰:56/64 bit(每 7 位插入一個校驗位的時候為 64 bit) 其設計思想充分體現了香農提出的混淆和擴散原則

DES 使用的是 Feistel 結構來加密的,一共需要 16 輪,加密過程如下:
- 將明文進行初始置換(通過置換表)
- 將置換后的數據分為左右 L1 R1 各 32 bit
- 將 48 bit 的子密鑰與 R1 作為輪函數F的輸入
- 將 L1 與輪函數的輸出異或運算,得到 L1密文
- 將 L1 密文與 R1 交換位置,分別作為下一輪的 R2,L2
- 將 2-5 再重復 15 次
- 將 L17 R17 交換位置,并拼接為 64bit 數據
- 將 64bit 數據進行逆初始置換,得到最終密文
需要注意的是:
- 子密鑰在每一輪中都是不一樣的
- 每一輪之間會將左側和右側對調(右側沒有加密)
- 解密的過程就是將輸出用相同的子密鑰再走一遍,如果加密的子密鑰順序是key1 key2 key3,則解密的子密鑰為key3 key2 key1
- 輪函數可以設計為不可逆函數如hash,對解密沒有影響

golang 代碼實戰:
func TestDesEncrypt(t *testing.T) {
key:=[]byte{0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01}
cipherBlock,err:=des.NewCipher(key)
if err!=nil{
t.Error(err)
}
src:=[]byte{0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08}
encrptDst :=make([]byte,len(src))
cipherBlock.Encrypt(encrptDst,src)
t.Log(encrptDst)
plainDst:=make([]byte,len(encrptDst))
cipherBlock.Decrypt(plainDst, encrptDst)
t.Log(plainDst)
}
//out: [206 173 55 61 184 14 171 248]
//out: [1 2 3 4 5 6 7 8]
三重DES
明文:64 bit 密文:64 bit 密鑰:56/64 * 3 bit(加入校驗位的時候為64 bit)
為了增加 DES 的強度,明文經過 3 次 DES 處理后變成最后的密文,因此密鑰長度為 56/64 * 3 bit。3 次 DES 處理并不是簡單的 3 次加密的過程,而是加密、解密、加密,解密的過程相應的就是解密、解密、解密。這樣設計是因為在 3 個密鑰相同時,可以兼容 DES 算法

golang 代碼實戰:
func TestTripleDesEncrypt(t *testing.T) {
key:=[]byte{0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01}
cipherBlock,err:=des.NewTripleDESCipher(key)
if err!=nil{
t.Error(err)
}
src:=[]byte{0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08}
encrptDst :=make([]byte,len(src))
cipherBlock.Encrypt(encrptDst,src)
t.Log(encrptDst)
plainDst:=make([]byte,len(encrptDst))
cipherBlock.Decrypt(plainDst, encrptDst)
t.Log(plainDst)
}
//此處3個密鑰相同,兼容DES
//out: [206 173 55 61 184 14 171 248]
//out: [1 2 3 4 5 6 7 8]
AES
明文:128 bit 密文:128 bit 密鑰:128/192/256 bit (分別需要10/12/14輪)
AES 標準最后評選出的算法是 Rijindale 算法,該算法支持密鑰 128/192/256 bit ,分別需要 10/12/14 輪,本文討論的是 128 bit密鑰。它的加密過程并沒有使用 DES 的 feistel 結構,而是使用了一種新的 SPN 結構,需要 10-14 輪計算,如下圖:

其中每一輪計算過程如下:
- SubBytes(字節替換):以字節大小為索引,與s_box表中字節映射
- ShiftRows(行移位-擴散):從上到下從左到右的順序組成 4 * 4 數組,從 0 行開始,第 n 行向左平移 n 個字節
- MixColums(列混肴-擴散):對每一列進行矩陣運算,共四列
- AddRoundKey(輪密鑰加):與輪密鑰即子密鑰異或運算
需要注意的是:
- 最后一輪沒有列混淆
- 加密時:SubBytes -> ShiftRows -> MixColums -> AddRoundKey 解密時:AddRoundkey -> InvMixColums -> InvShiftRows -> InvSubBytes (Inv代表逆運算)
golang 代碼實戰:
func TestAesEncrypt(t *testing.T){
key:=[]byte{0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01}
cipherBlock,err:=aes.NewCipher(key)
if err!=nil{
t.Error(err)
}
src:=[]byte{0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08}
encrptDst :=make([]byte,len(src))
cipherBlock.Encrypt(encrptDst,src)
t.Log(encrptDst)
plainDst:=make([]byte,len(encrptDst))
cipherBlock.Decrypt(plainDst, encrptDst)
t.Log(plainDst)
}
//out [19 7 34 196 163 153 225 186 223 245 40 131 80 80 70 203]
//out [1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8]
迭代模式
以上討論的三種加密算法都是分組密碼,每次只能處理特定長度的一塊數據,例如 DES 和 3DES 能處理的這塊數據長度為 8 bytes,AES 的為 16 bytes。而我們的日常需要加密的明文基本上都是大于這個長度,這就需要我們將明文的內容進行分組并迭代加密,這個迭代加密的方式就是模式。
ECB 模式
電子密碼本模式(electronic codebook ),最簡單的模式,將明文分組直接作為加密算法的輸入,加密算法的輸出直接作為密文分組。
CBC 模式
密文分組鏈接模式(Cipher Block Chaining),密文之間是鏈狀的,明文分組跟上個密文分組異或之后作為加密算法的輸入,加密算法的輸出作為密文分組。第一個明文分組加密時需要一個初始化向量。
CFB 模式
密文反饋模式(Cipher FeedBack),上一個密文分組作為下一個加密算法的輸入,加密算法的輸出與明文分組異或結果作為密文分組。同樣需要一個初始化向量
OFB 模式
輸出反饋模式(OutPut FeedBack),上一個加密算法的輸出作為下一個加密算法的輸入,明文與加密算法的輸出異或作為密文分組。需要初始化向量
CTR 模式
計數器模式(Counter),將計數器作為加密算法的輸入,加密算法的輸出與明文分組異或作為密文分組,計數器是累加的。需要一個初始的計數器值
以上各種模式,ECB 不推薦使用
golang 代碼實戰:
func TestCBCMode(t *testing.T) {
key:=[]byte{0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01}
cipherBlock,err:=aes.NewCipher(key)
if err!=nil{
t.Error(err)
}
src:=[]byte{0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08}
inv:=[]byte{0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08}
cbcEncrypter:=cipher.NewCBCEncrypter(cipherBlock,inv)
encrptDst :=make([]byte,len(src))
cbcEncrypter.CryptBlocks(encrptDst,src)
t.Log(encrptDst)
plainDst:=make([]byte,len(encrptDst))
cbcDecrypter:=cipher.NewCBCDecrypter(cipherBlock,inv)
cbcDecrypter.CryptBlocks(plainDst,encrptDst)
t.Log(plainDst)
}
//out [182 174 175 250 117 45 192 139 81 99 151 49 118 26 237 0 98 117 59 208 145 166 116 62 43 199 115 70 250 251 56 226]
//out [1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8]