go GCM gin中间件的加密解密文件流处理

作者:dz45693 时间:2024-04-26 17:32:36 

aes的gcm模式的加密和解密

要给已有的系统启用加密解密,目前推荐的是aes的gcm模式的加密和解密,在微服务如果向前有公共方法处理 读取数据和写返回数据,那么比较简单,修改以前的公共方法,但是这样本地调试平时肯定是明文,所以要加判断,如果以前的读数据和写数据是五花八门那就比较麻烦,在微服务体系里面一般有网关这个服务,所以加密和解密就放在网关服务,大致如下:

go GCM gin中间件的加密解密文件流处理

常规的请求有GET,POST JSON, POST file,以及POST Form表单,返回一般是json 或者下载文件流,所以我们需要截获请求流和返回流,收到请求流解密数据 然后重新写入到请求流,收到返回流加密数据,重写返回流。

首先来看aes加密和解密程序aes.go

package aes
import (
"crypto/aes"
"crypto/cipher"
"crypto/md5"
"crypto/rand"
"encoding/base64"
"encoding/hex"
"errors"
"io"
)
//加密字符串
func GcmEncrypt(key, plaintext string) (string, error) {
if len(key) != 32 && len(key) != 24 && len(key) != 16 {
return "", errors.New("the length of key is error")
}
if len(plaintext) < 1 {
return "", errors.New("plaintext is null")
}
keyByte := []byte(key)
plainByte:=[]byte(plaintext)
block, err := aes.NewCipher(keyByte)
if err != nil {
return "", err
}
aesGcm, err := cipher.NewGCM(block)
if err != nil {
return "", err
}
nonce := make([]byte, 12)
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
return "", err
}
seal := aesGcm.Seal(nonce, nonce, plainByte, nil)
return base64.URLEncoding.EncodeToString(seal), nil
}
//解密字符串
func GcmDecrypt(key, cipherText string) (string, error) {
if len(key) != 32 && len(key) != 24 && len(key) != 16 {
return "", errors.New("the length of key is error")
}
if len(cipherText) < 1 {
return "", errors.New("cipherText is null")
}
cipherByte, err := base64.URLEncoding.DecodeString(cipherText)
if err != nil {
return "", err
}
if len(cipherByte) < 12 {
return "", errors.New("cipherByte is error")
}
nonce, cipherByte := cipherByte[:12], cipherByte[12:]
keyByte := []byte(key)
block, err := aes.NewCipher(keyByte)
if err != nil {
return "", err
}
aesGcm, err := cipher.NewGCM(block)
if err != nil {
return "", err
}
plainByte, err := aesGcm.Open(nil, nonce, cipherByte, nil)
if err != nil {
return "", err
}
return string(plainByte), nil
}
//生成32位md5字串
func GetAesKey(s string) string {
h := md5.New()
h.Write([]byte(s))
return hex.EncodeToString(h.Sum(nil))
}

再来看看网关转发程序proxy.go

package middleware
import (
"fmt"
"github.com/gin-gonic/gin"
"github.com/valyala/fasthttp"
"io/ioutil"
"runtime/debug"
"time"
)
var fastClient *fasthttp.Client
func init() {
fastClient = &fasthttp.Client{}
fastClient.MaxIdemponentCallAttempts = 1
fastClient.ReadTimeout = time.Second * 60
}
func GetHttpClient() *fasthttp.Client {
return fastClient
}
func GateWay() gin.HandlerFunc {
return func(c *gin.Context) {
defer func() {
if e := recover(); e != nil {
stack := debug.Stack()
log("GateWay Recovery: err:%v, stack:%v", e, string(stack))
}
}()
err := Forward(c)
if err != nil {
response(c, 9999, "系统错误", err.Error())
}
return
}
}
func Forward(ctx *gin.Context) error {
req := &fasthttp.Request{}
//请求-获取服务地址
host := "http://localhost:8000/" + ctx.Request.URL.String()
//请求-url
req.SetRequestURI(host)
//请求-header
for k, v := range ctx.Request.Header {
req.Header.Set(k, v[0])
}
//请求-body
data, err := ioutil.ReadAll(ctx.Request.Body)
if err != nil {
log("Forward err:%v", err)
return fmt.Errorf("系统错误")
}
req.SetBody(data)
//请求-方法
req.Header.SetMethod(ctx.Request.Method)
//请求-发送
resp := &fasthttp.Response{}
//请求-新增调用链
/*
err = opentracing.GlobalTracer().Inject(
opentracing.SpanFromContext(ctx.Request.Context()).Context(),
opentracing.TextMap,
HTTPHeadersCarrier{&req.Header},
)
*/
err = GetHttpClient().Do(req, resp)
if err != nil {
log("Forward GetHttpClient DO err:%v", err)
return fmt.Errorf("系统错误")
}
//请求-响应
ContentType := fmt.Sprintf("%s", resp.Header.Peek("Content-Type"))
ctx.Data(resp.StatusCode(), ContentType, resp.Body())
return nil
}
type HTTPHeadersCarrier struct {
*fasthttp.RequestHeader
}
func (c HTTPHeadersCarrier) Set(key, val string) {
h := c.RequestHeader
h.Add(key, val)
}

最后来看一下gin的中间件crypto.go

package middleware
import (
"bytes"
"demo/aes"
"encoding/json"
"errors"
"fmt"
"github.com/gin-gonic/gin"
"io"
"io/ioutil"
"mime"
"mime/multipart"
"net/url"
"runtime/debug"
"strconv"
"strings"
)
type aesWriter struct {
gin.ResponseWriter
body *bytes.Buffer
}
func (w *aesWriter) Write(b []byte) (int, error) {
return w.body.Write(b)
}
func (w *aesWriter) WriteString(s string) (int, error) {
return w.body.WriteString(s)
}
//只有经过token 验证的才会加密 和解密
//handleFile 表示是否处理上传文件, 默认网关不处理上传文件的encryptString数据, 如果处理会导致具体服务无法接收到具体参数
func AesGcmDecrypt() gin.HandlerFunc {
return func(c *gin.Context) {
defer func() {
if e := recover(); e != nil {
stack := debug.Stack()
log("AesGcmDecrypt Recovery: err:%v, stack:%v", e, string(stack))
}
}()
if c.Request.Method == "OPTIONS" {
c.Next()
} else {
md5key := aes.GetAesKey("gavin12345678")
log("AesGcmDecrypt start url:%s  ,md5key:%s, Method:%s, Header:%+v", c.Request.URL.String(), md5key, c.Request.Method, c.Request.Header)
handleAes(c, md5key)
}
}
}
//请求和返回都加密 解密
func handleAes(c *gin.Context, md5key string) {
contentType := c.Request.Header.Get("Content-Type")
isJsonRequest := strings.Contains(contentType, "application/json")
isFileRequest := strings.Contains(contentType, "multipart/form-data")
isFormUrl := strings.Contains(contentType, "application/x-www-form-urlencoded")
if c.Request.Method == "GET" {
err := parseQuery(c, md5key)
if err != nil {
log("handleAes parseQuery  err:%v", err)
//这里输出应该密文 一旦加密解密调试好 这里就不会走进来
response(c, 2001, "系统错误", err.Error())
return
}
} else if isJsonRequest {
err := parseJson(c, md5key)
if err != nil {
log("handleAes parseJson err:%v", err)
//这里输出应该密文 一旦加密解密调试好 这里就不会走进来
response(c, 2001, "系统错误", err.Error())
return
}
} else if isFormUrl {
err := parseForm(c, md5key)
if err != nil {
log("handleAes parseForm err:%v", err)
//这里输出应该密文 一旦加密解密调试好 这里就不会走进来
response(c, 2001, "系统错误", err.Error())
return
}
} else if isFileRequest {
err := parseFile(c, md5key)
if err != nil {
log("handleAes parseFile err:%v", err)
//这里输出应该密文 一旦加密解密调试好 这里就不会走进来
response(c, 2001, "系统错误", err.Error())
return
}
}
///截取 response body
oldWriter := c.Writer
blw := &aesWriter{body: bytes.NewBufferString(""), ResponseWriter: c.Writer}
c.Writer = blw
// 走流程
c.Next()
///获取返回数据
responseByte := blw.body.Bytes()
//日志
c.Writer = oldWriter
//如果返回的不是json格式 那么直接返回,应为文件下载之类的不应该加密
if !isJsonResponse(c) {
_, _ = c.Writer.Write(responseByte)
return
}
///加密
encryptStr, err := aes.GcmEncrypt(md5key, string(responseByte))
if err != nil {
log("handleAes GcmEncrypt err:%v", err)
response(c, 2001, "系统错误", err.Error())
return
}
_, _ = c.Writer.WriteString(encryptStr)
}
//处理json
func parseJson(c *gin.Context, md5key string) error {
//读取数据 body处理
payload, err := c.GetRawData()
if err != nil {
return err
}
///解密body数据 请求的json是{"encryptString":{value}} value含有gcm的12字节nonce,实际长度大于32
if payload != nil && len(payload) > 20 {
var jsonData encryptJson
log("AesGcmDecrypt  parseJson url:%s md5key:%s,old data:%s,", c.Request.URL.String(), md5key, string(payload))
err := json.Unmarshal(payload, &jsonData)
if err != nil {
log("AesGcmDecrypt parseJson Unmarshal err:%v", err)
return err
}
payloadText := jsonData.EncryptString
if len(payloadText) > 0 {
payloadText, err = aes.GcmDecrypt(md5key, payloadText)
if err != nil {
log("AesGcmDecrypt parseJson GcmDecryptByte err:%v", err)
return err
}
payload = []byte(payloadText)
log("AesGcmDecrypt  parseJson url:%s md5key:%s,encryptString:%s,decrypt data:%s", c.Request.URL.String(), md5key, jsonData.EncryptString, payloadText)
}
}
c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(payload))
return nil
}
func parseForm(c *gin.Context, md5key string) error {
//读取数据 body处理
payload, err := c.GetRawData()
if err != nil {
return err
}
///解密body数据 请求的json是"encryptString= value含有gcm的12字节nonce,实际长度大于32
if payload != nil && len(payload) > 20 {
var jsonData encryptJson
log("AesGcmDecrypt  parseForm url:%s md5key:%s,old data:%s,", c.Request.URL.String(), md5key, string(payload))
values, err := url.ParseQuery(string(payload))
if err != nil {
log("AesGcmDecrypt parseForm ParseQuery err:%v", err)
return err
}
payloadText := values.Get("encryptString")
if len(payloadText) > 0 {
mapData, err := gcmDecryptString(md5key, payloadText)
if err != nil {
log("AesGcmDecrypt parseForm gcmDecryptString err:%v", err)
return err
}
for k, v := range mapData {
values.Add(k, getStr(v))
}
formData := values.Encode()
log("AesGcmDecrypt  parseForm url:%s md5key:%s,encryptString:%s,decrypt data:%s", c.Request.URL.String(), md5key, jsonData.EncryptString, formData)
payload = []byte(formData)
}
}
c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(payload))
return nil
}
//处理get url的解密
func parseQuery(c *gin.Context, md5Key string) error {
encryptString := c.Query("encryptString")
log("AesGcmDecrypt parseQuery url:%s, md5key:%s, encryptString:%s", c.Request.URL.String(), md5Key, encryptString)
if len(encryptString) < 1 {
return nil
}
queryData, err := gcmDecryptString(md5Key, encryptString)
if err != nil {
return err
}
var args []string
var logs []string
for k, v := range queryData {
val := getStr(v)
args = append(args, fmt.Sprintf("%s=%s", k, url.QueryEscape(val)))
logs = append(logs, fmt.Sprintf("%s=%s", k, val))
}
log("AesGcmDecrypt parseQuery  url:%s, md5key:%s, encryptString:%s, decrypt data:%s", c.Request.URL.String(), md5Key, encryptString, strings.Join(logs, "&"))
c.Request.URL.RawQuery = strings.Join(args, "&")
return nil
}
func parseFile(c *gin.Context, md5Key string) error {
contentType := c.Request.Header.Get("Content-Type")
_, params, _ := mime.ParseMediaType(contentType)
boundary, ok := params["boundary"]
if !ok {
return errors.New("no multipart boundary param in Content-Type")
}
//准备重写数据
bodyBuf := &bytes.Buffer{}
wr := multipart.NewWriter(bodyBuf)
mr := multipart.NewReader(c.Request.Body, boundary)
for {
p, err := mr.NextPart() //p的类型为Part
if err == io.EOF {
break
}
if err != nil {
log("NextPart err:%v", err)
break
}
fileByte, err := ioutil.ReadAll(p)
if err != nil {
log("ReadAll err:%v", err)
break
}
pName := p.FormName()
fileName := p.FileName()
if len(fileName) < 1 {
if pName == "encryptString" {
formData, err := gcmDecryptString(md5Key, string(fileByte))
if err != nil {
log("AesGcmDecrypt writeFile gcmDecryptString err:%v", err)
break
}
for k, v := range formData {
val := getStr(v)
err = wr.WriteField(k, val)
if err != nil {
log("AesGcmDecrypt writeFile WriteField :%s=%s, err:%v", k, val, err)
break
}
}
} else {
wr.WriteField(pName, string(fileByte))
}
} else {
tmp, err := wr.CreateFormFile(pName, fileName)
if err != nil {
log("AesGcmDecrypt parseFile CreateFormFile err:%v", err)
continue
}
tmp.Write(fileByte)
}
}
//写结尾标志
_ = wr.Close()
c.Request.Header.Set("Content-Type", wr.FormDataContentType())
c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBuf.Bytes()))
return nil
}
func gcmDecryptString(md5Key, encryptString string) (map[string]interface{}, error) {
formData := make(map[string]interface{}, 0)
if len(encryptString) < 1 {
return formData, nil
}
plaintext, err := aes.GcmDecrypt(md5Key, encryptString)
if err != nil {
return formData, err
}
if len(plaintext) < 3 {
//plaintext 应该是json 串 {}
return formData, nil
}
err = json.Unmarshal([]byte(plaintext), &formData)
if err != nil {
return formData, err
}
return formData, nil
}
func isJsonResponse(c *gin.Context) bool {
contentType := c.Writer.Header().Get("Content-Type")
return strings.Contains(contentType, "application/json")
}
func getStr(v interface{}) string {
val := ""
switch v.(type) {
case float64:
//tmp, _ := decimal.NewFromString(fmt.Sprintf("%.10f", v))
fl, _ := v.(float64)
val = strconv.FormatFloat(fl, 'f', -1, 64)
default:
val = fmt.Sprintf("%v", v)
}
return val
}
type encryptJson struct {
EncryptString string `json:"encryptString"`
}
func log(format string, arg ...interface{}) {
fmt.Print(fmt.Sprintf(format, arg...))
}
func response(c *gin.Context, code int, msg string, data interface{}) {
mapData := make(map[string]interface{}, 0)
mapData["code"] = code
mapData["msg"] = msg
mapData["data"] = data
c.JSON(200, data)
c.Abort()
return
}

最后我们来写一个demo程序main.go

package main
import (
"demo/middleware"
"fmt"
"github.com/gin-gonic/gin"
"os"
)
func main() {
go func() {
gateway := gin.Default()
gateway.Use(middleware.AesGcmDecrypt())
gateway.Use(middleware.GateWay())
gateway.Run(":8080")
}()
// 1.创建路由
r := gin.Default()
r.Use(middleware.Logger())
r.GET("/", func(c *gin.Context) {
c.Writer.WriteString("pong")
})
r.GET("/demo", func(c *gin.Context) {
req := ReqObj{}
err := c.ShouldBindQuery(&req)
if err != nil {
fmt.Print(err)
}
response(c, 200, "ok", req)
})
r.POST("/test", func(c *gin.Context) {
req := ReqObj{}
err := c.ShouldBind(&req)
if err != nil {
fmt.Print(err)
}
response(c, 200, "ok", req)
})
r.POST("/form", func(c *gin.Context) {
req := ReqObj{}
err := c.ShouldBind(&req)
if err != nil {
fmt.Print(err)
}
response(c, 200, "ok", req)
})
r.POST("/upload", func(c *gin.Context) {
file, err := c.FormFile("file")
if err != nil {
fmt.Print(err)
}
folder := c.Request.FormValue("folder")
tmp, _ := os.Getwd()
filePath := tmp + "/upload/" + folder + "/" + file.Filename
c.SaveUploadedFile(file, filePath)
})
r.Run(":8000")
}
type ReqObj struct {
Name       string `json:"name" form:"name"`
Age        int64  `json:"age"  form:"age"`
UpdateTime int64  `json:"update_time"  form:"update_time"`
Folder     string `json:"folder"  form:"folder"`
}
func response(c *gin.Context, code int, msg string, data interface{}) {
mapData := make(map[string]interface{}, 0)
mapData["code"] = code
mapData["msg"] = msg
mapData["data"] = data
c.JSON(200, data)
c.Abort()
return
}

验证

来让我们一次验证一下运行结果:

1.GET请求

go GCM gin中间件的加密解密文件流处理

2.看看post json

go GCM gin中间件的加密解密文件流处理

3验证postform

go GCM gin中间件的加密解密文件流处理

最后来看一下文件上传:

go GCM gin中间件的加密解密文件流处理

下载地址 :https://github.com/dz45693/gindemo

来源:https://blog.csdn.net/ma_jiang/article/details/123412666

标签:go,GCM,gin,加解密,文件流处理
0
投稿

猜你喜欢

  • Qt操作SQLite数据库的教程详解

    2024-01-16 14:10:04
  • ASP.NET MVC4入门教程(二):添加一个控制器

    2024-05-11 09:26:25
  • python列表的增删改查实例代码

    2021-08-11 04:06:51
  • DateDiff函数在Sql与Access中的区别

    2009-06-04 18:02:00
  • Python中使用第三方库xlrd来写入Excel文件示例

    2023-08-16 21:37:29
  • Python学习笔记之图片人脸检测识别实例教程

    2021-12-03 06:17:28
  • 25个出色的使用叶子的logo设计

    2009-12-29 12:53:00
  • Python 中创建 PostgreSQL 数据库连接池

    2024-01-19 22:33:37
  • 详解pytorch的多GPU训练的两种方式

    2023-08-04 09:58:29
  • Ubuntu20.04环境安装tensorflow2的方法步骤

    2023-07-04 06:41:21
  • SQL Server 2008中的新日期数据类型

    2009-03-16 15:05:00
  • Pandas:DataFrame对象的基础操作方法

    2023-07-20 16:13:19
  • PowerDesigner中如何导入SQL Server数据库

    2024-01-17 08:38:46
  • 基于JS实现Android,iOS一个手势动画效果

    2024-04-28 09:36:41
  • MySQL连接无法解析HOST主机名的解决方法

    2024-01-23 08:38:05
  • Python实战项目用PyQt5制作漫画脸GUI界面

    2023-07-05 13:17:19
  • Python调用SMTP服务自动发送Email的实现步骤

    2023-02-23 14:17:30
  • Python中内置数据类型list,tuple,dict,set的区别和用法

    2022-09-10 05:59:46
  • Python10行代码实现模拟百度搜索的示例

    2022-07-19 17:10:57
  • ASP.NET页面间的传值的几种方法

    2024-05-11 09:26:52
  • asp之家 网络编程 m.aspxhome.com