go 实现 Laravel 的 加密encrypt () 和 解密decrypt () 方法

最近项目准备从laravel迁移到go,但是为了最小范围的影响,只能一部分一部分的慢慢迁移,所以就会涉及到兼容laravel加密解密的问题。现将代码记录到这里,以备后续查看。

加密方式

AES-256-CBC

密钥格式

支持常规32位字符串密钥,也支持base64格式的密钥

代码

package main

import (
    "crypto/hmac"
    "crypto/rand"
    "crypto/sha256"
    "encoding/base64"
    "encoding/hex"
    "encoding/json"
    "errors"
    "os"
    "strings"

    "github.com/forgoer/openssl"
    "github.com/techoner/gophp/serialize"
)

//支持两种key格式
//const EncrypterKey = "9bcv6ioh0s51pjlaw2d7u8rt3fyqnxge"
const EncrypterKey = "base64:UXn0F1XSd2peV2M6mPtEWfeKPVlf5p+j5NqCd3+4/AA="

//Encrypt 加密
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
}

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

    //json反序列化
    tokenJson := make(map[string]string)
    err = json.Unmarshal(token, &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)
    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)
    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 := EncrypterKey
    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 协议》,转载必须注明作者和本文链接
本帖由系统于 2年前 自动加精
讨论数量: 6

牛逼 直接可以用

2年前 评论

牛逼!!!!刚好需要,顶一个

2年前 评论

找了一天,终于找到了,顶一个!!! laravel项目迁移go必备利器

1年前 评论

牛逼啊兄弟

1年前 评论

感觉版本问题么。我这边aes解密value出来就是缓存的key了。 读取之后要进行两遍反序列化才能拿到映射map的值

1个月前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!