欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

微信消息加解密(GoLang)-- 接收和回复加密消息

程序员文章站 2022-03-07 16:53:31
`接收微信消息的接口内容:` 附:微信消息加解密工具包(GoLang) go package wechat //微信消息加解密工具包 const ( //以下均为公众号管理后台设置项 token = "XXXXXXXX" appID = "XXXXXXXXXX" encodingAESKey = " ......

接收微信消息的接口内容:

    //"r"为*http.Request
    r.ParseForm()

    timestamp := strings.Join(r.Form["timestamp"], "")
    nonce := strings.Join(r.Form["nonce"], "")
    signature := strings.Join(r.Form["signature"], "")
    encryptType := strings.Join(r.Form["encrypt_type"], "")
    msgSignature := strings.Join(r.Form["msg_signature"], "")

    
    if !wechat.ValidateUrl(timestamp, nonce, signature) {
        wmm.log.AppendObj(nil, "Wechat Message Service: this http request is not from Wechat platform!")
        return
    }
    
    //微信安全模式更改/首次添加url,需要验证,将参数原样写会即可
    var es string
    if e = req.ParseGet("echostr", &es); e != nil {

    }
    if es != "" {
        //todo 将参数值es原先写回即可
        return
    }

    if r.Method == "POST" {
        if encryptType == "aes" {
            encryptRequestBody := wechat.ParseEncryptRequestBody(r)
            // Validate mstBody signature
            if !wechat.ValidateMsg(timestamp, nonce, encryptRequestBody.Encrypt, msgSignature) {
                return
            }

            // Decode base64
            cipherData, err := base64.StdEncoding.DecodeString(encryptRequestBody.Encrypt)
            if err != nil {
                return
            }

            // AES Decrypt
            plainData, err := wechat.AesDecrypt(cipherData, wechat.AesKey)
            if err != nil {
                return
            }

            //封装struct
            textRequestBody, err := wechat.ParseEncryptTextRequestBody(plainData)
            if err != nil {
                return
            }

            var url string

            tp := textRequestBody.MsgType
            if tp == "text" && textRequestBody.Content == "【收到不支持的消息类型,暂无法显示】" {
            //安全模式下向用户回复消息也需要加密
                respBody, e := wechat.MakeEncryptResponseBody(textRequestBody.ToUserName, textRequestBody.FromUserName, "一些回复给用户的消息", nonce, timestamp)
                if e != nil {
                    return e
                }
                //此处return NewSimpleError是一个对返回值处理的封装,返回xml格式消息,并不是返回错误
                return service.NewSimpleError(service.SERVER_WRITE_XML, string(respBody))

            }
            if tp == "event" {
                //某个类型的消息暂时后台不作处理,也需要向微信服务器做出响应
                return service.NewSimpleError(service.SERVER_WRITE_TEXT, "success")
            }
             
    }
    return service.NewSimpleError(service.SERVER_WRITE_TEXT, "success")

附:微信消息加解密工具包(GoLang)

package wechat
//微信消息加解密工具包

const (
    //以下均为公众号管理后台设置项
    token          = "XXXXXXXX"
    appID          = "XXXXXXXXXX"
    encodingAESKey = "XXXXXXXXXXXXXXX"
)

var AesKey []byte

func EncodingAESKey2AESKey(encodingKey string) []byte {
    data, _ := base64.StdEncoding.DecodeString(encodingKey + "=")
    return data
}

func init() {
    AesKey = EncodingAESKey2AESKey(encodingAESKey)
}

type TextRequestBody struct {
    XMLName      xml.Name `xml:"xml"`
    ToUserName   string
    FromUserName string
    CreateTime   time.Duration
    MsgType      string
    Url          string
    PicUrl       string
    MediaId      string
    ThumbMediaId string
    Content      string
    MsgId        int
    Location_X   string
    Location_Y   string
    Label        string
}

type TextResponseBody struct {
    XMLName      xml.Name `xml:"xml"`
    ToUserName   CDATAText
    FromUserName CDATAText
    CreateTime   string
    MsgType      CDATAText
    Content      CDATAText
}

type EncryptRequestBody struct {
    XMLName    xml.Name `xml:"xml"`
    ToUserName string
    Encrypt    string
}

type EncryptResponseBody struct {
    XMLName      xml.Name `xml:"xml"`
    Encrypt      CDATAText
    MsgSignature CDATAText
    TimeStamp    string
    Nonce        CDATAText
}

type EncryptResponseBody1 struct {
    XMLName      xml.Name `xml:"xml"`
    Encrypt      string
    MsgSignature string
    TimeStamp    string
    Nonce        string
}

type CDATAText struct {
    Text string `xml:",innerxml"`
}

func MakeSignature(timestamp, nonce string) string {
    sl := []string{token, timestamp, nonce}
    sort.Strings(sl)
    s := sha1.New()
    io.WriteString(s, strings.Join(sl, ""))
    return fmt.Sprintf("%x", s.Sum(nil))
}

func MakeMsgSignature(timestamp, nonce, msg_encrypt string) string {
    sl := []string{token, timestamp, nonce, msg_encrypt}
    sort.Strings(sl)
    s := sha1.New()
    io.WriteString(s, strings.Join(sl, ""))
    return fmt.Sprintf("%x", s.Sum(nil))
}

func ValidateUrl(timestamp, nonce, signatureIn string) bool {
    signatureGen := MakeSignature(timestamp, nonce)
    if signatureGen != signatureIn {
        return false
    }
    return true
}

func ValidateMsg(timestamp, nonce, msgEncrypt, msgSignatureIn string) bool {
    msgSignatureGen := MakeMsgSignature(timestamp, nonce, msgEncrypt)
    if msgSignatureGen != msgSignatureIn {
        return false
    }
    return true
}

func ParseEncryptRequestBody(r *http.Request) *EncryptRequestBody {
    body, err := ioutil.ReadAll(r.Body)
    if err != nil {
        return nil
    }
    //  mlog.AppendObj(nil, "Wechat Message Service: RequestBody--", body)
    requestBody := &EncryptRequestBody{}
    xml.Unmarshal(body, requestBody)
    return requestBody

}

func ParseTextRequestBody(r *http.Request) *TextRequestBody {
    body, err := ioutil.ReadAll(r.Body)
    r.Body.Close()
    if err != nil {
        log.Fatal(err)
        return nil
    }
    requestBody := &TextRequestBody{}
    xml.Unmarshal(body, requestBody)
    return requestBody
}

func Value2CDATA(v string) CDATAText {
    //return CDATAText{[]byte("<![CDATA[" + v + "]]>")}
    return CDATAText{"<![CDATA[" + v + "]]>"}
}

func MakeTextResponseBody(fromUserName, toUserName, content string) ([]byte, error) {
    textResponseBody := &TextResponseBody{}
    textResponseBody.FromUserName = Value2CDATA(fromUserName)
    textResponseBody.ToUserName = Value2CDATA(toUserName)
    textResponseBody.MsgType = Value2CDATA("text")
    textResponseBody.Content = Value2CDATA(content)
    textResponseBody.CreateTime = strconv.Itoa(int(time.Duration(time.Now().Unix())))
    return xml.MarshalIndent(textResponseBody, " ", "  ")
}
func MakeEncryptResponseBody(fromUserName, toUserName, content, nonce, timestamp string) ([]byte, error) {
    encryptBody := &EncryptResponseBody{}

    encryptXmlData, _ := MakeEncryptXmlData(fromUserName, toUserName, timestamp, content)
    encryptBody.Encrypt = Value2CDATA(encryptXmlData)
    encryptBody.MsgSignature = Value2CDATA(MakeMsgSignature(timestamp, nonce, encryptXmlData))
    encryptBody.TimeStamp = timestamp
    encryptBody.Nonce = Value2CDATA(nonce)

    return xml.MarshalIndent(encryptBody, " ", "  ")
}

func MakeEncryptXmlData(fromUserName, toUserName, timestamp, content string) (string, error) {
    textResponseBody := &TextResponseBody{}
    textResponseBody.FromUserName = Value2CDATA(fromUserName)
    textResponseBody.ToUserName = Value2CDATA(toUserName)
    textResponseBody.MsgType = Value2CDATA("text")
    textResponseBody.Content = Value2CDATA(content)
    textResponseBody.CreateTime = timestamp
    body, err := xml.MarshalIndent(textResponseBody, " ", "  ")
    if err != nil {
        return "", errors.New("xml marshal error")
    }

    buf := new(bytes.Buffer)
    err = binary.Write(buf, binary.BigEndian, int32(len(body)))
    if err != nil {
        mlog.AppendObj(err, "Binary write err:", err)
    }
    bodyLength := buf.Bytes()

    randomBytes := []byte("abcdefghijklmnop")

    plainData := bytes.Join([][]byte{randomBytes, bodyLength, body, []byte(appID)}, nil)
    cipherData, err := AesEncrypt(plainData, AesKey)
    if err != nil {
        return "", errors.New("AesEncrypt error")
    }
    return base64.StdEncoding.EncodeToString(cipherData), nil
}

// PadLength calculates padding length, from github.com/vgorin/cryptogo
func PadLength(slice_length, blocksize int) (padlen int) {
    padlen = blocksize - slice_length%blocksize
    if padlen == 0 {
        padlen = blocksize
    }
    return padlen
}

//from github.com/vgorin/cryptogo
func PKCS7Pad(message []byte, blocksize int) (padded []byte) {
    // block size must be bigger or equal 2
    if blocksize < 1<<1 {
        panic("block size is too small (minimum is 2 bytes)")
    }
    // block size up to 255 requires 1 byte padding
    if blocksize < 1<<8 {
        // calculate padding length
        padlen := PadLength(len(message), blocksize)

        // define PKCS7 padding block
        padding := bytes.Repeat([]byte{byte(padlen)}, padlen)

        // apply padding
        padded = append(message, padding...)
        return padded
    }
    // block size bigger or equal 256 is not currently supported
    panic("unsupported block size")
}

func AesEncrypt(plainData []byte, aesKey []byte) ([]byte, error) {
    k := len(aesKey)
    if len(plainData)%k != 0 {
        plainData = PKCS7Pad(plainData, k)
    }

    block, err := aes.NewCipher(aesKey)
    if err != nil {
        return nil, err
    }

    iv := make([]byte, aes.BlockSize)
    if _, err := io.ReadFull(rand.Reader, iv); err != nil {
        return nil, err
    }

    cipherData := make([]byte, len(plainData))
    blockMode := cipher.NewCBCEncrypter(block, iv)
    blockMode.CryptBlocks(cipherData, plainData)

    return cipherData, nil
}

func AesDecrypt(cipherData []byte, aesKey []byte) ([]byte, error) {
    k := len(aesKey) //PKCS#7
    if len(cipherData)%k != 0 {
        return nil, errors.New("crypto/cipher: ciphertext size is not multiple of aes key length")
    }

    block, err := aes.NewCipher(aesKey)
    if err != nil {
        return nil, err
    }

    iv := make([]byte, aes.BlockSize)
    if _, err := io.ReadFull(rand.Reader, iv); err != nil {
        return nil, err
    }
    blockMode := cipher.NewCBCDecrypter(block, iv)
    plainData := make([]byte, len(cipherData))
    blockMode.CryptBlocks(plainData, cipherData)
    return plainData, nil
}

func ValidateAppId(id []byte) bool {
    if string(id) == appID {
        return true
    }
    return false
}

func ParseEncryptTextRequestBody(plainText []byte) (*TextRequestBody, error) {

    // Read length
    buf := bytes.NewBuffer(plainText[16:20])
    var length int32
    binary.Read(buf, binary.BigEndian, &length)

    // appID validation
    appIDstart := 20 + length
    id := plainText[appIDstart : int(appIDstart)+len(appID)]
    if !ValidateAppId(id) {
        mlog.AppendObj(nil, "Wechat Message Service: appid is invalid!")
        return nil, errors.New("Appid is invalid")
    }
    mlog.AppendObj(nil, "Wechat Message Service: appid validation is ok!")

    textRequestBody := &TextRequestBody{}
    xml.Unmarshal(plainText[20:20+length], textRequestBody)
    return textRequestBody, nil
}

func ParseEncryptResponse(responseEncryptTextBody []byte) {
    textResponseBody := &EncryptResponseBody1{}
    xml.Unmarshal(responseEncryptTextBody, textResponseBody)

    if !ValidateMsg(textResponseBody.TimeStamp, textResponseBody.Nonce, textResponseBody.Encrypt, textResponseBody.MsgSignature) {
        mlog.AppendInfo("msg signature is invalid")
        return
    }

    cipherData, err := base64.StdEncoding.DecodeString(textResponseBody.Encrypt)
    if err != nil {
        mlog.AppendObj(err, "Wechat Message Service: Decode base64 error")
        return
    }

    plainText, err := AesDecrypt(cipherData, AesKey)
    if err != nil {
        mlog.AppendInfo(err)
        return
    }

    mlog.AppendInfo(string(plainText))
}

func DecryptWechatAppletUser(encryptedData string, session_key string, iv string) ([]byte, error) {
    ciphertext, _ := base64.StdEncoding.DecodeString(encryptedData)
    key, _ := base64.StdEncoding.DecodeString(session_key)
    keyBytes := []byte(key)
    block, err := aes.NewCipher(keyBytes) //选择加密算法
    if err != nil {
        return nil, err
    }
    iv_b, _ := base64.StdEncoding.DecodeString(iv)
    blockModel := cipher.NewCBCDecrypter(block, iv_b)
    plantText := make([]byte, len(ciphertext))
    blockModel.CryptBlocks(plantText, ciphertext)
    plantText = PKCS7UnPadding(plantText, block.BlockSize())
    return plantText, nil
    }

func PKCS7UnPadding(plantText []byte, blockSize int) []byte {
    length := len(plantText)
    unpadding := int(plantText[length-1])
    return plantText[:(length - unpadding)]
}