crypto 加解密庫簡介與測試【GO 常用的庫】

2023-09-15 06:00:59

〇、前言

GO 語言的標準庫 crypto 提供了一組用於加密和解密的包,包括對稱加密、雜湊函數、數位簽章、亂數生成等功能。在日常開發中,通過使用這些庫,可以確保資料的安全性和完整性。

對於使用頻率比較高的東西,還是要彙總一下用來備查。

一、md5 加密

md5 包實現了 RFC 1321 中定義的 MD5 雜湊演演算法。

需要注意的是,md5 是可以通過暴力碰撞破解的,因此不可用於安全性要求較高的場景。

package main

import (
	"crypto/md5"
	"encoding/hex"
	"fmt"
	"io"
	"strings"
)

func main() {
	h := md5.New()
	io.WriteString(h, "md5 加密測試!")
	fmt.Printf("%x\n", h.Sum(nil))
	fmt.Printf("%X\n", h.Sum(nil)) // 大寫的 X,代表大寫的十六進位制字串

	hh := md5.New()
	hh.Write([]byte("md5 加密測試!"))
	fmt.Print(hex.EncodeToString(hh.Sum(nil)) + "\n")
    fmt.Print(strings.ToTitle(hex.EncodeToString(hh.Sum(nil)))) // strings.ToTitle() 轉大寫
}

二、sha256 字串、檔案加密

sha1 和 md5 類似,都是可以被暴力碰撞破解,因此首先推薦的就是本節主要介紹的 sha256,結果為 64 位十六進位制的字串。

其實 sha1、sha256、sha512 等等用法都是類似的,均適用如下程式碼實現方式:

package main

import (
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"io"
	"log"
	"os"
	"strings"
)

func main() {
	// 對字串加密,方式一
	h := sha256.New()
	h.Write([]byte("sha256 加密測試!"))
	fmt.Printf("%x\n", h.Sum(nil))
	fmt.Printf("%X\n", h.Sum(nil)) // 大寫的 X,代表大寫的十六進位制字串

	// 對字串加密,方式二
	hh := sha256.New()
	hh.Write([]byte("sha256 加密測試!"))
	fmt.Print(hex.EncodeToString(hh.Sum(nil)) + "\n")
	fmt.Print(strings.ToTitle(hex.EncodeToString(hh.Sum(nil))) + "\n")

	// 對檔案進行加密
	f, err := os.Open("file.txt")
	if err != nil {
		log.Fatal(err)
	}
	defer f.Close()
	h2 := sha256.New()
	if _, err := io.Copy(h2, f); err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%x\n", h2.Sum(nil))
	fmt.Printf("%X", h2.Sum(nil))
}

三、rsa 加密解密以及金鑰生成

RSA 是一種非對稱加密演演算法,它的名字是由它的三位開發者,即 Ron.Rivest、Adi.Shamir 和 Leonard.Adleman 的姓氏的首字母組成的(Rivest-Shamir-Adleman),可用於資料加密和數位簽章。

用於資料加密時,訊息傳送方利用對方的公鑰進行加密,訊息接受方收到密文時使用自己的私鑰進行解密。

 如下程式碼,包含了生成金鑰和加解密:(其中包含了 byte 型別和 base64 型別互相轉換的操作)

package main

import (
	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"encoding/base64"
	"encoding/pem"
	"fmt"
	"os"
)

func main() {
	// 生成金鑰對,儲存到檔案
	GenerateRSAKey(2048)
	message := []byte("rsa 加密解密測試!")
	// 加密
	cipherText := RSA_Encrypt(message, "public.pem")
	cipherText_base64 := base64.StdEncoding.EncodeToString(cipherText) // 將 []byte 型別的密文轉換為 base64 字串
	fmt.Println("加密後為(base64):", cipherText_base64)
	fmt.Println("加密後為([]byte):", cipherText)
	// 解密
	cipherText, _ = base64.StdEncoding.DecodeString(cipherText_base64) // 若轉換為輸入為 base64 字串,則需先解碼為 []byte
	plainText := RSA_Decrypt(cipherText, "private.pem")
	fmt.Println("解密後為([]byte):", plainText)
	fmt.Println("解密後為(string):", string(plainText))
}

// 生成 RSA 私鑰和公鑰,儲存到檔案中
func GenerateRSAKey(bits int) {
	// GenerateKey 函數使用亂資料生成器 random 生成一對具有指定字位數的 RSA 金鑰
	// Reader 是一個全域性、共用的密碼用強亂數生成器
	privateKey, err := rsa.GenerateKey(rand.Reader, bits)
	if err != nil {
		panic(err)
	}
	// 儲存私鑰
	// 通過 x509 標準將得到的 ras 私鑰序列化為 ASN.1 的 DER 編碼字串
	X509PrivateKey := x509.MarshalPKCS1PrivateKey(privateKey)
	// 使用 pem 格式對 x509 輸出的內容進行編碼
	// 建立檔案儲存私鑰
	privateFile, err := os.Create("private.pem")
	if err != nil {
		panic(err)
	}
	defer privateFile.Close()
	// 構建一個 pem.Block 結構體物件
	privateBlock := pem.Block{Type: "RSA Private Key", Bytes: X509PrivateKey}
	// 將資料儲存到檔案
	pem.Encode(privateFile, &privateBlock)
	// 儲存公鑰
	// 獲取公鑰的資料
	publicKey := privateKey.PublicKey
	// X509 對公鑰編碼
	X509PublicKey, err := x509.MarshalPKIXPublicKey(&publicKey)
	if err != nil {
		panic(err)
	}
	// pem 格式編碼
	// 建立用於儲存公鑰的檔案
	publicFile, err := os.Create("public.pem")
	if err != nil {
		panic(err)
	}
	defer publicFile.Close()
	// 建立一個 pem.Block 結構體物件
	publicBlock := pem.Block{Type: "RSA Public Key", Bytes: X509PublicKey}
	// 儲存到檔案
	pem.Encode(publicFile, &publicBlock)
}

// RSA 加密
func RSA_Encrypt(plainText []byte, path string) []byte {
	// 開啟檔案
	file, err := os.Open(path)
	if err != nil {
		panic(err)
	}
	defer file.Close()
	// 讀取檔案的內容
	info, _ := file.Stat()
	buf := make([]byte, info.Size())
	file.Read(buf)
	// pem 解碼
	block, _ := pem.Decode(buf)
	// x509 解碼
	publicKeyInterface, err := x509.ParsePKIXPublicKey(block.Bytes)
	if err != nil {
		panic(err)
	}
	// 型別斷言
	publicKey := publicKeyInterface.(*rsa.PublicKey)
	// 對明文進行加密
	cipherText, err := rsa.EncryptPKCS1v15(rand.Reader, publicKey, plainText)
	if err != nil {
		panic(err)
	}
	// 返回 []byte 密文
	return cipherText
}

// RSA 解密
func RSA_Decrypt(cipherText []byte, path string) []byte {
	// 開啟檔案
	file, err := os.Open(path)
	if err != nil {
		panic(err)
	}
	defer file.Close()
	// 獲取檔案內容
	info, _ := file.Stat()
	buf := make([]byte, info.Size())
	file.Read(buf)
	// pem 解碼
	block, _ := pem.Decode(buf)
	// X509 解碼
	privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
	if err != nil {
		panic(err)
	}
	// 對密文進行解密
	plainText, _ := rsa.DecryptPKCS1v15(rand.Reader, privateKey, cipherText)
	// 返回明文
	return plainText
}

執行生成金鑰方法後,main.go 的同級目錄下,回自動生成兩個 .pem 字尾的金鑰檔案:

下面是加解密輸出的結果:

 

 參考:https://blog.csdn.net/chenxing1230/article/details/83757638

四、sm2 加解密以及簽名驗證

加密庫 crypto 中其實是不包含 sm2 的,但是它畢竟是國家公鑰密碼演演算法的標準,有很廣的使用度,因此必須安排。

國密 SM2 為非對稱加密,也稱為公鑰密碼。它是我國在吸收國際先進成果基礎上研發出來的具有自主智慧財產權的 ECC(橢圓曲線公鑰密碼演演算法),它在安全性和實現效率方面相當於或略優於國際上同類的 ECC 演演算法,能取代 RSA 以滿足各種應用對公鑰密碼演演算法安全性和實現效率的更高要求,這也是國家主推此加密方法的原因。

以下範例一開源庫 https://github.com/tjfoc/gmsm 為基礎實現:

// 安裝必要的模組
go get github.com/tjfoc/gmsm/sm2
go get github.com/tjfoc/gmsm/x509
// 整理全部模組
go mod tidy
package main

import (
	"crypto/rand"
	"encoding/base64"
	"fmt"
	"os"

	"github.com/tjfoc/gmsm/sm2"
	"github.com/tjfoc/gmsm/x509"
)

type GMCrypt struct {
	PublicFile  string
	PrivateFile string
}

var (
	path        = "./"
	privateFile = "sm2private.pem" // 私鑰檔案
	publicFile  = "sm2public.pem"  // 公鑰檔案
	data        = "hello 國密"
)

// 測試一下
func main() {
	GenerateSM2Key() // 金鑰生成並儲存在檔案中
	crypt := GMCrypt{
		PublicFile:  path + publicFile,
		PrivateFile: path + privateFile,
	}
	encryptText, _ := crypt.Encrypt(data) // 加密
	fmt.Println(encryptText)
	decryptText, _ := crypt.Decrypt(encryptText) // 解密
	fmt.Println(decryptText)

	msg := []byte("hello 國密")
	sig, key, _ := CreateSm2Sig(msg) // 簽名
	fmt.Printf("簽名結果:%x\n公鑰:%v, \n", sig, key)
	verSm2Sig := VerSm2Sig(key, msg, sig) // 驗證簽名
	fmt.Println("驗證結果為:", verSm2Sig)
}

// 生成公鑰、私鑰
func GenerateSM2Key() {
	// 生成私鑰、公鑰
	priKey, err := sm2.GenerateKey(rand.Reader)
	if err != nil {
		fmt.Println("祕鑰產生失敗:", err)
		os.Exit(1)
	}
	pubKey := &priKey.PublicKey
	// 生成檔案 儲存私鑰、公鑰
	// x509 編碼
	pemPrivKey, _ := x509.WritePrivateKeyToPem(priKey, nil)
	privateFile, _ := os.Create(path + privateFile)
	defer privateFile.Close()
	privateFile.Write(pemPrivKey)
	pemPublicKey, _ := x509.WritePublicKeyToPem(pubKey)
	publicFile, _ := os.Create(path + publicFile)
	defer publicFile.Close()
	publicFile.Write(pemPublicKey)
}
// 讀取金鑰檔案
func readPemCxt(path string) ([]byte, error) {
	file, err := os.Open(path)
	if err != nil {
		return []byte{}, err
	}
	defer file.Close()
	fileInfo, err := file.Stat()
	if err != nil {
		return []byte{}, err
	}
	buf := make([]byte, fileInfo.Size())
	_, err = file.Read(buf)
	if err != nil {
		return []byte{}, err
	}
	return buf, err
}
// 加密
func (s *GMCrypt) Encrypt(data string) (string, error) {
	pub, err := readPemCxt(s.PublicFile)
	if err != nil {
		return "", err
	}
	// read public key
	publicKeyFromPem, err := x509.ReadPublicKeyFromPem(pub)
	if err != nil {
		fmt.Println(err)
		return "", err
	}
	ciphertxt, err := publicKeyFromPem.EncryptAsn1([]byte(data), rand.Reader)
	if err != nil {
		return "", err
	}
	return base64.StdEncoding.EncodeToString(ciphertxt), nil
}
// 解密
func (s *GMCrypt) Decrypt(data string) (string, error) {
	pri, err := readPemCxt(s.PrivateFile)
	if err != nil {
		return "", err
	}
	privateKeyFromPem, err := x509.ReadPrivateKeyFromPem(pri, nil)
	if err != nil {
		return "", err
	}
	ciphertxt, err := base64.StdEncoding.DecodeString(data)
	if err != nil {
		return "", err
	}
	plaintxt, err := privateKeyFromPem.DecryptAsn1(ciphertxt)
	if err != nil {
		return "", err
	}
	return string(plaintxt), nil
}
// 使用私鑰建立簽名
func CreateSm2Sig(msg []byte) ([]byte, *sm2.PublicKey, error) {
	// 讀取金鑰
	pri, _ := readPemCxt(path + privateFile)
	privateKey, _ := x509.ReadPrivateKeyFromPem(pri, nil)
	c := sm2.P256Sm2() // 橢圓曲線
	priv := new(sm2.PrivateKey)
	priv.PublicKey.Curve = c
	priv.D = privateKey.D
	priv.PublicKey.X = privateKey.X
	priv.PublicKey.Y = privateKey.Y
	sign, err := priv.Sign(rand.Reader, msg, nil) // sm2簽名
	if err != nil {
		return nil, nil, err
	}
	return sign, &priv.PublicKey, err
}
// 驗證簽名
func VerSm2Sig(pub *sm2.PublicKey, msg []byte, sign []byte) bool {
	isok := pub.Verify(msg, sign)
	return isok
}

參考:https://blog.csdn.net/weixin_42117918/article/details/130558002

五、aes 加解密

AES 密碼與分組密碼 Rijndael 基本上完全一致,Rijndael 分組大小和金鑰大小都可以為 128 位、192 位和256 位。然而 AES 只要求分組大小為 128 位,因此只有分組長度為 128Bit 的 Rijndael 才稱為 AES 演演算法。但這並不妨礙三種密碼的相同使用環境。

5.1 對字串加解密

package main

import (
	"bytes"
	"crypto/aes"
	"crypto/cipher"
	"encoding/base64"
	"errors"
	"fmt"
)

// 測試一下
func main() {
	data := "aes 加密測試!"
	// 加密
	enstr, _ := EncryptByAes([]byte(data))
	// 解密
	destr, _ := DecryptByAes(enstr)
	// 列印
	fmt.Printf(" 加密:%v\n 解密:%s", enstr, destr)
}

var PwdKey = []byte("ABCDABCDABCDABCD") // 三種密碼標準:128 位、192 位和 256 位,對應字串位數 16、32、64
// EncryptByAes Aes加密 後 base64 再加
func EncryptByAes(data []byte) (string, error) {
	res, err := AesEncrypt(data, PwdKey)
	if err != nil {
		return "", err
	}
	return base64.StdEncoding.EncodeToString(res), nil
}

// DecryptByAes Aes 解密
func DecryptByAes(data string) ([]byte, error) {
	dataByte, err := base64.StdEncoding.DecodeString(data)
	if err != nil {
		return nil, err
	}
	return AesDecrypt(dataByte, PwdKey)
}

// 加密
func AesEncrypt(data []byte, key []byte) ([]byte, error) {
	// 建立加密範例
	block, err := aes.NewCipher(key)
	if err != nil {
		return nil, err
	}
	// 判斷加密快的大小
	blockSize := block.BlockSize()
	// 填充
	encryptBytes := pkcs7Padding(data, blockSize)
	// 初始化加密資料接收切片
	crypted := make([]byte, len(encryptBytes))
	// 使用cbc加密模式
	blockMode := cipher.NewCBCEncrypter(block, key[:blockSize]) // Cipher Block Chaining:加密塊鏈
	// 執行加密
	blockMode.CryptBlocks(crypted, encryptBytes)
	return crypted, nil
}

// 解密
func AesDecrypt(data []byte, key []byte) ([]byte, error) {
	// 建立範例
	block, err := aes.NewCipher(key)
	if err != nil {
		return nil, err
	}
	// 獲取塊的大小
	blockSize := block.BlockSize()
	// 使用cbc
	blockMode := cipher.NewCBCDecrypter(block, key[:blockSize]) // Cipher Block Chaining:加密塊鏈
	// 初始化解密資料接收切片
	crypted := make([]byte, len(data))
	// 執行解密
	blockMode.CryptBlocks(crypted, data)
	// 去除填充
	crypted, err = pkcs7UnPadding(crypted)
	if err != nil {
		return nil, err
	}
	return crypted, nil
}

// pkcs7Padding 填充
func pkcs7Padding(data []byte, blockSize int) []byte {
	// 判斷缺少幾位長度。最少1,最多 blockSize
	padding := blockSize - len(data)%blockSize
	// 補足位數。把切片[]byte{byte(padding)}複製padding個
	padText := bytes.Repeat([]byte{byte(padding)}, padding)
	return append(data, padText...)
}

// pkcs7UnPadding 填充的反向操作
func pkcs7UnPadding(data []byte) ([]byte, error) {
	length := len(data)
	if length == 0 {
		return nil, errors.New("加密字串錯誤!")
	}
	// 獲取填充的個數
	unPadding := int(data[length-1])
	return data[:(length - unPadding)], nil
}

5.2 對檔案加解密

 此部分是在字串加解密基礎上,先讀取檔案內容,然後和加密字串一樣,將 []byte 型別的資料加密即可。

 對於單行超大文字檔案,加密時就需要分片去讀,加密後字串寫入檔案中,每次加密寫入一行,一定要換行,不然解密的時候區分不出來。非單行的可以逐行加密,密文也是逐行寫入文字中。解密時,逐行讀取解密檔案,每一行為一個密文字串,將其解密,寫入到文字中。

package main

import (
	"bufio"
	"bytes"
	"crypto/aes"
	"crypto/cipher"
	"encoding/base64"
	"errors"
	"fmt"
	"os"
	"time"
)

func main() {
	startTime := time.Now()
	// EncryptFile("file.txt", "secuityfile.txt")
	DecryptFile("encryptFile_secuityfile.txt", "file_new.txt")
	fmt.Printf("耗時:%v", time.Since(startTime))
}

var PwdKey = []byte("ABCDABCDABCDABCD") // 三種密碼標準:128 位、192 位和 256 位,對應字串位數 16、32、64

// EncryptByAes Aes加密 後 base64 再加
func EncryptByAes(data []byte) (string, error) {
	res, err := AesEncrypt(data, PwdKey)
	if err != nil {
		return "", err
	}
	return base64.StdEncoding.EncodeToString(res), nil
}

// DecryptByAes Aes 解密
func DecryptByAes(data string) ([]byte, error) {
	dataByte, err := base64.StdEncoding.DecodeString(data)
	if err != nil {
		return nil, err
	}
	return AesDecrypt(dataByte, PwdKey)
}

// 加密
func AesEncrypt(data []byte, key []byte) ([]byte, error) {
	// 建立加密範例
	block, err := aes.NewCipher(key)
	if err != nil {
		return nil, err
	}
	// 判斷加密快的大小
	blockSize := block.BlockSize()
	// 填充
	encryptBytes := pkcs7Padding(data, blockSize)
	// 初始化加密資料接收切片
	crypted := make([]byte, len(encryptBytes))
	// 使用cbc加密模式
	blockMode := cipher.NewCBCEncrypter(block, key[:blockSize]) // Cipher Block Chaining:加密塊鏈
	// 執行加密
	blockMode.CryptBlocks(crypted, encryptBytes)
	return crypted, nil
}

// 解密
func AesDecrypt(data []byte, key []byte) ([]byte, error) {
	// 建立範例
	block, err := aes.NewCipher(key)
	if err != nil {
		return nil, err
	}
	// 獲取塊的大小
	blockSize := block.BlockSize()
	// 使用cbc
	blockMode := cipher.NewCBCDecrypter(block, key[:blockSize]) // Cipher Block Chaining:加密塊鏈
	// 初始化解密資料接收切片
	crypted := make([]byte, len(data))
	// 執行解密
	blockMode.CryptBlocks(crypted, data)
	// 去除填充
	crypted, err = pkcs7UnPadding(crypted)
	if err != nil {
		return nil, err
	}
	return crypted, nil
}

// pkcs7Padding 填充
func pkcs7Padding(data []byte, blockSize int) []byte {
	// 判斷缺少幾位長度。最少1,最多 blockSize
	padding := blockSize - len(data)%blockSize
	// 補足位數。把切片[]byte{byte(padding)}複製padding個
	padText := bytes.Repeat([]byte{byte(padding)}, padding)
	return append(data, padText...)
}

// pkcs7UnPadding 填充的反向操作
func pkcs7UnPadding(data []byte) ([]byte, error) {
	length := len(data)
	if length == 0 {
		return nil, errors.New("加密字串錯誤!")
	}
	// 獲取填充的個數
	unPadding := int(data[length-1])
	return data[:(length - unPadding)], nil
}

// EncryptFile 檔案加密,filePath 需要加密的檔案路徑 ,fName加密後檔名
func EncryptFile(filePath, fName string) (err error) {
	f, err := os.Open(filePath)
	if err != nil {
		fmt.Println("未找到檔案")
		return
	}
	defer f.Close()
	fInfo, _ := f.Stat()
	fLen := fInfo.Size()
	fmt.Println("待處理檔案大小:", fLen)
	maxLen := 1024 * 1024 * 100 // 100mb  每 100mb 進行加密一次
	var forNum int64 = 0
	getLen := fLen
	if fLen > int64(maxLen) {
		getLen = int64(maxLen)
		forNum = fLen / int64(maxLen)
		fmt.Println("需要加密次數:", forNum+1)
	}
	// 加密後儲存的檔案
	ff, err := os.OpenFile("encryptFile_"+fName, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
	if err != nil {
		fmt.Println("檔案寫入錯誤")
		return err
	}
	defer ff.Close()
	// 迴圈加密,並寫入檔案
	for i := 0; i < int(forNum+1); i++ {
		a := make([]byte, getLen)
		n, err := f.Read(a)
		if err != nil {
			fmt.Println("檔案讀取錯誤")
			return err
		}
		getByte, err := EncryptByAes(a[:n])
		if err != nil {
			fmt.Println("加密錯誤")
			return err
		}
		// 換行處理,有點亂了,想到更好的再改
		getBytes := append([]byte(getByte), []byte("\n")...)
		// 寫入
		buf := bufio.NewWriter(ff)
		buf.WriteString(string(getBytes[:]))
		buf.Flush()
	}
	ffInfo, _ := ff.Stat()
	fmt.Printf("檔案加密成功,生成檔名為:%s,檔案大小為:%v Byte \n", ffInfo.Name(), ffInfo.Size())
	return nil
}

// DecryptFile 檔案解密
func DecryptFile(filePath, fName string) (err error) {
	f, err := os.Open(filePath)
	if err != nil {
		fmt.Println("未找到檔案")
		return
	}
	defer f.Close()
	fInfo, _ := f.Stat()
	fmt.Println("待處理檔案大小:", fInfo.Size())

	br := bufio.NewReader(f)
	ff, err := os.OpenFile("decryptFile_"+fName, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666)
	if err != nil {
		fmt.Println("檔案寫入錯誤")
		return err
	}
	defer ff.Close()
	num := 0
	// 逐行讀取密文,進行解密,寫入檔案
	for {
		num = num + 1
		a, err := br.ReadString('\n')
		if err != nil {
			break
		}
		getByte, err := DecryptByAes(a)
		if err != nil {
			fmt.Println("解密錯誤")
			return err
		}
		buf := bufio.NewWriter(ff)
		buf.Write(getByte)
		buf.Flush()
	}
	fmt.Println("解密次數:", num)
	ffInfo, _ := ff.Stat()
	fmt.Printf("檔案解密成功,生成檔名為:%s,檔案大小為:%v Byte \n", ffInfo.Name(), ffInfo.Size())
	return
}

 

5.3 明文長度與密文長度關係

n 明文長度 密文長度
1 0~15 24
2 16~31 44
3 32~47 64
4 48~63 88
5 64~79 108
6 80~95 128
7 96~111 152
... ... ...
n 16(n-1) ~ (16n-1) 20n+(n/3+1)x4     (/ :除法取整)

參考:https://www.jianshu.com/p/0caab60fea9f    https://cloud.tencent.com/developer/section/1140756