Golang與對稱加密

語言: CN / TW / HK

1、對稱加密介紹

對稱加密算法用來對敏感數據等信息進行加密,常用的算法包括:

  • DES(Data Encryption Standard):數據加密標準,速度較快,適用於加密大量數據的場合
  • 3DES(Triple DES):是基於 DES ,對一塊數據用三個不同的密鑰進行三次加密,強度更高
  • AES(Advanced Encryption Standard):高級加密標準,是下一代的加密算法標準,速度快,安全級別高
  • CBC 分組加密的四種模式之一 ECBCBCCFBOFB

對稱加密又分為分組加密和序列密碼

  • 分組密碼,也叫塊加密 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年, EFF22 小時 15 分完成了破解工作

2.2 主要思路

對原始數據(明文)進行分組,每組 64bit ,最後一組不足 64 位時按一定規則填充,每一組上單獨施加 DES 算法

2.3 DES子密鑰生成

  • 第一步

初始密鑰 64 位,實際有效位 56 位,每隔 7 位有一個校驗位

根據初始密鑰生成 1648 位的字密鑰

密鑰置換(打散),64——>56

例如,第 57 位放在第 1 個位置,第 49 位放在第 2 個位置,將順序打亂並去除了校驗位

  • 第二步

左旋右旋,再次置換56——>48

2.4 DES加密過程

明文——>初始置換——>L0( 32 位)、R0( 32 位)

S 盒替換的邏輯

輸入 48 位,輸出 32 位,各分為 8 組,輸入每組 6 位,輸出每組 4

分別在每組上施加 S 盒替換,一共 8S

合併

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

200010 月, NIST (美國國家標準和技術協會)宣佈通過從 15 種侯選算法中選出的一項新的密匙加密標準。 Rijndael 被選中成為將來的 AESRijndael 是在 1999 年下半年,由研究員 Joan DaemenVincent Rijmen 創建的。 AES 正日益成為加密各種形式的電子數據的實際標準

並於 2002526 日製定了新的高級加密標準 AES 規範

算法原理

AES 算法基於排列和置換運算。排列是對數據重新進行安排,置換是將一個數據單元替換為另一個。 AES 使用幾種不同的方法來執行排列和置換運算。

AES 是一個迭代的、對稱密鑰分組的密碼,它可以使用 128192256 位密鑰,並且用 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 ,適合傳輸長度長的報文,是 SSLIPSec 的標準。每個密文塊依賴於所有的信息塊, 明文消息中一個改變會影響所有密文塊
  • 發送方和接收方都需要知道初始化向量
  • 加密過程是串行的,無法被並行化(在解密時,從兩個鄰接的密文塊中即可得到一個平文塊。因此,解密過程可以被並行化)

See you ~