Golang與對稱加密

1、對稱加密介紹
對稱加密演算法用來對敏感資料等資訊進行加密,常用的演算法包括:
- DES(Data Encryption Standard):資料加密標準,速度較快,適用於加密大量資料的場合
- 3DES(Triple DES):是基於
DES
,對一塊資料用三個不同的金鑰進行三次加密,強度更高 - AES(Advanced Encryption Standard):高階加密標準,是下一代的加密演算法標準,速度快,安全級別高
- CBC 分組加密的四種模式之一
ECB
、CBC
、CFB
、OFB
對稱加密又分為分組加密和序列密碼
-
分組密碼,也叫塊加密
block cyphers
,一次加密明文中的一個塊。是將明文按一定的位長分組,明文組經過加密運算得到密文組,密文組經過解密運算(加密運算的逆運算),還原成明文組 -
序列密碼,也叫流加密
stream cyphers
,一次加密明文中的一個位。是指利用少量的金鑰(制亂元素)通過某種複雜的運算(密碼演算法)產生大量的偽隨機位流,用於對明文位流的加密
對稱加密的特點
-
加密過程每一步都是可逆的
-
加密和解密用的是同一組金鑰
2、DES
2.1 概述
DES( Data Encryption Standard
)資料加密標準,是目前最為流行的加密演算法之一
DES是一種使用金鑰加密的塊演算法, 1977
年被美國聯邦政府的國家標準局確定為聯邦資料處理標準 FIPS
,並授權在非密級政府通訊中使用,隨後該演算法在國際上廣泛流傳開來
AES與3DES的比較
演算法名稱 | 演算法型別 | 金鑰長度 | 速度 | 解密時間(建設機器每秒嘗試255個金鑰) | 資源消耗 |
---|---|---|---|---|---|
AES | 對稱block密碼 | 128、192、256位 | 高 | 1490000億年 | 低 |
3DES | 對稱feistel密碼 | 112位或168位 | 低 | 46億年 | 中 |
破解歷史
歷史上有三次對 DES
有影響的攻擊實驗。 1997
年,利用當時各國 7
萬臺計算機,歷時 96
天破解了 DES
的金鑰。 1998
年,電子邊境基金會(EFF)用 25
萬美元製造的專用計算機,用 56
小時破解了 DES
的金鑰。1999年, EFF
用 22
小時 15
分完成了破解工作
2.2 主要思路
對原始資料(明文)進行分組,每組 64
位 bit
,最後一組不足 64
位時按一定規則填充,每一組上單獨施加 DES
演算法
2.3 DES子金鑰生成
- 第一步
初始金鑰 64
位,實際有效位 56
位,每隔 7
位有一個校驗位
根據初始金鑰生成 16
個 48
位的字金鑰
金鑰置換(打散),64——>56

例如,第 57
位放在第 1
個位置,第 49
位放在第 2
個位置,將順序打亂並去除了校驗位
- 第二步
左旋右旋,再次置換56——>48

2.4 DES加密過程
明文——>初始置換——>L0( 32
位)、R0( 32
位)

S
盒替換的邏輯
輸入 48
位,輸出 32
位,各分為 8
組,輸入每組 6
位,輸出每組 4
位
分別在每組上施加 S
盒替換,一共 8
個 S
盒

合併
L16( 32
位)、R16( 32
位)——>合併——>最終置換——>密文( 64
位)
2.5 使用示例
/DesEncrypt DES加密 //金鑰必須是64位,所以key必須是長度為8的byte陣列 func DesEncrypt(text string, key []byte) (string, error) { if len(key) != 8 { return "", fmt.Errorf("DES加密演算法要求key必須是64位bit") } block, err := des.NewCipher(key) //用des建立一個加密器cipher if err != nil { return "", err } src := []byte(text) blockSize := block.BlockSize() //分組的大小,blockSize=8 src = common.ZeroPadding(src, blockSize) //填充成64位整倍數 out := make([]byte, len(src)) //密文和明文的長度一致 dst := out for len(src) > 0 { //分組加密 block.Encrypt(dst, src[:blockSize]) //對src進行加密,加密結果放到dst裡 //移到下一組 src = src[blockSize:] dst = dst[blockSize:] } return hex.EncodeToString(out), nil } //DesDecrypt DES解密 //金鑰必須是64位,所以key必須是長度為8的byte陣列 func DesDecrypt(text string, key []byte) (string, error) { src, err := hex.DecodeString(text) //轉成[]byte if err != nil { return "", err } block, err := des.NewCipher(key) if err != nil { return "", err } blockSize := block.BlockSize() out := make([]byte, len(src)) dst := out for len(src) > 0 { //分組解密 block.Decrypt(dst, src[:blockSize]) src = src[blockSize:] dst = dst[blockSize:] } out = common.ZeroUnPadding(out) //反填充 return string(out), nil }
2.6 分組模式
- CBC(Cipher Block Chaining)密文分組連結模式,將當前明文分組與前一個密文分組進行異或運算,然後再進行加密
- 其他分組模式還有ECB、CTR、CFR、OFB
分組模式使用示例
func DesEncryptCBC(text string, key []byte) (string, error) { src := []byte(text) block, err := des.NewCipher(key) //用des建立一個加密器cipher if err != nil { return "", err } blockSize := block.BlockSize() //分組的大小,blockSize=8 src = common.ZeroPadding(src, blockSize) //填充 out := make([]byte, len(src)) //密文和明文的長度一致 encrypter := cipher.NewCBCEncrypter(block, key) //CBC分組模式加密 encrypter.CryptBlocks(out, src) return hex.EncodeToString(out), nil } func DesDecryptCBC(text string, key []byte) (string, error) { src, err := hex.DecodeString(text) //轉成[]byte if err != nil { return "", err } block, err := des.NewCipher(key) if err != nil { return "", err } out := make([]byte, len(src)) //密文和明文的長度一致 encrypter := cipher.NewCBCDecrypter(block, key) //CBC分組模式解密 encrypter.CryptBlocks(out, src) out = common.ZeroUnPadding(out) //反填充 return string(out), nil }
3、AES
AES( Advanced Encryption Standard
)高階加密標準,旨在取代 DES
2000
年 10
月, NIST
(美國國家標準和技術協會)宣佈通過從 15
種侯選演算法中選出的一項新的密匙加密標準。 Rijndael
被選中成為將來的 AES
。 Rijndael
是在 1999
年下半年,由研究員 Joan Daemen
和 Vincent Rijmen
建立的。 AES
正日益成為加密各種形式的電子資料的實際標準
並於 2002
年 5
月 26
日製定了新的高階加密標準 AES
規範
演算法原理
AES
演算法基於排列和置換運算。排列是對資料重新進行安排,置換是將一個數據單元替換為另一個。 AES
使用幾種不同的方法來執行排列和置換運算。
AES
是一個迭代的、對稱金鑰分組的密碼,它可以使用 128
、 192
和 256
位金鑰,並且用 128
位( 16
位元組)分組加密和解密資料。與公共金鑰密碼使用金鑰對不同,對稱金鑰密碼使用相同的金鑰加密和解密資料。通過分組密碼返回的加密資料的位數與輸入資料相同。迭代加密使用一個迴圈結構,在該迴圈中重複置換和替換輸入資料
綜上看來 AES
安全度最高, 基本現狀就是 AES
已經替代 DES
成為新一代對稱加密的標準
AES
使用示例
package main import ( "crypto/aes" "crypto/cipher" "fmt" ) var commonIV = []byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f} func encrypt(plainText string, keyText string) (cipherByte []byte, err error) { // 轉換成位元組資料, 方便加密 plainByte := []byte(plainText) keyByte := []byte(keyText) // 建立加密演算法aes c, err := aes.NewCipher(keyByte) if err != nil { return nil, err } //加密字串 cfb := cipher.NewCFBEncrypter(c, commonIV) cipherByte = make([]byte, len(plainByte)) cfb.XORKeyStream(cipherByte, plainByte) return } func decrypt(cipherByte []byte, keyText string) (plainText string, err error) { // 轉換成位元組資料, 方便加密 keyByte := []byte(keyText) // 建立加密演算法aes c, err := aes.NewCipher(keyByte) if err != nil { return "", err } // 解密字串 cfbdec := cipher.NewCFBDecrypter(c, commonIV) plainByte := make([]byte, len(cipherByte)) cfbdec.XORKeyStream(plainByte, cipherByte) plainText = string(plainByte) return } func main() { plain := "The text need to be encrypt." // AES 規定有3種長度的key: 16, 24, 32分別對應AES-128, AES-192, or AES-256 key := "abcdefgehjhijkmlkjjwwoew" // 加密 cipherByte, err := encrypt(plain, key) if err != nil { fmt.Println(err) } fmt.Printf("%s ==> %x\n", plain, cipherByte) // 解密 plainText, err := decrypt(cipherByte, key) if err != nil { fmt.Println(err) } fmt.Printf("%x ==> %s\n", cipherByte, plainText) }
4、CBC
分組密碼,也叫塊加密 block cyphers
,一次加密明文中的一個塊。是將明文按一定的位長分組,明文組經過加密運算得到密文組,密文組經過解密運算(加密運算的逆運算),還原成明文組。
序列密碼,也叫流加密 stream cyphers
,一次加密明文中的一個位。是指利用少量的金鑰(制亂元素)通過某種複雜的運算(密碼演算法)產生大量的偽隨機位流,用於對明文位流的加密。
解密是指用同樣的金鑰和密碼演算法及與加密相同的偽隨機位流,用以還原明文位流
分組加密演算法中,有 ECB
, CBC
, CFB
, OFB
這幾種演算法模式, 我們介紹其中常用的一種 CBC
CBC
( Cipher Block Chaining
)密文分組連結方式
加密步驟如下:
- 首先將資料按照8個位元組一組進行分組得到
D1D2......Dn
(若資料不是8的整數倍,用指定的PADDING
資料補位) - 第一組資料
D1
與初始化向量I異或後的結果進行DES
加密得到第一組密文C1
(初始化向量I為全零) - 第二組資料
D2
與第一組的加密結果C1
異或以後的結果進行DES
加密,得到第二組密文C2
- 之後的資料以此類推,得到
Cn
- 按順序連為
C1C2C3......Cn
即為加密結果
// aesCBCEncrypt aes加密,填充祕鑰key的16位,24,32分別對應AES-128, AES-192, or AES-256. func aesCBCEncrypt(rawData, key []byte) ([]byte, error) { block, err := aes.NewCipher(key) if err != nil { return nil, err } //填充原文 blockSize := block.BlockSize() rawData = pkcs7Padding(rawData, blockSize) //初始向量IV必須是唯一,但不需要保密 cipherText := make([]byte, blockSize+len(rawData)) //block大小 16 iv := cipherText[:blockSize] if _, err := io.ReadFull(rand.Reader, iv); err != nil { return nil, err } //block大小和初始向量大小一定要一致 mode := cipher.NewCBCEncrypter(block, iv) mode.CryptBlocks(cipherText[blockSize:], rawData) return cipherText, nil }
解密是加密的逆過程,步驟如下:
- 首先將資料按照
8
個位元組一組進行分組得到C1C2C3......Cn
- 將第一組資料進行解密後與初始化向量
I
進行異或得到第一組明文D1
(注意:一定是先解密再異或) - 將第二組資料
C2
進行解密後與第一組密文資料進行異或得到第二組資料D2
- 之後依此類推,得到
Dn
- 按順序連為
D1D2D3......Dn
即為解密結果
func aesCBCDecrypt(encryptData, key []byte) ([]byte, error) { block, err := aes.NewCipher(key) if err != nil { return nil, err } blockSize := block.BlockSize() if len(encryptData) < blockSize { return nil, errors.New("ciphertext too short") } iv := encryptData[:blockSize] encryptData = encryptData[blockSize:] // CBC mode always works in whole blocks. if len(encryptData)%blockSize != 0 { return nil, errors.New("ciphertext is not a multiple of the block size") } mode := cipher.NewCBCDecrypter(block, iv) // CryptBlocks can work in-place if the two arguments are the same. mode.CryptBlocks(encryptData, encryptData) //解填充 encryptData = pkcs7UnPadding(encryptData) return encryptData, nil }
這裡要注意的是,解密的結果並不一定是我們原來的加密資料,可能還含有補位,一定要把補位去掉才是原來的資料
特點:
- 不容易主動攻擊,安全性好於
ECB
,適合傳輸長度長的報文,是SSL
、IPSec
的標準。每個密文塊依賴於所有的資訊塊, 明文訊息中一個改變會影響所有密文塊 - 傳送方和接收方都需要知道初始化向量
- 加密過程是序列的,無法被並行化(在解密時,從兩個鄰接的密文塊中即可得到一個平文塊。因此,解密過程可以被並行化)
See you ~
- Gradle打包工具入門
- 服務網格和Istio初識-續
- 服務網格和Istio初識
- Golang與非對稱加密
- ack叢集Terway網路場景下的vSwitch擴容
- Golang與對稱加密
- 基於ack k8s叢集排程的方案設計
- 基於Dockerfile構建容器映象的最佳實踐
- Golang反射-下篇
- Golang反射-上篇
- Azure DevOps的使用入門
- Golang介面型別-下篇
- Golang介面型別-上篇
- 基於Python實現原生的登入驗證碼
- Golang開發命令列工具之flag包的使用
- Golang檔案操作-下篇
- k8s環境下處理容器時間問題的多種姿勢
- Golang基準測試
- 淺談Prometheus的資料儲存
- Golang單元測試