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

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

    //"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)]
}

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

上一篇:Zabbix报警脚本-微信


下一篇:Linux防火墙设置-DNS服务器篇