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加密,实际结果如下:
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解密,实际结果如下:
可以看到,成功解密出密文且跟加密时传入的明文一致,解密正确。