Last active
December 29, 2022 15:42
-
-
Save lakemove/5283850e4316c54ac4755cfda01ade7c to your computer and use it in GitHub Desktop.
weixin api message encryption and decryption
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"bytes" | |
"crypto/aes" | |
"crypto/cipher" | |
"crypto/rand" | |
"crypto/sha1" | |
"encoding/base64" | |
"encoding/binary" | |
"encoding/hex" | |
"encoding/json" | |
"encoding/xml" | |
"fmt" | |
"io" | |
mrand "math/rand" | |
"sort" | |
"strings" | |
"time" | |
) | |
// weixinEncrypt encrypts plaintext and signs ciphertext | |
func weixinEncrypt(plaintext, key, appid, token string) (ciphertext, signature, nonce, ts string, err error) { | |
var ( | |
buf bytes.Buffer | |
r = rand.Reader | |
) | |
// pin randomness, helps debugging | |
// r = strings.NewReader("aaaaaaaaaaaaaaaa") | |
// nonce = "n123" | |
// ts = "ts123" | |
io.CopyN(&buf, r, 16) // 16 bytes random string | |
binary.Write(&buf, binary.BigEndian, uint32(len(plaintext))) // 4 bytes of length of text plaintext, in big-endian (network byte order in TCP/IP stack means big-endian) | |
buf.WriteString(plaintext) // text msg | |
buf.WriteString(appid) // appid | |
npad := 32 - buf.Len()%32 // 0 < npad <= 31 | |
buf.Write(bytes.Repeat([]byte{byte(npad)}, npad)) | |
bkey, err := base64.StdEncoding.DecodeString(key + "=") | |
if err != nil { | |
return | |
} | |
cipherBlock, err := aes.NewCipher(bkey) | |
if err != nil { | |
return | |
} | |
encrypter := cipher.NewCBCEncrypter(cipherBlock, bkey[:16]) | |
data := make([]byte, buf.Len()) | |
copy(data, buf.Bytes()) | |
encrypter.CryptBlocks(data, data) | |
ciphertext = base64.StdEncoding.EncodeToString(data) | |
if ts == "" { | |
ts = fmt.Sprint(time.Now().UnixMilli()) | |
} | |
if nonce == "" { | |
nonce = fmt.Sprint(mrand.Int63()) | |
} | |
signature = weixinSign(ciphertext, token, nonce, ts) | |
return | |
} | |
// weixinDecrypt decrypts ciphertext | |
func weixinDecrypt(ciphertext, key string) (plaintext string, err error) { | |
bkey, err := base64.StdEncoding.DecodeString(key + "=") | |
cipherBlock, err := aes.NewCipher(bkey) | |
if err != nil { | |
return | |
} | |
cipherbytes, err := base64.StdEncoding.DecodeString(ciphertext) | |
if err != nil { | |
return | |
} | |
decryptor := cipher.NewCBCDecrypter(cipherBlock, bkey[:16]) | |
data := make([]byte, len(cipherbytes)) | |
copy(data, cipherbytes) | |
decryptor.CryptBlocks(data, data) | |
n := binary.BigEndian.Uint32(data[16:20]) | |
plaintext = string(data[20 : 20+n]) // drop 16 + 4 at head and appid + npad at tail | |
return | |
} | |
// weixinSign signs ciphertext | |
func weixinSign(ciphertext, token, nonce, ts string) (signature string) { | |
sigInputs := []string{ | |
token, | |
ts, | |
nonce, | |
ciphertext, | |
} | |
sort.Strings(sigInputs) | |
sha1sum := sha1.Sum([]byte(strings.Join(sigInputs, ""))) | |
signature = hex.EncodeToString(sha1sum[:]) | |
return | |
} | |
// weixinVerify verifies signature | |
func weixinVerify(ciphertext, signature, token, nonce, ts string) (verified bool) { | |
verified = weixinSign(ciphertext, token, nonce, ts) == signature | |
return | |
} | |
// # References | |
// [1]: https://developers.weixin.qq.com/doc/aispeech/confapi/thirdkefu/flow.html "第三方客服系统接入 - 接入和数据运转流程" | |
// [2]: https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/2.0/api/Before_Develop/Message_encryption_and_decryption.html "消息加解密说明" | |
// [3]: https://res.wx.qq.com/op_res/-serEQ6xSDVIjfoOHcX78T1JAYX-pM_fghzfiNYoD8uHVd3fOeC0PC_pvlg4-kmP "示例代码下载 -- too out-dated, fk" | |
// [4]: https://developers.weixin.qq.com/doc/aispeech/platform/example/kefu.html "智能对话 第三方客服系统接入" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment