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

Posted dz45693

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了go GCM加密解密 gin中间件加密解密,gin文件流处理相关的知识,希望对你有一定的参考价值。

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

 常规的请求有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"
    "github.com/shopspring/decimal"
    "io"
    "io/ioutil"
    "mime"
    "mime/multipart"
    "net/url"
    "runtime/debug"
    "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 := &aesWriterbody: 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
    for k, v := range queryData 
        args = append(args, fmt.Sprintf("%s=%s", k, url.QueryEscape(getStr(v))))
    

    queryString := strings.Join(args, "&")
    c.Request.URL.RawQuery = queryString

    log("AesGcmDecrypt parseQuery  url:%s, md5key:%s, encryptString:%s, decrypt data:%s", c.Request.URL.String(), md5Key, encryptString, queryString)
    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))
        val = tmp.String()
    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请求

  2.看看post json

 3验证postform

  最后来看一下文件上传:

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

以上是关于go GCM加密解密 gin中间件加密解密,gin文件流处理的主要内容,如果未能解决你的问题,请参考以下文章

Go语言web框架 gin

Go-gin CORS 跨域中间件

如何使用 AES-GCM 对 C#.NET 加密()然后 JS WebCryptoApi 解密()?

go的web框架-gin

AES GCM 使用 web 微妙加密进行加密并使用颤振加密进行解密

Nodejs AES-256-GCM 通过 webcrypto api 解密加密的客户端消息