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
windows技术爱好者
以上是关于go GCM加密解密 gin中间件加密解密,gin文件流处理的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 AES-GCM 对 C#.NET 加密()然后 JS WebCryptoApi 解密()?