在 Go 項目中封裝 AES 加解密客戶端接口
1.摘要
在一個中型以上的項目中, 我們一般會在項目工程中開辟一個pkg文件夾用來存放一些基礎工具接口,比如:數(shù)據(jù)庫、中間件、加解密算法、基礎協(xié)議等等。在這篇文章中, 我主要分享一下在基于Go語言的項目中, 加解密算法中如何封裝一個通用的加解密接口, 并以使用比較廣泛的AES加解密算法實現(xiàn)為基礎進行講解, 最后模擬客戶端分別演示調用AES的加密接口和解密接口。
2.工程文件結構
在一個正規(guī)項目中, 我們要封裝的文件主要添加在算法文件夾下, 目錄結構規(guī)劃如下:
pkg
|
---- algorithm
|
---- base.go // 基礎接口函數(shù)定義
|
---- aes.go // aes加解密算法接口
|
---- aes_test.go // aes加解密算法接口函數(shù)測試
我在名為"algorithm"文件夾下新建了三個文件, 其中base.go為基礎接口函數(shù)定義, 因為以后可能要加入進來的算法會比較多,因此需要有一個基礎類文件來定義通用函數(shù)接口。
aes.go文件中主要實現(xiàn)AES算法的加解密過程, 并提供一個對外的初始化接口,方便應用層調用。
aes_test.go是作為單元測試的文件, 在里面可以針對AES加密函數(shù)和解密函數(shù)寫測試用例, 不用編譯整個工程實現(xiàn)單元測試。
如果后面有新的算法加入進來, 例如:des算法, 只需要添加一個des.go和des_test.go文件, 在里面實現(xiàn)函數(shù)功能即可。
3.基礎接口實現(xiàn)
基礎接口實現(xiàn)主要在base.go文件中, 因為對于所有加密算法來講, 都有兩個最基礎通用的方法:加密函數(shù)和解密函數(shù),因此這里定義了兩個通用的方法接口:
type IAlgorithm interface {
Encrypt() // 加密函數(shù)接口
Decrypt() // 解密函數(shù)接口
}
因為現(xiàn)在不知道項目默認需要使用什么算法,因此實現(xiàn)這兩個方法的空接口:
type DefaultAlgorithm struct{}
func (dal DefaultAlgorithm) Encrypt() {}
func (dal DefaultAlgorithm) Decrypt() {}
考慮在應用層方便切換不同的算法, 這里需要設計一個管理接口的方法, 首先定義一個結構體:
type AlgorithmManager struct {
algorithm IAlgorithm
}
在這個結構體中, 成員是上面接口名稱的對象。
然后我定義了兩個方法, 一個是設置算法對象的方法, 另一個是執(zhí)行算法方式的方法。
首先是設置算法對象的方法:
func (gor *AlgorithmManager) SetAlgorithm(algorithm IAlgorithm) {
gor.algorithm = algorithm
}
這個方法會接收一個參數(shù),這個參數(shù)就是用戶想要調用哪種算法的對象, 只有給接口賦對應算法的對象,接口才知道調用哪個算法的方法。
其次是運行算法類型的方法:
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, 則會執(zhí)行加密函數(shù),反之則執(zhí)行解密函數(shù)。
4.AES加解密算法實現(xiàn)
在AES加解密客戶端調用接口中, 我選擇了選項設計模式, 用戶可以根據(jù)加密算法和解密算法參數(shù)不同進行靈活的選項傳參。
首先定義一個方法結構體:
type AesAlgorithm struct {
AppAlg *AlgorithmManager
EncryptKey string // 密鑰
PlaintextContent string // 明文內容
CiphertextContent string // 密文內容
}
在這個結構體中, 密鑰、明文內容、密文內容是我們在使用功能過程中必須傳入的參數(shù), 其中還帶有一個結構對象指針: *AlgorithmManager, 方便我們將AES算法的對象傳給接口,讓其調用AES的加密方法或解密方法。
其次定義一個方便客戶端調用的接口, 并使用動態(tài)選項傳參,實現(xiàn)代碼如下:
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
}
// 通過該選項函數(shù)傳入key
func WithEncryptKey(key string) AesAlgorithmOption {
return func(aes *AesAlgorithm) {
aes.EncryptKey = key
}
}
// 通過該選項函數(shù)傳入明文
func WithPlaintextContent(plainText string) AesAlgorithmOption {
return func(aes *AesAlgorithm) {
aes.PlaintextContent = plainText
}
}
// 通過該選項函數(shù)傳入密文
func WithCiphertextContent(cipherContent string) AesAlgorithmOption {
return func(aes *AesAlgorithm) {
aes.CiphertextContent = cipherContent
}
}
下面我們還實現(xiàn)了兩個內部函數(shù),分別是加密和解密過程中需要填充塊的實現(xiàn)方法,代碼如下:
加密填充塊:
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)]
}
最后實現(xiàn)了加密接口函數(shù)和解密接口函數(shù),代碼如下:
加密接口函數(shù)實現(xiàn):
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)
}
解密接口函數(shù)實現(xiàn):
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加密函數(shù)驗證
我在aes_test.go中實現(xiàn)加密函數(shù)測試模塊: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算法的對外統(tǒng)一接口函數(shù):NewAesAlgorithm, 并分別調用WithEncryptKey和WithPlaintextContent傳入了Key內容和明文內容, 并調用接口管理方法:SetAlgorithm進行對象賦值, 最后調用RunAlgorithm("encrypt")方法進行AES加密,實際結果如下:
6.AES解密函數(shù)驗證
同樣在aes_test.go中實現(xiàn)加密函數(shù)測試模塊: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算法的對外統(tǒng)一接口函數(shù):NewAesAlgorithm, 并分別調用WithEncryptKey和WithCiphertextContent傳入了Key內容和上面加密的密文內容, 并調用接口管理方法:SetAlgorithm進行對象賦值, 最后調用RunAlgorithm("decrypt")方法進行AES解密,實際結果如下:
可以看到,成功解密出密文且跟加密時傳入的明文一致,解密正確。