Skip to content

Instantly share code, notes, and snippets.

@lakemove
Last active December 29, 2022 15:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lakemove/5283850e4316c54ac4755cfda01ade7c to your computer and use it in GitHub Desktop.
Save lakemove/5283850e4316c54ac4755cfda01ade7c to your computer and use it in GitHub Desktop.
weixin api message encryption and decryption
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