您好,登錄后才能下訂單哦!
這篇文章主要介紹“go GCM gin中間件怎么加密解密文件”的相關知識,小編通過實際案例向大家展示操作過程,操作方法簡單快捷,實用性強,希望這篇“go GCM 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" "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 }
來讓我們一次驗證一下運行結果:
最后來看一下文件上傳:
關于“go GCM gin中間件怎么加密解密文件”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識,可以關注億速云行業資訊頻道,小編每天都會為大家更新不同的知識點。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。