在Go编程中封装AES加解密接口
  vUhc7kqxotqT 2023年11月22日 25 0

1.摘要

在一个中型以上的项目中, 我们一般会在项目工程中开辟一个pkg文件夹用来存放一些基础工具接口,比如:数据库、中间件、加解密算法、基础协议等等。在这篇文章中, 我主要分享一下在基于Go语言的项目中, 加解密算法中如何封装一个通用的加解密接口, 并以使用比较广泛的AES加解密算法实现为基础进行讲解, 最后模拟客户端分别演示调用AES的加密接口和解密接口。

2.工程文件结构

在一个正规项目中, 我们要封装的文件主要添加在算法文件夹下, 目录结构规划如下:

pkg 
| 
---- algorithm        
       |        
       ---- base.go        // 基础接口函数定义
       |        
       ---- aes.go         // aes加解密算法接口
       |        
       ---- aes_test.go    // aes加解密算法接口函数测试

我在名为"algorithm"文件夹下新建了三个文件, 其中base.go为基础接口函数定义, 因为以后可能要加入进来的算法会比较多,因此需要有一个基础类文件来定义通用函数接口。

aes.go文件中主要实现AES算法的加解密过程, 并提供一个对外的初始化接口,方便应用层调用。

aes_test.go是作为单元测试的文件, 在里面可以针对AES加密函数和解密函数写测试用例, 不用编译整个工程实现单元测试。

如果后面有新的算法加入进来, 例如:des算法, 只需要添加一个des.go和des_test.go文件, 在里面实现函数功能即可。

3.基础接口实现

基础接口实现主要在base.go文件中, 因为对于所有加密算法来讲, 都有两个最基础通用的方法:加密函数和解密函数,因此这里定义了两个通用的方法接口:

type IAlgorithm interface {
  Encrypt() // 加密函数接口  
  Decrypt() // 解密函数接口
}

因为现在不知道项目默认需要使用什么算法,因此实现这两个方法的空接口:

type DefaultAlgorithm struct{}

func (dal DefaultAlgorithm) Encrypt() {}

func (dal DefaultAlgorithm) Decrypt() {}

考虑在应用层方便切换不同的算法, 这里需要设计一个管理接口的方法, 首先定义一个结构体:

type AlgorithmManager struct {  
  algorithm IAlgorithm
 }

在这个结构体中, 成员是上面接口名称的对象。

然后我定义了两个方法, 一个是设置算法对象的方法, 另一个是执行算法方式的方法。

首先是设置算法对象的方法:

func (gor *AlgorithmManager) SetAlgorithm(algorithm IAlgorithm) {  
		gor.algorithm = algorithm
}

这个方法会接收一个参数,这个参数就是用户想要调用哪种算法的对象, 只有给接口赋对应算法的对象,接口才知道调用哪个算法的方法。

其次是运行算法类型的方法:

const (
	encryptMode = "encrypt"
	decryptMode = "decrypt"
)

func (gor *AlgorithmManager) RunAlgorithm(runMode string) {
	switch runMode {
	case encryptMode:
		gor.algorithm.Encrypt()
		break
	case decryptMode:
		gor.algorithm.Decrypt()
		break
	}
}

这里我定义了两个模式用来标识加密模式和解密模式, 当给RunAlgorithm传参encryptMode, 则会执行加密函数,反之则执行解密函数。

4.AES加解密算法实现

在AES加解密客户端调用接口中, 我选择了选项设计模式, 用户可以根据加密算法和解密算法参数不同进行灵活的选项传参。

首先定义一个方法结构体:

type AesAlgorithm struct {
	AppAlg            *AlgorithmManager
	EncryptKey        string // 密钥
	PlaintextContent  string // 明文内容
	CiphertextContent string // 密文内容
}

在这个结构体中, 密钥、明文内容、密文内容是我们在使用功能过程中必须传入的参数, 其中还带有一个结构对象指针: *AlgorithmManager, 方便我们将AES算法的对象传给接口,让其调用AES的加密方法或解密方法。

其次定义一个方便客户端调用的接口, 并使用动态选项传参,实现代码如下:

type AesAlgorithmOption func(aes *AesAlgorithm)

// 用户初始化调用并传参
func NewAesAlgorithm(options ...AesAlgorithmOption) *AesAlgorithm {
	aesAlg := &AesAlgorithm{
		AppAlg:            new(AlgorithmManager),
		EncryptKey:        "",
		PlaintextContent:  "",
		CiphertextContent: "",
	}
	for _, option := range options {
		option(aesAlg)
	}
	return aesAlg
}

// 通过该选项函数传入key
func WithEncryptKey(key string) AesAlgorithmOption {
	return func(aes *AesAlgorithm) {
		aes.EncryptKey = key
	}
}

// 通过该选项函数传入明文
func WithPlaintextContent(plainText string) AesAlgorithmOption {
	return func(aes *AesAlgorithm) {
		aes.PlaintextContent = plainText
	}
}

// 通过该选项函数传入密文
func WithCiphertextContent(cipherContent string) AesAlgorithmOption {
	return func(aes *AesAlgorithm) {
		aes.CiphertextContent = cipherContent
	}
}

下面我们还实现了两个内部函数,分别是加密和解密过程中需要填充块的实现方法,代码如下:

加密填充块:

func pkcs5Padding(cipherText []byte, blockSize int) []byte {
	padding := blockSize - len(cipherText)%blockSize
	padtext := bytes.Repeat([]byte{byte(padding)}, padding)
	return append(cipherText, padtext...)
}

解密填充块:

func pkcs5UnPadding(origData []byte) []byte {
	length := len(origData)
	unpadding := int(origData[length-1])
	return origData[:(length - unpadding)]
}

最后实现了加密接口函数和解密接口函数,代码如下:

加密接口函数实现:

func (aalg *AesAlgorithm) Encrypt() {
	tmpKeys := []byte(aalg.EncryptKey)
	tmpPlaintext := aalg.PlaintextContent
	block, err := aes.NewCipher(tmpKeys)
	if err != nil {
		fmt.Println("aes加密失败,原因:" + err.Error())
		return
	}
	blockSize := block.BlockSize()
	origData := pkcs5Padding([]byte(tmpPlaintext), blockSize)

	blockMode := cipher.NewCBCEncrypter(block, tmpKeys[:blockSize])
	crypted := make([]byte, len(origData))
	blockMode.CryptBlocks(crypted, origData)
	aalg.CiphertextContent = hex.EncodeToString(crypted)
}

解密接口函数实现:

func (aalg *AesAlgorithm) Decrypt() {
	tmpKeys := []byte(aalg.EncryptKey)
	cryptedByte, _ := hex.DecodeString(aalg.CiphertextContent)
	block, err := aes.NewCipher(tmpKeys)
	if err != nil {
		fmt.Println("aes解密失败,原因:" + err.Error())
		return
	}
	blockSize := block.BlockSize()
	blockMode := cipher.NewCBCDecrypter(block, tmpKeys[:blockSize])
	origin := make([]byte, len(cryptedByte))
	blockMode.CryptBlocks(origin, cryptedByte)
	decryptStrings := pkcs5UnPadding(origin)
	aalg.PlaintextContent = string(decryptStrings)
}

5.AES加密函数验证

我在aes_test.go中实现加密函数测试模块:TestEncrypt(t *testing.T), 代码如下:

func TestEncrypt(t *testing.T) {
	aesAlg := NewAesAlgorithm(
		WithEncryptKey("ZEplYJFPLlhhMaJI"),
		WithPlaintextContent("qYWwo7!!Eq-TX3q"),
	)
	aesAlg.AppAlg.SetAlgorithm(aesAlg)
	aesAlg.AppAlg.RunAlgorithm("encrypt")
	fmt.Println(aesAlg.CiphertextContent)
}

在上面的代码中, 我们调用了AES算法的对外统一接口函数:NewAesAlgorithm, 并分别调用WithEncryptKey和WithPlaintextContent传入了Key内容和明文内容, 并调用接口管理方法:SetAlgorithm进行对象赋值, 最后调用RunAlgorithm("encrypt")方法进行AES加密,实际结果如下:

在Go编程中封装AES加解密接口_AES

6.AES解密函数验证

同样在aes_test.go中实现加密函数测试模块:TestDecrypt(t *testing.T), 代码如下:

func TestDecrypt(t *testing.T) {
	aesAlg := NewAesAlgorithm(
		WithEncryptKey("ZEplYJFPLlhhMaJI"),
		WithCiphertextContent("31404e2eb60e2d16faae152106882f4b"),
	)
	aesAlg.AppAlg.SetAlgorithm(aesAlg)
	aesAlg.AppAlg.RunAlgorithm("decrypt")
	fmt.Println(aesAlg.PlaintextContent)
}

在上面的代码中, 我们调用了AES算法的对外统一接口函数:NewAesAlgorithm, 并分别调用WithEncryptKey和WithCiphertextContent传入了Key内容和上面加密的密文内容, 并调用接口管理方法:SetAlgorithm进行对象赋值, 最后调用RunAlgorithm("decrypt")方法进行AES解密,实际结果如下:

在Go编程中封装AES加解密接口_编程语言_02

可以看到,成功解密出密文且跟加密时传入的明文一致,解密正确。

7.总结

在本章节中, 我们利用选项设计模式使用Go语言封装了一个AES加解密接口, 在基础接口中,定义了两个通用方法: 代表通用加密算法的函数:Encrypt()和代表通用解密算法函数:Decrypt(), 这样做的好处是以后如果加入其它加解密算法, 不用再定义其它函数名,直接实现该算法的加解密接口方法即可, 而且我们已经实现了接口管理类, 在应用时减少了大量接口指派的重复代码, 可扩展性得到了保证。



在Go编程中封装AES加解密接口_编程语言_03

【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月22日 0

暂无评论

推荐阅读
vUhc7kqxotqT