Develop WeChat public platform with Golang-receive encrypted messages

Source: Internet
Author: User
Tags decrypt sha1
This is a creation in Article, where the information may have evolved or changed.

In the previous article, "Receiving text messages", we learned that the message between the public service and the server is "bare-Ben" (that is, the plaintext transmission, which can be seen through the capture packet). Obviously this is for some high security requirements of the large enterprise service number, such as banks, securities, telecommunications operators or aviation customer service is not fully meet the requirements. Thus there is a data encryption communication process between the server and the public service.

The public number administrator can choose whether to use "Safe Mode" (as distinct from clear text mode) in the public number "Developer Center":

Once Safe mode is selected, the server encrypts portions of the XML packet as it forwards the message to the public number service. The XML data in this type of encrypted request body becomes the following:

The basic structure of the XML data becomes:


xx
xx

Additionally, in "Safe Mode", two fields are added to the Http Post Request line: Encrypt_type and msg_signuature for message type determination and cryptographic message content validation:

POST/?signature=891789ec400309a6be74ac278030e472f90782a5xtamp=1419214101&nonce=788148964&encrypt_type= aes&msg_signature=87d7b127fab3771b452bc6a592f530cd8edba950 http/1.1\r\n

which

Encrypt_type = "AES", which indicates that the message is encrypted, otherwise "raw", that is, the message is unencrypted.
MSG_SIGNATURE=SHA1 (sort (Token, timestamp, nonce, Msg_encrypt))

For the test number, the test number configuration page does not have encryption-related configuration, so only through the "Public Platform Interface debugging Tool" to debug the relevant cryptographic interface.

First, message signature verification

For the message interaction under "Safe Mode", the first thing to do is to verify the message signature, and only the authenticated message will be decrypted, parsed and processed in the next step.

The principle of message signature verification is to compare the msg_signature that are carried in the platform HTTP Post line with the msg_signture of four field values, such as token, timestamp, nonce, and Msg_encrypt. Consistency is verified by message signing.

We still complete the signature verification of the message under "Safe Mode" in Procrequest.

Recvencryptedtextmsg.go
Type encryptrequestbody struct {
XMLName XML. Name ' xml: ' XML '
Tousername string
Encrypt string
}

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 to FMT. Sprintf ("%x", S.sum (nil))
}

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 {
Log. Fatal (ERR)
return Nil
}
Requestbody: = &encryptrequestbody{}
Xml. Unmarshal (body, requestbody)
Return Requestbody
}

Func procrequest (w http. Responsewriter, 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"], "")

... ...

F R.method = = "POST" {
if Encrypttype = = "AES" {
Log. Println ("Wechat service:in Safe Mode")
Encryptrequestbody: = Parseencryptrequestbody (R)

Validate MSG Signature
If!validatemsg (timestamp, nonce, Encryptrequestbody.encrypt, msgsignature) {
Log. Println ("Wechat service:msg_signature is invalid")
Return
}
Log. Println ("Wechat service:msg_signature validation is ok!")
... ...
}
... ...
}

The results of the program compilation execution are as follows:
$sudo./recvencryptedtextmsg
2014/12/22 13:15:56 Wechat service:start!

Send a message to the public by mobile phone, the program output the following results:

2014/12/22 13:17:35 Wechat service:in Safe Mode
2014/12/22 13:17:35 Wechat service:msg_signature validation is ok!

Second, data packet decryption

So far, we've got the encrypt of the encrypted packet encryptrequestbody with the message validation OK. To get the real message content, we need to decrypt the value of the Encrypt field. Using the AES plus decryption scheme, let's look at how to do AES decryption.

When you choose to convert to Safe mode in the developer center, there is a field encodingaeskey to fill, which is fixed to 43 characters, which is the key we need to use the AES algorithm. But this encodingaeskey is a code, the real use to add decryption aeskey need ourselves through the decoding to get. The decoding method is:

Aeskey=base64_decode (encodingaeskey + "=")

Base64 Decode, we get a 32-byte Aeskey, which shows the AES-256 algorithm (256=32x8bit) for encrypting and decrypting.

In Golang, we can get the real aeskey from the following code:

Const (
token = "Wechat4go"
AppID = "WX5B5C2614D269DDB2"
Encodingaeskey = "Kzvgybdkbtpbhv4lbwocdsp5vkta3xe9epvhinevtgg"
)

var Aeskey []byte

Func Encodingaeskey2aeskey (Encodingkey string) []byte {
Data, _: = base64. Stdencoding.decodestring (encodingkey + "=")
Return data
}

Func init () {
Aeskey = Encodingaeskey2aeskey (Encodingaeskey)
}

With Aeskey, we'll decrypt the packet again. The Public platform development document gives the parsing steps for encrypted packets:

1. Aes_msg=base64_decode (Msg_encrypt)
2. Rand_msg=aes_decrypt (aes_msg)
3. Verify that the tail $appid is your own appid, and that the same message is not tampered with, which further strengthens the message signature verification
4. Remove the 16 random bytes from the rand_msg head, and the $appid of the Msg_len and tail of the 4 bytes as the final XML message body

If you can use a simple diagram in the wiki to illustrate the Base64_decode data format is better. Here is a further explanation, the decrypted data, we call it plaindata, it consists of four parts, in order to arrange the following:

1, Random value 16 bytes
2. Xml packet length 4 bytes (note read in Big_endian mode)
3, the XML package (* The length of this part of the data is identified by the previous field, this package is equivalent to a complete text receive message body data, from Tousername to MsgId)
4, AppID

The third paragraph of the XML package is a complete receive text packet, and the "Receive message" in the text of the standard text packet format consistent, which is convenient for us to parse. Well, the following code illustrates the decryption, parsing process, and AppID validation:

In Procrequest, add the following code:

Decode Base64
CipherData, err: = base64. Stdencoding.decodestring (Encryptrequestbody.encrypt)
If err! = Nil {
Log. Println ("Wechat service:decode base64 Error:", err)
Return
}

AES Decrypt
Plaindata, err: = Aesdecrypt (CipherData, Aeskey)
If err! = Nil {
Fmt. PRINTLN (ERR)
Return
}

//xml decod ing
Textrequestbody, _: = Parseencrypttextrequestbody (Plaindata)
Fmt. Printf ("Wechat service:recv text msg [%s] from user [%s]!",
Textrequestbody.content,
Textrequestbody.fromusername)

According to the decryption method, we first base64 the Encryptrequestbody.encrypt decode operation to get CipherData, Then use Aesdecrypt to decrypt the cipherdata to get the four-part plaindata mentioned above. Plaindata after XML decoding, we get our textrequestbody struct.

The difficulty here is obviously in the realization of aesdecrypt. The encryption packet uses the AES-256 algorithm, the secret key length 32B, uses the pkcs#7 padding way. Golang provides a powerful AES encryption and decryption method, which we use to implement packet decryption:

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
}

For the decrypted plaindata do AppID checksum as well as XML decoding processing as follows:

Func parseencrypttextrequestbody (plaintext []byte) (*textrequestbody, error) {
Fmt. Println (String (plaintext))

        //Read length
        BUF: = bytes. Newbuffer (plaintext[16:20])
        var length int32
         binary. Read (buf, Binary. Bigendian, &length)
        FMT. Println (String (plaintext[20:20+length))

        //appID validation
         Appidstart: = + length
        id: = Plaintext[appidstart:int ( Appidstart) +len (AppID)]
        if!validateappid (id) {
                 log. Println ("Wechat service:appid is invalid!")
                return nil, errors . New ("Appid is invalid")
       }
         Log. Println ("Wechat service:appid validation is ok!")

//XML decoding
Textrequestbody: = &textrequestbody{}
Xml. Unmarshal (Plaintext[20:20+length], textrequestbody)
Return Textrequestbody, Nil
}

Compile execution output Textrequestbody:

&{{XML} gh_6ebaca4bb551 on95ht9upitsmzmq_mvuz4h6f6ci 1.419239875s text Hello, Wechat 6095588848508047134 }

Third, packet encryption of response message

Public Platform Development documentation requirements: Public accounts ' replies to ciphertext messages also require encryption.

Compare the normal response message format and the encrypted response message format:

After encryption:

We define a struct map response message packet:

Type encryptresponsebody struct {
XMLName XML. Name ' xml: ' XML '
Encrypt Cdatatext
Msgsignature Cdatatext
TimeStamp string
Nonce Cdatatext
}

Type Cdatatext struct {
Text string ' xml: ', InnerXml "'
}

All we have to do is assign a value to the instance of Encryptresponsebody and then pass the XML. Marshalindent into an XML data stream, each field value generation rule is as follows:

Encrypt = Base64_encode (Aes_encrypt [Random (16B) + Msg_len (4B) + msg + $AppId])
MSGSIGNATURE=SHA1 (sort (Token, timestamp, nonce, Msg_encrypt))
TimeStamp = Use the value in the request or the new
Nonce = Use the value in the request or the new

The encryption complexity of public interface is higher than decryption, the key problem is the determination of cryptographic results and the debugging of the encryption logic, the results of AES encryption are different every time, we either through the platform real operation verification, or through the provision of online debugging tools to verify that the encryption is correct. It is strongly recommended to use the Online debugging tool (the test number can only select this one).

The configuration reference of the online debugging tool is as follows, Tousername and fromusername recommend filling in the real (by decrypting the Post package printout):

If the online debugging tool receives your response and decrypts it successfully, it will give the following feedback:

In Procrequest, we construct an encrypted response back to the platform or debug tool with the following lines of code after receiving parsing HTTP request:

Responseencrypttextbody, _: =Makeencryptresponsebody(Textrequestbody.tousername,
Textrequestbody.fromusername,
"Hello," +textrequestbody.fromusername,
Nonce
Timestamp
W.header (). Set ("Content-type", "Text/xml")
Fmt. fprintf (W, String (responseencrypttextbody))

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, "", "")
}

Only the Encrypt field in the answer XML package is encrypted, and the field is generated in the following way:

Func makeencryptxmldata (Fromusername, tousername, timestamp, content String) (string, error) {
//Encrypt part3:xml Encoding
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")
}

Encrypt part2:length bytes
BUF: = new (bytes. Buffer)
Err = binary. Write (buf, Binary. Bigendian, Int32 (Len (body)))
If err! = Nil {
Fmt. Println ("Binary Write err:", err)
}
Bodylength: = buf. Bytes ()

Encrypt part1:random bytes
Randombytes: = []byte ("Abcdefghijklmnop")

//Encrypt part, with Part4 -AppID
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
}

Func aesencrypt (Plaindata []byte, Aeskey []byte) ([]byte, error) {
        k: = Len (A Eskey)
        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
}

According to the official documentation: the AES used in the CBC mode, the key length is 32 bytes (Aeskey), the data is filled with pkcs#7, the pkcs#7:k is the number of key bytes (in terms of), buf for the content to be encrypted, N is its number of bytes. buf needs to be filled with an integer multiple of K . So when we pad to encrypt the data, make sure Pad is an integer multiple of K (=32) , not AES. An integer multiple of BlockSize (=16).

The public number after the Safe Mode interactive performance appears to have dropped, sending "Hello, WeChat" to the public number after a long time to receive a response.

The code of the public to receive encrypted messages can be downloaded here. These codes are just demo code, which is not optimized in structure and can be encapsulated into a common interface to lay the groundwork for subsequent public platform interface Development.

, Bigwhite. All rights reserved.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.