go 實現 Laravel 的 encrypt () 和 decrypt () 方法

gedongdong2010發表於2020-06-19

最近專案準備從laravel遷移到go,但是為了最小範圍的影響,只能一部分一部分的慢慢遷移,所以就會涉及到相容laravel加密解密的問題。現將程式碼記錄到這裡,以備後續檢視。

加密方式

AES-256-CBC

金鑰格式

支援常規32位字串金鑰,也支援base64格式的金鑰

程式碼

package main

import (
   "crypto/hmac"
 "crypto/rand" "crypto/sha256" "encoding/base64" "encoding/hex" "encoding/json" "errors" "fmt" "os" "strings"
 "github.com/forgoer/openssl" "github.com/techoner/gophp/serialize")

//支援兩種key格式
//const EncrypterKey = "9bcv6ioh0s51pjlaw2d7u8rt3fyqnxge"

//const EncrypterKey = "base64:UXn0F1XSd2peV2M6mPtEWfeKPVlf5p+j5NqCd3+4/AA="

//加密
func Encrypt(value string) (string, error) {
   iv := make([]byte, 16)
   _, err := rand.Read(iv)
   if err != nil {
      return "", err
   }

   //反序列化
  message, err := serialize.Marshal(value)
   if err != nil {
      return "", err
   }

   key := getKey()

   //加密value
  res, err := openssl.AesCBCEncrypt(message, []byte(key), iv, openssl.PKCS7_PADDING)
   if err != nil {
      return "", err
   }

   //base64加密
  resVal := base64.StdEncoding.EncodeToString(res)
   resIv := base64.StdEncoding.EncodeToString(iv)

   //生成mac值
  data := resIv + resVal
   mac := computeHmacSha256(data, key)

   //構造ticket結構
  ticket := make(map[string]interface{})
   ticket["iv"] = resIv
   ticket["mac"] = mac
   ticket["value"] = resVal

   //json序列化
  resTicket, err := json.Marshal(ticket)
   if err != nil {
      return "", err
   }
   //base64加密ticket
  ticketR := base64.StdEncoding.EncodeToString(resTicket)

   return ticketR, nil
}

//解密
func Decrypt(value string) (string, error) {
   //base64解密
  token, err := base64.StdEncoding.DecodeString(value)
   fmt.Println("token---", string(token))
   if err != nil {
      return "", err
   }

   //json反序列化
  tokenJson := make(map[string]string)
   err = json.Unmarshal(token, &tokenJson)
   fmt.Println("tokenJson---", tokenJson)
   if err != nil {
      return "", err
   }

   tokenJsonIv, okIv := tokenJson["iv"]
   tokenJsonValue, okValue := tokenJson["value"]
   tokenJsonMac, okMac := tokenJson["mac"]
   if !okIv || !okValue || !okMac {
      return "", errors.New("value is not full")
   }

   key := getKey()

   //mac檢查,防止資料篡改
  data := tokenJsonIv + tokenJsonValue
   check := checkMAC(data, tokenJsonMac, key)
   if !check {
      return "", errors.New("mac valid failed")
   }

   //base64解密iv和value
  tokenIv, err := base64.StdEncoding.DecodeString(tokenJsonIv)
   if err != nil {
      return "", err
   }
   tokenValue, err := base64.StdEncoding.DecodeString(tokenJsonValue)
   if err != nil {
      return "", err
   }
   //aes解密value
  dst, err := openssl.AesCBCDecrypt(tokenValue, []byte(key), tokenIv, openssl.PKCS7_PADDING)
   fmt.Println("dst", string(dst))
   if err != nil {
      return "", err
   }

   //反序列化
  res, err := serialize.UnMarshal(dst)
   if err != nil {
      return "", err
   }
   return res.(string), nil
}

//比較預期的hash和實際的hash
func checkMAC(message, msgMac, secret string) bool {
   expectedMAC := computeHmacSha256(message, secret)
   fmt.Println(expectedMAC, msgMac)
   return hmac.Equal([]byte(expectedMAC), []byte(msgMac))
}

//計算mac值
func computeHmacSha256(message string, secret string) string {
   key := []byte(secret)
   h := hmac.New(sha256.New, key)
   h.Write([]byte(message))
   sha := hex.EncodeToString(h.Sum(nil))
   return sha
}

//處理金鑰
func getKey() string {
   appKey := os.Getenv("APP_KEY")
   if strings.HasPrefix(appKey, "base64:") {
      split := appKey[7:]
      if key, err := base64.StdEncoding.DecodeString(split); err == nil {
         return string(key)
      }
      return split
   }
   return appKey
}
本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章