github.com/sereiner/library@v0.0.0-20200518095232-1fa3e640cc5f/weixin/weixin.go (about)

     1  package weixin
     2  
     3  import (
     4  	"bytes"
     5  	"crypto/aes"
     6  	"crypto/cipher"
     7  	"crypto/rand"
     8  	"crypto/sha1"
     9  	"encoding/base64"
    10  	"encoding/binary"
    11  	"encoding/xml"
    12  	"errors"
    13  	"fmt"
    14  	"io"
    15  	"io/ioutil"
    16  	"log"
    17  	"net/http"
    18  	"sort"
    19  	"strconv"
    20  	"strings"
    21  	"time"
    22  )
    23  
    24  type WechatEntity struct {
    25  	token          string
    26  	appID          string
    27  	encodingAESKey string
    28  	aesKey         []byte
    29  }
    30  
    31  func (e WechatEntity) encodingAESKey2AESKey(encodingKey string) (data []byte, err error) {
    32  	data, err = base64.StdEncoding.DecodeString(encodingKey + "=")
    33  	return
    34  }
    35  
    36  //NewWechatEntity 设置初始参数
    37  func NewWechatEntity(appid string, token string, encodingAESKey string) (e WechatEntity, err error) {
    38  	e = WechatEntity{}
    39  	e.appID = appid
    40  	e.token = token
    41  	e.encodingAESKey = encodingAESKey
    42  	e.aesKey, err = e.encodingAESKey2AESKey(e.encodingAESKey)
    43  	return
    44  }
    45  
    46  type TextRequestBody struct {
    47  	XMLName      xml.Name      `xml:"xml"`
    48  	ToUserName   string        `json:"toUserName"`
    49  	FromUserName string        `json:"fromUserName"`
    50  	CreateTime   time.Duration `json:"createTime"`
    51  	MsgType      string        `json:"msgType"`
    52  	Content      string        `json:"content"`
    53  	MsgId        int           `json:"msgId"`
    54  	Event        string
    55  	EventKey     string
    56  	Latitude     string
    57  	Longitude    string
    58  	Precision    string
    59  	Ticket       string
    60  }
    61  
    62  type TextResponseBody struct {
    63  	XMLName      xml.Name `xml:"xml"`
    64  	ToUserName   CDATAText
    65  	FromUserName CDATAText
    66  	CreateTime   string
    67  	MsgType      CDATAText
    68  	Content      CDATAText
    69  }
    70  
    71  type EncryptRequestBody struct {
    72  	XMLName    xml.Name `xml:"xml"`
    73  	ToUserName string
    74  	Encrypt    string
    75  }
    76  
    77  type EncryptResponseBody struct {
    78  	XMLName      xml.Name `xml:"xml"`
    79  	Encrypt      CDATAText
    80  	MsgSignature CDATAText
    81  	TimeStamp    string
    82  	Nonce        CDATAText
    83  }
    84  
    85  type EncryptResponseBody1 struct {
    86  	XMLName      xml.Name `xml:"xml"`
    87  	Encrypt      string
    88  	MsgSignature string
    89  	TimeStamp    string
    90  	Nonce        string
    91  }
    92  
    93  type CDATAText struct {
    94  	Text string `xml:",innerxml"`
    95  }
    96  
    97  func (e WechatEntity) makeSignature(timestamp, nonce string) string {
    98  	sl := []string{e.token, timestamp, nonce}
    99  	sort.Strings(sl)
   100  	s := sha1.New()
   101  	io.WriteString(s, strings.Join(sl, ""))
   102  	return fmt.Sprintf("%x", s.Sum(nil))
   103  }
   104  
   105  func (e WechatEntity) makeMsgSignature(timestamp, nonce, msg_encrypt string) string {
   106  	sl := []string{e.token, timestamp, nonce, msg_encrypt}
   107  	sort.Strings(sl)
   108  	s := sha1.New()
   109  	io.WriteString(s, strings.Join(sl, ""))
   110  	return fmt.Sprintf("%x", s.Sum(nil))
   111  }
   112  
   113  func (e WechatEntity) validateUrl(timestamp, nonce, signatureIn string) bool {
   114  	signatureGen := e.makeSignature(timestamp, nonce)
   115  	if signatureGen != signatureIn {
   116  		return false
   117  	}
   118  	return true
   119  }
   120  
   121  func (e WechatEntity) validateMsg(timestamp, nonce, msgEncrypt, msgSignatureIn string) bool {
   122  	msgSignatureGen := e.makeMsgSignature(timestamp, nonce, msgEncrypt)
   123  	if msgSignatureGen != msgSignatureIn {
   124  		return false
   125  	}
   126  	return true
   127  }
   128  func (e WechatEntity) Decrypt(content string) (b *TextRequestBody, err error) {
   129  	cipherData, err := base64.StdEncoding.DecodeString(content)
   130  	if err != nil {
   131  		return
   132  	}
   133  
   134  	// AES Decrypt
   135  	plainData, err := aesDecrypt(cipherData, e.aesKey)
   136  	if err != nil {
   137  		return
   138  	}
   139  	b, err = e.parseEncryptTextRequestBody([]byte(plainData))
   140  	return
   141  }
   142  
   143  func (e WechatEntity) parseEncryptRequestBody(r *http.Request) *EncryptRequestBody {
   144  	body, err := ioutil.ReadAll(r.Body)
   145  	if err != nil {
   146  		log.Fatal(err)
   147  		return nil
   148  	}
   149  	requestBody := &EncryptRequestBody{}
   150  	xml.Unmarshal(body, requestBody)
   151  	return requestBody
   152  }
   153  
   154  func (e WechatEntity) parseTextRequestBody(r *http.Request) *TextRequestBody {
   155  	body, err := ioutil.ReadAll(r.Body)
   156  	if err != nil {
   157  		log.Fatal(err)
   158  		return nil
   159  	}
   160  	fmt.Println(string(body))
   161  	requestBody := &TextRequestBody{}
   162  	xml.Unmarshal(body, requestBody)
   163  	return requestBody
   164  }
   165  
   166  func (e WechatEntity) value2CDATA(v string) CDATAText {
   167  	//return CDATAText{[]byte("<![CDATA[" + v + "]]>")}
   168  	return CDATAText{"<![CDATA[" + v + "]]>"}
   169  }
   170  
   171  func (e WechatEntity) makeTextResponseBody(fromUserName, toUserName, content string) ([]byte, error) {
   172  	textResponseBody := &TextResponseBody{}
   173  	textResponseBody.FromUserName = e.value2CDATA(fromUserName)
   174  	textResponseBody.ToUserName = e.value2CDATA(toUserName)
   175  	textResponseBody.MsgType = e.value2CDATA("text")
   176  	textResponseBody.Content = e.value2CDATA(content)
   177  	textResponseBody.CreateTime = strconv.Itoa(int(time.Duration(time.Now().Unix())))
   178  	return xml.MarshalIndent(textResponseBody, " ", "  ")
   179  }
   180  
   181  func (e WechatEntity) makeEncryptResponseBody(fromUserName, toUserName, content, nonce, timestamp string) ([]byte, error) {
   182  	encryptBody := &EncryptResponseBody{}
   183  
   184  	encryptXmlData, _ := e.makeEncryptXmlData(fromUserName, toUserName, timestamp, content)
   185  	encryptBody.Encrypt = e.value2CDATA(encryptXmlData)
   186  	encryptBody.MsgSignature = e.value2CDATA(e.makeMsgSignature(timestamp, nonce, encryptXmlData))
   187  	encryptBody.TimeStamp = timestamp
   188  	encryptBody.Nonce = e.value2CDATA(nonce)
   189  
   190  	return xml.Marshal(encryptBody)
   191  }
   192  
   193  func (e WechatEntity) makeEncryptXmlData(fromUserName, toUserName, timestamp, body string) (string, error) {
   194  	/*textResponseBody := &TextResponseBody{}
   195  	textResponseBody.FromUserName = e.value2CDATA(fromUserName)
   196  	textResponseBody.ToUserName = e.value2CDATA(toUserName)
   197  	textResponseBody.MsgType = e.value2CDATA("text")
   198  	textResponseBody.Content = e.value2CDATA(content)
   199  	textResponseBody.CreateTime = timestamp
   200  	body, err := xml.MarshalIndent(textResponseBody, " ", "  ")
   201  	if err != nil {
   202  		return "", errors.New("xml marshal error")
   203  	}*/
   204  
   205  	buf := new(bytes.Buffer)
   206  	err := binary.Write(buf, binary.BigEndian, int32(len(body)))
   207  	if err != nil {
   208  		fmt.Println("Binary write err:", err)
   209  	}
   210  	bodyLength := buf.Bytes()
   211  
   212  	randomBytes := []byte("abcdefghijklmnop")
   213  
   214  	plainData := bytes.Join([][]byte{randomBytes, bodyLength, []byte(body), []byte(e.appID)}, nil)
   215  	cipherData, err := aesEncrypt(plainData, e.aesKey)
   216  	if err != nil {
   217  		return "", errors.New("aesEncrypt error")
   218  	}
   219  
   220  	return base64.StdEncoding.EncodeToString(cipherData), nil
   221  }
   222  
   223  // PadLength calculates padding length, from github.com/vgorin/cryptogo
   224  func PadLength(slice_length, blocksize int) (padlen int) {
   225  	padlen = blocksize - slice_length%blocksize
   226  	if padlen == 0 {
   227  		padlen = blocksize
   228  	}
   229  	return padlen
   230  }
   231  
   232  //from github.com/vgorin/cryptogo
   233  func PKCS7Pad(message []byte, blocksize int) (padded []byte) {
   234  	// block size must be bigger or equal 2
   235  	if blocksize < 1<<1 {
   236  		panic("block size is too small (minimum is 2 bytes)")
   237  	}
   238  	// block size up to 255 requires 1 byte padding
   239  	if blocksize < 1<<8 {
   240  		// calculate padding length
   241  		padlen := PadLength(len(message), blocksize)
   242  
   243  		// define PKCS7 padding block
   244  		padding := bytes.Repeat([]byte{byte(padlen)}, padlen)
   245  
   246  		// apply padding
   247  		padded = append(message, padding...)
   248  		return padded
   249  	}
   250  	// block size bigger or equal 256 is not currently supported
   251  	panic("unsupported block size")
   252  }
   253  
   254  func aesEncrypt(plainData []byte, aesKey []byte) ([]byte, error) {
   255  	k := len(aesKey)
   256  	if len(plainData)%k != 0 {
   257  		plainData = PKCS7Pad(plainData, k)
   258  	}
   259  
   260  	block, err := aes.NewCipher(aesKey)
   261  	if err != nil {
   262  		return nil, err
   263  	}
   264  
   265  	iv := make([]byte, aes.BlockSize)
   266  	if _, err := io.ReadFull(rand.Reader, iv); err != nil {
   267  		return nil, err
   268  	}
   269  
   270  	cipherData := make([]byte, len(plainData))
   271  	blockMode := cipher.NewCBCEncrypter(block, iv)
   272  	blockMode.CryptBlocks(cipherData, plainData)
   273  
   274  	return cipherData, nil
   275  }
   276  
   277  func aesDecrypt(cipherData []byte, aesKey []byte) ([]byte, error) {
   278  	k := len(aesKey) //PKCS#7
   279  	if len(cipherData)%k != 0 {
   280  		return nil, errors.New("crypto/cipher: ciphertext size is not multiple of aes key length")
   281  	}
   282  
   283  	block, err := aes.NewCipher(aesKey)
   284  	if err != nil {
   285  		return nil, err
   286  	}
   287  
   288  	iv := make([]byte, aes.BlockSize)
   289  	if _, err := io.ReadFull(rand.Reader, iv); err != nil {
   290  		return nil, err
   291  	}
   292  
   293  	blockMode := cipher.NewCBCDecrypter(block, iv)
   294  	plainData := make([]byte, len(cipherData))
   295  	blockMode.CryptBlocks(plainData, cipherData)
   296  	return plainData, nil
   297  }
   298  
   299  func (e WechatEntity) validateAppId(id []byte) bool {
   300  	if string(id) == e.appID {
   301  		return true
   302  	}
   303  	return false
   304  }
   305  
   306  func (e WechatEntity) parseEncryptTextRequestBody(plainText []byte) (*TextRequestBody, error) {
   307  	//fmt.Println(string(plainText), len(plainText))
   308  
   309  	// Read length
   310  	buf := bytes.NewBuffer(plainText[16:20])
   311  	var length int32
   312  	binary.Read(buf, binary.BigEndian, &length)
   313  	//fmt.Println("lenth:", length)
   314  	//	fmt.Println(string(plainText[20 : 20+length]))
   315  
   316  	// appID validation
   317  	appIDstart := 20 + length
   318  	id := plainText[appIDstart : int(appIDstart)+len(e.appID)]
   319  	if !e.validateAppId(id) {
   320  		return nil, errors.New("Appid is invalid")
   321  	}
   322  
   323  	textRequestBody := &TextRequestBody{}
   324  	xml.Unmarshal(plainText[20:20+length], textRequestBody)
   325  	return textRequestBody, nil
   326  }
   327  
   328  func (e WechatEntity) parseEncryptResponse(responseEncryptTextBody []byte) {
   329  	textResponseBody := &EncryptResponseBody1{}
   330  	xml.Unmarshal(responseEncryptTextBody, textResponseBody)
   331  
   332  	if !e.validateMsg(textResponseBody.TimeStamp, textResponseBody.Nonce, textResponseBody.Encrypt, textResponseBody.MsgSignature) {
   333  		fmt.Println("msg signature is invalid")
   334  		return
   335  	}
   336  
   337  	cipherData, err := base64.StdEncoding.DecodeString(textResponseBody.Encrypt)
   338  	if err != nil {
   339  		log.Println("Wechat Service: Decode base64 error:", err)
   340  		return
   341  	}
   342  
   343  	plainText, err := aesDecrypt(cipherData, e.aesKey)
   344  	if err != nil {
   345  		fmt.Println(err)
   346  		return
   347  	}
   348  
   349  	fmt.Println(string(plainText))
   350  }
   351  
   352  func (e WechatEntity) procRequest(w http.ResponseWriter, r *http.Request) {
   353  	r.ParseForm()
   354  
   355  	timestamp := strings.Join(r.Form["timestamp"], "")
   356  	nonce := strings.Join(r.Form["nonce"], "")
   357  	signature := strings.Join(r.Form["signature"], "")
   358  	encryptType := strings.Join(r.Form["encrypt_type"], "")
   359  	msgSignature := strings.Join(r.Form["msg_signature"], "")
   360  
   361  	fmt.Println("timestamp =", timestamp)
   362  	fmt.Println("nonce =", nonce)
   363  	fmt.Println("signature =", signature)
   364  	fmt.Println("msgSignature =", msgSignature)
   365  
   366  	if !e.validateUrl(timestamp, nonce, signature) {
   367  		log.Println("Wechat Service: this http request is not from Wechat platform!")
   368  		return
   369  	}
   370  
   371  	if r.Method == "POST" {
   372  		if encryptType == "aes" {
   373  			log.Println("Wechat Service: in safe mode")
   374  			encryptRequestBody := e.parseEncryptRequestBody(r)
   375  
   376  			// Validate msg signature
   377  			if !e.validateMsg(timestamp, nonce, encryptRequestBody.Encrypt, msgSignature) {
   378  				log.Println("Wechat Service: msg_signature is invalid")
   379  				return
   380  			}
   381  			log.Println("Wechat Service: msg_signature validation is ok!")
   382  
   383  			// Decode base64
   384  			cipherData, err := base64.StdEncoding.DecodeString(encryptRequestBody.Encrypt)
   385  			if err != nil {
   386  				log.Println("Wechat Service: Decode base64 error:", err)
   387  				return
   388  			}
   389  
   390  			// AES Decrypt
   391  			plainData, err := aesDecrypt(cipherData, e.aesKey)
   392  			if err != nil {
   393  				fmt.Println(err)
   394  				return
   395  			}
   396  
   397  			textRequestBody, _ := e.parseEncryptTextRequestBody(plainData)
   398  			fmt.Println(textRequestBody)
   399  			fmt.Printf("Wechat Service: Recv text msg [%s] from user [%s]!",
   400  				textRequestBody.Content,
   401  				textRequestBody.FromUserName)
   402  
   403  			responseEncryptTextBody, _ := e.makeEncryptResponseBody(textRequestBody.ToUserName,
   404  				textRequestBody.FromUserName,
   405  				"Hello, "+textRequestBody.FromUserName,
   406  				nonce,
   407  				timestamp)
   408  			w.Header().Set("Content-Type", "text/xml")
   409  			fmt.Println("\n", string(responseEncryptTextBody))
   410  			fmt.Fprintf(w, string(responseEncryptTextBody))
   411  
   412  			e.parseEncryptResponse(responseEncryptTextBody)
   413  		} else if encryptType == "raw" {
   414  			log.Println("Wechat Service: in raw mode")
   415  		}
   416  	}
   417  }
   418  
   419  /*
   420  func main() {
   421  	log.Println("Wechat Service: Start!")
   422  	http.HandleFunc("/", procRequest)
   423  	err := http.ListenAndServe(":80", nil)
   424  	if err != nil {
   425  		log.Fatal("Wechat Service: ListenAndServe failed, ", err)
   426  	}
   427  	log.Println("Wechat Service: Stop!")
   428  }
   429  */