接收微信消息的接口内容:
//"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)]
}