微信ユーザー暗号化データ復号-golangバージョン


背景
アプレットが先にフロントエンドで取得したユーザデータのopenIdとUnionIdはユーザのプライバシーデータに属するため、ユーザの暗号化データを復号して取得する必要があり、公式に提供されている復号demoにはphp、python、C++などの言語バージョンがありますが、golangバージョンはありませんので、今日ブログを書いてgolang版の復号を書きます.
暗号化データ復号アルゴリズム
インタフェースがwx.getUserInfoのopenIdやunionIdなどの機密データに関連する場合、インタフェースの明示的な内容には機密データは含まれません.開発者が機密データを取得する必要がある場合は,インタフェースから返される暗号化データ(encryptedData)を対称的に復号する必要がある.復号アルゴリズムは次のとおりです.
  • 対称復号で使用されるアルゴリズムはAES-128-CBCであり、データはPKCS#7で埋め込まれている.
  • 対称復号のターゲット暗号文はBase 64_Decode(encryptedData).
  • 対称復号鍵aeskey=Base 64_Decode(session_key)、aeskeyは16バイトです.
  • 対称復号アルゴリズム初期ベクトルはBase 64_Decode(iv)は、ivがデータインタフェースによって返される.

  • コアコード
    wechat.go
    package wechat
    
    import (
        "crypto/aes"
        "crypto/cipher"
        "encoding/base64"
        "encoding/json"
        "errors"
    )
    
    var (
        ErrAppIDNotMatch       = errors.New("app id not match")
        ErrInvalidBlockSize    = errors.New("invalid block size")
        ErrInvalidPKCS7Data    = errors.New("invalid PKCS7 data")
        ErrInvalidPKCS7Padding = errors.New("invalid padding on input")
    )
    
    type WxUserInfo struct {
        OpenID    string `json:"openId"`
        UnionID   string `json:"unionId"`
        NickName  string `json:"nickName"`
        Gender    int    `json:"gender"`
        City      string `json:"city"`
        Province  string `json:"province"`
        Country   string `json:"country"`
        AvatarURL string `json:"avatarUrl"`
        Language  string `json:"language"`
        Watermark struct {
            Timestamp int64  `json:"timestamp"`
            AppID     string `json:"appid"`
        } `json:"watermark"`
    }
    
    type WXUserDataCrypt struct {
        appID, sessionKey string
    }
    
    func NewWXUserDataCrypt(appID, sessionKey string) *WXUserDataCrypt {
        return &WXUserDataCrypt{
            appID:      appID,
            sessionKey: sessionKey,
        }
    }
    
    // pkcs7Unpad returns slice of the original data without padding
    func pkcs7Unpad(data []byte, blockSize int) ([]byte, error) {
        if blockSize <= 0 {
            return nil, ErrInvalidBlockSize
        }
        if len(data)%blockSize != 0 || len(data) == 0 {
            return nil, ErrInvalidPKCS7Data
        }
        c := data[len(data)-1]
        n := int(c)
        if n == 0 || n > len(data) {
            return nil, ErrInvalidPKCS7Padding
        }
        for i := 0; i < n; i++ {
            if data[len(data)-n+i] != c {
                return nil, ErrInvalidPKCS7Padding
            }
        }
        return data[:len(data)-n], nil
    }
    
    func (w *WXUserDataCrypt) Decrypt(encryptedData, iv string) (*WxUserInfo, error) {
        aesKey, err := base64.StdEncoding.DecodeString(w.sessionKey)
        if err != nil {
            return nil, err
        }
        cipherText, err := base64.StdEncoding.DecodeString(encryptedData)
        if err != nil {
            return nil, err
        }
        ivBytes, err := base64.StdEncoding.DecodeString(iv)
        if err != nil {
            return nil, err
        }
        block, err := aes.NewCipher(aesKey)
        if err != nil {
            return nil, err
        }
        mode := cipher.NewCBCDecrypter(block, ivBytes)
        mode.CryptBlocks(cipherText, cipherText)
        cipherText, err = pkcs7Unpad(cipherText, block.BlockSize())
        if err != nil {
            return nil, err
        }
        var userInfo WxUserInfo
        err = json.Unmarshal(cipherText, &userInfo)
        if err != nil {
            return nil, err
        }
        if userInfo.Watermark.AppID != w.appID {
            return nil, ErrAppIDNotMatch
        }
        return &userInfo, nil
    }
    

    main.goファイル
    package main
    
    import (
        "fmt"
        "test/wxbizdatacrypt"
    )
    
    func main() {
        appID := "wx33f640141e02040e"
        sessionKey := `SE/BLocg+sMlvcKmxm8vQA==`
        encryptedData :="7SfFtStsHqKZYhbIkke3BH2bCRzGD15T0jEiUtuksrl9lDeHm9LsPmswJymBXuinPCiXkZhd/uq7s7pACTvbWuvvoKEwz5fAJ6Vr9bTx79XVxiIN4r+Fwm6QHO9DjPkFrxTGAZvMYLyH6IOyOV/nmmlMoBM3G4peSnBi1qCYukwlyCMNp67lb93wSiPAoI7eRhYYw8ayPTsZ/MAJ9CBBUiCwM5aFOUWrMKNTikeq7YVjNCv7KCz0LJTrMKda0YMS0J/034L8x9vJ1OnIkxlWVMQEy/f55IfWVHI1I1fSKd5azzyVKXCbWDpU0PLJnU8XM/l4L7ZUlDOcRMR5KQVGhB9rIjVkykdXUPQK87v8lpnitslK06XceOJqDjK6mRkhJWOYpFUozZa6idFV6xmLZX8bkBsLxczzp1h/satEH7rIz3nKbxd3O1c+3dI2soSt8qFtaumcGdwhenTm+at0gxccAp8JD8PZiB5ZDLTofZIQ4RmI004SIExYUDZUje9mZO+3aC8McVwzrEyK7NKD/NZ5/dYPgDRwzBl1Vm99niY="
        iv := "z3tGYrgMcbLzd0qXqZuduQ=="
        pc := wxbizdatacrypt.NewWXBizDataCrypt(appID, sessionKey)
        userInfo, err := pc.Decrypt(encryptedData, iv)
        if err != nil {
            fmt.Println(err.Error())
            return
        }
        fmt.Printf("userData:%+v", userInfo)
    }

    結果
    参考記事
  • 参照コード:https://github.com/yilee/wx-biz-data-crypt/blob/master/wxbizdatacrypt_test.go
  • WeChat公式ドキュメント:https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/signature.html#%E5%8A%A0%E5%AF%86%E6%95%B0%E6%8D%AE%E8%A7%A3%E5%AF%86%E7%AE%97%E6%B3%95
  • 関連概念を解読する:http://blog.studygolang.com/2013/01/go%E5%8A%A0%E5%AF%86%E8%A7%A3%E5%AF%86%E4%B9%8Bdes/