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 */