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 ~