在Go项目中封装AES加解密客户端接口

news2024/11/18 17:30:08

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解密,实际结果如下:

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1138503.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

day11力扣打卡

打卡记录 避免洪水泛滥(贪心 Map Set) 链接 将晴天的日期全部记录到 set 中。 使用 unordered_map 来记录每个湖泊上一次下雨的日期。 当下雨时,湖泊已经水满了时,查询到上次下雨的日期。 通过这个日期在晴天记录中查找对应的…

JS问题:项目中如何区分使用防抖或节流?

前端功能问题系列文章,点击上方合集↑ 序言 大家好,我是大澈! 本文约2300字,整篇阅读大约需要6分钟。 本文主要内容分三部分,第一部分是需求分析,第二部分是实现步骤,第三部分是问题详解。 …

Android 发布 15 周年了!Google 员工说出了这些年的美好回忆

原文链接:https://blog.google/products/android/android-15th-anniversary-googler-highlights/ 作者:Sameer Samat, GM and VP of Android Ecosystem 翻译者:张拭心 https://shixin.blog.csdn.net/ 自从带有 Android Market(现在…

MVC架构_Qt自己的MV架构

文章目录 前言模型/视图编程1.先写模型2. 视图3. 委托 例子(Qt代码)例1 查询本机文件系统例2 标准模型项操作例3 自定义模型示例:军事武器模型例4 只读模型操作示例例5 选择模型操作例6 自 定 义委 托(在testSelectionModel上修改) 前言 在Qt中&#xf…

Git(四)底层命令:git对象、树对象、提交对象

目录 一、知识回顾1.1 Linux 基础命令1.2 .git 文件夹解析 二、git 对象(数据对象)2.1 hash-object 存储对象2.2 cat-file 查看对象 三、树对象3.1 ls-files 查看暂存区3.2 update-index 创建暂存区3.3 write-tree 生成树对象3.4 更新暂存区,…

C/C++面试常见问题——const关键字的作用和用法

首先我们需要一下const关键字的定义,const名叫常量限定符,当const修饰变量时,就是在告诉编译器该变量只可访问不可修改,而编译器对于被const修饰的变量有一个优化,编译器不会专门为其开辟空间,而是将变量名…

Liunx两台服务器实现相互SSH免密登录

一、首先准备两台Linux虚拟机当作此次实验的两台服务器 服务器1:server IPV4:192.168.110.136 服务器2:client IPV4: 192.168.110.134 二、准备阶段 [rootserver ~]# systemctl disable firewalld #关…

【MySQL索引与优化篇】InnoDB数据存储结构

文章目录 1. 数据库的存储结构:页1.1 磁盘与内存交互基本单位:页1.2 页结构概述1.3 页的上层结构 2. 页的内部结构3. InnoDB行格式(或记录格式)3.1 Compact行格式3.2 Dynamic和Compressed行格式3.3 Redundant行格式 4. 区、段与碎片区4.1 为什么要有区?4.2 为什么要…

Webpack简介及打包演示

Webpack 是一个静态模块打包工具,从入口构建依赖图,打包有关的模块,最后用于展示你的内容 静态模块:编写代码过程中的,html,css, js,图片等固定内容的文件 打包过程,注…

【iOS安全】提取app对应的URLScheme

获取app的URLScheme 在已越狱的iPhone上,使用Filza进入app列表目录: /private/var/containers/Bundle/Application/ 比如我要分析Microsoft Authenticator,明显对应的是这里面的“Authenticator”,那就在Filza中点击进入“Authen…

网络协议--TFTP:简单文件传送协议

15.1 引言 TFTP(Trivial File Transfer Protocol)即简单文件传送协议,最初打算用于引导无盘系统(通常是工作站或X终端)。和将在第27章介绍的使用TCP的文件传送协议(FTP)不同,为了保持简单和短小&#xff0…

STM32中断,看着一篇就够了

1,环境搭建: 硬件平台:STM32H750XBH6 开发环境:STM32CubeMX V6.8.1KEIL V5.28.0.0 STM32H750固件版本:package V1.11.0 仿真下载驱动:ST-Link 2,中断的定义 中断(Interrupt&#xff…

【考研数学】数学“背诵”手册 | 需要记忆且容易遗忘的知识点

文章目录 引言一、高数常见泰勒展开 n n n 阶导数公式多元微分函数连续、可微、连续可偏导之间的关系多元函数极值无条件极值条件极值 三角函数的积分性质华里士公式( “点火”公式 )特殊性质 原函数与被积函数的奇偶性结论球坐标变换公式 二、写在最后 …

centos中安装mysql5.7

建议第八步骤,和第九步骤对于生产者人员就不用配置了,风险大,我自己的也没有配置 1.首先切换到root用户下 2.更新密钥 rpm --import https://repo.mysql.com/RPM-GPG-KEY-mysql-2022 3.安装mysql yum库 rpm -Uvh https://repo.mysql.com//…

云计算模式的区域LIS系统源码,基于ASP.NET+JQuery、EasyUI+MVC技术架构开发

云计算模式的区域LIS系统源码 云LIS系统源码,自主版权 LIS系统是专为医院检验科的仪器设备能与计算机连接。可通过LIS系统向仪器发送指令,让仪器自动操作和接收仪器数据。并快速的将检验仪器中的数据导入到医生工作站中进行管理,且可将检验结…

10.26ALP论文原代码请稿

尊敬的作者, 我是中国重庆大学的一名学生,近期准备就浮点数据无损压缩这个研究领域作一篇综述。 我对于您的ALP压缩算法十分感兴趣,并对于它的表现感到十分惊喜,我自己也尝试按您文章里的伪代码与思路复现您的方法,但…

非小米笔记本小米妙享中心安装最新教程 3.2.0.464 兼容所有Windows系统

小米妙享中心 3.2.0.464 版本帮助 : 支持音频流转、屏幕镜像、屏幕拓展、键鼠拓展、无线耳机、小米互传 目录 小米妙享中心 3.2.0.464 版本帮助 : 1.常规教程使用安装包方式安装失败 或者 1.1安装失败可使用大佬的加载补丁方法解决 补充卸载残留 1.2 截图存档 2. 本教程…

CentOS - 安装 Elasticsearch

"Elasticsearch"是一个流行的开源搜索和分析引擎,它可以用于实时搜索、日志和事件数据分析等任务。以下是在 CentOS 上安装 Elasticsearch 的基本步骤: 安装 Java: Elasticsearch 是基于 Java 的应用程序,所以首先需要…

Git窗口打开vim后如何退出编辑(IDEA/Goland等编辑器)

最近在学习git高级操作过程中,遇到了一下问题: 我在学习Git合并多个commit为一个的时候,需要输入一个命令 git rebase -i HEAD~2 这说明已经是编辑模式了。当我写好后,我还按照原来在linux上的按下ESC键,但是只是光…

题目 1058: 二级C语言-求偶数和(python详解)——练气四层中期

✨博主:命运之光 🦄专栏:算法修炼之练气篇(C\C版) 🍓专栏:算法修炼之筑基篇(C\C版) 🍒专栏:算法修炼之练气篇(Python版) ✨…