Files
utils/tencentx/wechatx/cryptx/cryptx.go
T
2023-09-16 20:14:20 +08:00

361 lines
10 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package cryptx
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/sha1"
"encoding/base64"
"encoding/binary"
"encoding/xml"
"errors"
"fmt"
"math/rand"
"sort"
"strings"
"time"
)
const (
LETTERDIGITS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
LD_COUNT = 62
)
const (
WXBizMsgCrypt_OK = 0
WXBizMsgCrypt_ValidateSignature_Error = -40001
WXBizMsgCrypt_ParseXml_Error = -40002
WXBizMsgCrypt_ComputeSignature_Error = -40003
WXBizMsgCrypt_IllegalAesKey = -40004
WXBizMsgCrypt_ValidateAppid_Error = -40005
WXBizMsgCrypt_EncryptAES_Error = -40006
WXBizMsgCrypt_DecryptAES_Error = -40007
WXBizMsgCrypt_IllegalBuffer = -40008
WXBizMsgCrypt_EncodeBase64_Error = -40009
WXBizMsgCrypt_DecodeBase64_Error = -40010
WXBizMsgCrypt_GenReturnXml_Error = -40011
)
var (
letterDigitArr = []byte(LETTERDIGITS)
)
type (
XMLParse struct {
XMLtmpl string
}
WXBizMsgCrypt interface {
EncryptMsg(sReplyMsg string, sNonce string, timestamp int64) (int, string)
DecryptMsg(sPostData, sMsgSignature string, sTimeStamp int64, sNonce string) (int, string)
}
wxBizMsgCrypt struct {
key string
token string
appid string
}
)
// sToken: 公众平台上,开发者设置的Token
// sEncodingAESKey: 公众平台上,开发者设置的EncodingAESKey
// sAppId: 企业号的AppId
func NewWXBizMsgCrypt(sToken, sEncodingAESKey, sAppId string) (WXBizMsgCrypt, error) {
decodeBytes, err := base64.StdEncoding.DecodeString(sEncodingAESKey + "=")
if err != nil {
return nil, errors.New("EncodingAESKey err")
}
//assert len(key) == 32
if len(decodeBytes) != 32 {
return nil, errors.New("EncodingAESKey length err")
}
c := &wxBizMsgCrypt{
key: string(decodeBytes),
token: sToken,
appid: sAppId,
}
return c, nil
}
// 将公众号回复用户的消息加密打包
// sReplyMsg: 企业号待回复用户的消息,xml格式的字符串
// sTimeStamp: 时间戳,可以自己生成,也可以用URL参数的timestamp,如为None则自动用当前时间
// sNonce: 随机串,可以自己生成,也可以用URL参数的nonce
// sEncryptMsg: 加密后的可以直接回复用户的密文,包括msg_signature, timestamp, nonce, encrypt的xml格式的字符串,
// return:成功0sEncryptMsg,失败返回对应的错误码None
func (c *wxBizMsgCrypt) EncryptMsg(sReplyMsg string, sNonce string, timestamp int64) (int, string) {
pc := NewPrpcrypt(c.key)
ret, encryptBytes := pc.encrypt([]byte(sReplyMsg), c.appid)
if ret != WXBizMsgCrypt_OK {
return ret, ""
}
if timestamp <= 0 {
timestamp = time.Now().Unix()
}
encrypt_text := string(encryptBytes)
timestampStr := fmt.Sprintf("%d", timestamp)
// 生成安全签名
ret, signature := getSHA1(c.token, timestampStr, sNonce, encrypt_text)
if ret != WXBizMsgCrypt_OK {
return ret, ""
}
xmlParse := NewXMLParse()
resp_xml := xmlParse.generate(encrypt_text, signature, timestampStr, sNonce)
return ret, resp_xml
}
// 检验消息的真实性,并且获取解密后的明文
// sMsgSignature: 签名串,对应URL参数的msg_signature
// sTimeStamp: 时间戳,对应URL参数的timestamp
// sNonce: 随机串,对应URL参数的nonce
// sPostData: 密文,对应POST请求的数据
// xml_content: 解密后的原文,当return返回0时有效
// return: 成功0,失败返回对应的错误码
// 验证安全签名
func (c *wxBizMsgCrypt) DecryptMsg(sPostData, sMsgSignature string, sTimeStamp int64, sNonce string) (int, string) {
// 验证安全签名
xmlParse := NewXMLParse()
ret, encrypt, _ := xmlParse.extract(sPostData)
if ret != WXBizMsgCrypt_OK {
return ret, ""
}
timestampStr := fmt.Sprintf("%d", sTimeStamp)
ret, signature := getSHA1(c.token, timestampStr, sNonce, encrypt)
if ret != WXBizMsgCrypt_OK {
return ret, ""
}
if signature != sMsgSignature {
return WXBizMsgCrypt_ValidateSignature_Error, ""
}
pc := NewPrpcrypt(c.key)
ret, xml_content := pc.decrypt([]byte(encrypt), c.appid)
return ret, string(xml_content)
}
// 提供提取消息格式中的密文及生成回复消息格式的接口
func NewXMLParse() *XMLParse {
xp := &XMLParse{
// xml消息模板
XMLtmpl: `<xml>
<Encrypt><![CDATA[%(msg_encrypt)s]]></Encrypt>
<MsgSignature><![CDATA[%(msg_signaturet)s]]></MsgSignature>
<TimeStamp>%(timestamp)s</TimeStamp>
<Nonce><![CDATA[%(nonce)s]]></Nonce>
</xml>`,
}
return xp
}
type XmlResources struct {
XMLName xml.Name `xml:"xml"`
Encrypt string `xml:"Encrypt"`
ToUserName string `xml:"ToUserName"`
}
// 提取出xml数据包中的加密消息
// xmltext: 待提取的xml字符串
// return: 提取出的加密消息字符串
//
func (xp *XMLParse) extract(xmltext string) (int, string, string) {
var result XmlResources
err := xml.Unmarshal([]byte(xmltext), &result)
if err != nil {
fmt.Println(err)
return WXBizMsgCrypt_ParseXml_Error, "", ""
}
return WXBizMsgCrypt_OK, result.Encrypt, result.ToUserName
}
// 生成xml消息
// encrypt: 加密后的消息密文
// signature: 安全签名
// timestamp: 时间戳
// nonce: 随机字符串
// return: 生成的xml字符串
//
func (xp *XMLParse) generate(encrypt, signature, timestampStr, nonce string) string {
resp_xml := strings.Replace(xp.XMLtmpl, "%(msg_encrypt)s", encrypt, 1)
resp_xml = strings.Replace(resp_xml, "%(msg_signaturet)s", signature, 1)
resp_xml = strings.Replace(resp_xml, "%(timestamp)s", timestampStr, 1)
resp_xml = strings.Replace(resp_xml, "%(nonce)s", nonce, 1)
return resp_xml
}
// 计算公众平台的消息签名接口
// 用SHA1算法生成安全签名
// token: 票据
// timestamp: 时间戳
// encrypt: 密文
// nonce: 随机字符串
// return: 安全签名
func getSHA1(token string, timestampStr, nonce, encrypt string) (int, string) {
sortlist := []string{token, timestampStr, nonce, encrypt}
sort.Strings(sortlist)
hashBytes := sha1.Sum([]byte(strings.Join(sortlist, "")))
hashString := fmt.Sprintf("%x", hashBytes)
return WXBizMsgCrypt_OK, hashString
}
type Prpcrypt struct {
key string
mode string
}
// 提供接收和推送给公众平台消息的加解密接口
func NewPrpcrypt(key string) *Prpcrypt {
// 设置加解密模式为AES的CBC模式
pc := &Prpcrypt{
key: key,
mode: "AES.CBC",
}
return pc
}
// s对明文进行加密
// textBytes: 需要加密的明文
// return: 加密得到的字符串
//
func (pc *Prpcrypt) encrypt(textBytes []byte, appid string) (int, []byte) {
//# 16位随机字符串添加到明文开头
rand_bytes := pc.get_random_bytes()
len1 := len(rand_bytes)
len3 := len(textBytes)
len_bytes := htonl(len3)
len2 := len(len_bytes)
appid_bytes := []byte(appid)
len4 := len(appid_bytes)
toBytes := make([]byte, len1+len2+len3+len4)
copy(toBytes[0:len1], rand_bytes)
copy(toBytes[len1:(len1+len2)], len_bytes)
copy(toBytes[(len1+len2):(len1+len2+len3)], textBytes)
copy(toBytes[(len1+len2+len3):(len1+len2+len3+len4)], appid_bytes)
// 使用自定义的填充方式对明文进行补位填充
pkcs7 := NewPKCS7Encoder()
plantBytes := pkcs7.encode(toBytes)
// 加密
keyBytes := []byte(pc.key)
block, err := aes.NewCipher(keyBytes) //选择加密算法
if err != nil {
fmt.Println(err)
return WXBizMsgCrypt_EncryptAES_Error, []byte{}
}
blockModel := cipher.NewCBCEncrypter(block, keyBytes[0:16])
cipherBytes := make([]byte, len(plantBytes))
blockModel.CryptBlocks(cipherBytes, plantBytes)
// 使用BASE64对加密后的字符串进行编码
dstBytes := make([]byte, base64.StdEncoding.EncodedLen(len(cipherBytes)))
base64.StdEncoding.Encode(dstBytes, cipherBytes)
return WXBizMsgCrypt_OK, dstBytes
}
// 对解密后的明文进行补位删除
// cipherBytes: 密文
// return: 删除填充补位后的明文
//
func (pc *Prpcrypt) decrypt(cipherBytes []byte, appid string) (int, []byte) {
_dstBytes := make([]byte, base64.StdEncoding.DecodedLen(len(cipherBytes)))
n, err := base64.StdEncoding.Decode(_dstBytes, cipherBytes)
if err != nil {
fmt.Println(err)
return WXBizMsgCrypt_DecodeBase64_Error, []byte{}
}
dstBytes := _dstBytes[0:n]
keyBytes := []byte(pc.key)
block, err := aes.NewCipher(keyBytes) //选择加密算法
if err != nil {
fmt.Println(err)
return WXBizMsgCrypt_IllegalBuffer, []byte{}
}
blockModel := cipher.NewCBCDecrypter(block, keyBytes[0:16])
plantBytes := make([]byte, len(dstBytes))
blockModel.CryptBlocks(plantBytes, dstBytes)
//
length := len(plantBytes)
pad := int(uint8(plantBytes[length-1]))
// 去除16位随机字符串
content := plantBytes[16:(length - pad)]
xml_len := ntohl(content[0:4])
xml_content := content[4:(xml_len + 4)]
from_appid := content[(xml_len + 4):len(content)]
if string(from_appid) != appid {
return WXBizMsgCrypt_ValidateAppid_Error, []byte{}
}
return WXBizMsgCrypt_OK, xml_content
}
// 随机生成16位字符串
// return: 16位字符串
//
func (pc *Prpcrypt) get_random_bytes() []byte {
rander := rand.New(rand.NewSource(time.Now().UnixNano()))
length := 16
result := make([]byte, length)
for i, _ := range result {
result[i] = letterDigitArr[rander.Intn(LD_COUNT)]
//result[i] = letterDigitArr[52+((i+1)%10)]
}
return result
}
// 提供基于PKCS7算法的加解密接口
type PKCS7Encoder struct {
block_size int
}
func NewPKCS7Encoder() *PKCS7Encoder {
p := &PKCS7Encoder{
block_size: 32,
}
return p
}
// 对需要加密的明文进行填充补位
// text: 需要进行填充补位操作的明文
// return: 补齐明文字符串
func (p *PKCS7Encoder) encode(textBytes []byte) []byte {
length := len(textBytes)
if length == 0 {
return []byte{}
}
// 计算需要填充的位数
amount_to_pad := p.block_size - (length % p.block_size)
if amount_to_pad == 0 {
amount_to_pad = p.block_size
}
// 获得补位所用的字符
padArr := bytes.Repeat([]byte{byte(amount_to_pad)}, amount_to_pad)
encTextBytes := make([]byte, length+amount_to_pad)
copy(encTextBytes, textBytes)
copy(encTextBytes[length:(length+amount_to_pad)], padArr)
return encTextBytes
}
// 删除解密后明文的补位字符
// decrypted: 解密后的明文
// return: 删除补位字符后的明文
func (p *PKCS7Encoder) decode(decrypted []byte) []byte {
length := len(decrypted)
if length == 0 {
return []byte{}
}
pad := int(uint8(decrypted[length-1]))
if pad < 1 || pad > 32 {
pad = 0
}
if pad >= length {
return []byte{}
}
return decrypted[0:(length - pad)]
}
func htonl(n int) []byte {
data := make([]byte, 4)
binary.BigEndian.PutUint32(data, uint32(n))
return data
}
func ntohl(data []byte) int {
n := binary.BigEndian.Uint32(data[0:4])
return int(n)
}