crypto 加解密库简介与测试【GO 常用的库】

〇、前言

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

相关推荐
童先生2 小时前
Go 项目中实现类似 Java Shiro 的权限控制中间件?
开发语言·go
幼儿园老大*3 小时前
走进 Go 语言基础语法
开发语言·后端·学习·golang·go
架构师那点事儿9 小时前
golang 用unsafe 无所畏惧,但使用不得到会panic
架构·go·掘金技术征文
于顾而言1 天前
【笔记】Go Coding In Go Way
后端·go
qq_172805591 天前
GIN 反向代理功能
后端·golang·go
follycat1 天前
2024强网杯Proxy
网络·学习·网络安全·go
OT.Ter1 天前
【力扣打卡系列】单调栈
算法·leetcode·职场和发展·go·单调栈
探索云原生1 天前
GPU 环境搭建指南:如何在裸机、Docker、K8s 等环境中使用 GPU
ai·云原生·kubernetes·go·gpu
OT.Ter2 天前
【力扣打卡系列】移动零(双指针)
算法·leetcode·职场和发展·go
码财小子2 天前
k8s 集群中 Golang pprof 工具的使用
后端·kubernetes·go