15 changed files with 1009 additions and 1 deletions
@ -0,0 +1,5 @@ |
|||
package wechaturl |
|||
|
|||
//企业微信回调
|
|||
|
|||
type ApiRouter struct{} |
|||
@ -0,0 +1,19 @@ |
|||
package wechaturl |
|||
|
|||
import ( |
|||
"key_performance_indicators/middleware/wechatapp" |
|||
|
|||
"github.com/gin-gonic/gin" |
|||
) |
|||
|
|||
// 企业微信回调路由
|
|||
func (a *ApiRouter) RouterGroup(router *gin.RouterGroup) { |
|||
apiRouter := router.Group("wechatcallback") |
|||
|
|||
var methodBinding = wechatapp.WechatAppApi.WechatCallBackApi |
|||
{ |
|||
apiRouter.GET("", methodBinding.Index) //入口
|
|||
apiRouter.POST("", methodBinding.Index) //入口
|
|||
apiRouter.GET("callback_message_api", methodBinding.CallbackMessageApi) //回调入口
|
|||
} |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
package wechatapp |
|||
|
|||
import ( |
|||
"key_performance_indicators/middleware/wechatapp/wechatcallback" |
|||
"key_performance_indicators/middleware/wechatapp/wechatsendmsg" |
|||
) |
|||
|
|||
type WechatApp struct { |
|||
WechatCallBackApi wechatcallback.ApiRouter |
|||
WechatSendMsgApi wechatsendmsg.ApiRouter |
|||
} |
|||
|
|||
var WechatAppApi = new(WechatApp) |
|||
@ -0,0 +1,125 @@ |
|||
package wechatcallback |
|||
|
|||
import ( |
|||
"fmt" |
|||
"key_performance_indicators/middleware/wechatapp/wechatstatice" |
|||
"key_performance_indicators/models/wechatcallback" |
|||
"key_performance_indicators/overall" |
|||
"key_performance_indicators/overall/publicmethod" |
|||
"strconv" |
|||
"time" |
|||
|
|||
"github.com/gin-gonic/gin" |
|||
) |
|||
|
|||
// 企业微信回调入口
|
|||
func (a *ApiRouter) Index(c *gin.Context) { |
|||
outputCont := publicmethod.MapOut[string]() |
|||
outputCont["index"] = "企业微信回调入口" |
|||
publicmethod.Result(0, outputCont, c) |
|||
} |
|||
|
|||
/* |
|||
* |
|||
@ 作者: 秦东 |
|||
@ 时间: 2022-09-27 11:33:29 |
|||
@ 功能: 回调入口 |
|||
@ 参数 |
|||
|
|||
#MsgSignature 企业微信加密签名,msg_signature计算结合了企业填写的token、请求中的timestamp、nonce、加密的消息体。 |
|||
#Timestamp 时间戳。与nonce结合使用,用于防止请求重放攻击。 |
|||
#Nonce 随机数。与timestamp结合使用,用于防止请求重放攻击。 |
|||
#Echostr 加密的字符串。需要解密得到消息内容明文,解密后有random、msg_len、msg、receiveid四个字段,其中msg即为消息内容明文 |
|||
|
|||
@ 返回值 |
|||
|
|||
# |
|||
*/ |
|||
func (a *ApiRouter) CallbackMessageApi(c *gin.Context) { |
|||
MsgSignature := c.Query("msg_signature") //企业微信加密签名,msg_signature计算结合了企业填写的token、请求中的timestamp、nonce、加密的消息体。
|
|||
Timestamp := c.Query("timestamp") //时间戳。与nonce结合使用,用于防止请求重放攻击。
|
|||
Nonce := c.Query("nonce") //随机数。与timestamp结合使用,用于防止请求重放攻击。
|
|||
Echostr := c.Query("echostr") //加密的字符串。需要解密得到消息内容明文,解密后有random、msg_len、msg、receiveid四个字段,其中msg即为消息内容明文
|
|||
EchostrType := c.Query("type") |
|||
SystemApp := c.Query("systemapp") |
|||
|
|||
if EchostrType == "" { |
|||
EchostrType = "json" |
|||
} |
|||
if SystemApp == "" { |
|||
SystemApp = "kpi" |
|||
} |
|||
// fmt.Printf("(1)SystemApp---------->%v--->EchostrType---------->%v--->MsgSignature---------->%v--->Timestamp---------->%v--->Nonce---------->%v--->Echostr---------->%v\n", SystemApp, EchostrType, MsgSignature, Timestamp, Nonce, Echostr)
|
|||
|
|||
var basicValueCallback CallBackData //企业微信回调基础参数
|
|||
basicValueCallback.MsgSignature = MsgSignature |
|||
basicValueCallback.Timestamp = Timestamp |
|||
basicValueCallback.Nonce = Nonce |
|||
basicValueCallback.DataType = EchostrType |
|||
basicValueCallback.SystemApp = SystemApp |
|||
var msgStr string |
|||
if Echostr != "" { |
|||
//Api地址验证
|
|||
basicValueCallback.Echostr = Echostr |
|||
msgStr = basicValueCallback.VerificationUrl() |
|||
c.String(200, msgStr) |
|||
} else { |
|||
//回调事件
|
|||
fmt.Printf("回调事件") |
|||
} |
|||
// fmt.Printf("(3)CallbackMessageApi---------->%v------------------->%v\n", msgStr, basicValueCallback)
|
|||
|
|||
// publicmethod.Result(1, basicValueCallback, c, msgStr)
|
|||
} |
|||
|
|||
// 验证URL
|
|||
func (c *CallBackData) VerificationUrl() (msg string) { |
|||
// wecahtCpt := WechatVerification()
|
|||
// timestampStr := strconv.FormatInt(c.Timestamp, 10)
|
|||
// echoStr, cryptErr := wecahtCpt.VerifyURL(c.MsgSignature, timestampStr, c.Nonce, c.Echostr)
|
|||
|
|||
// if nil != cryptErr {
|
|||
// fmt.Println("verifyUrl fail", cryptErr)
|
|||
// }
|
|||
// msg = string(echoStr)
|
|||
// jsonk, _ := json.Marshal(c)
|
|||
// fmt.Printf("json--------->%v\n", string(jsonk))
|
|||
switch c.DataType { |
|||
case "json": |
|||
wxcptJson := wechatstatice.WechatDecryptJson(c.SystemApp) |
|||
echoStr, cryptErr := wxcptJson.VerifyURL(c.MsgSignature, c.Timestamp, c.Nonce, c.Echostr) |
|||
fmt.Printf("(2)wxcptJson---------->%v------------echoStr------->%v----cryptErr---->%v-----MsgSignature--->%v----Timestamp---->%v-----Nonce--->%v-----Echostr--->%v\n", wxcptJson, string(echoStr), cryptErr, c.MsgSignature, c.Timestamp, c.Nonce, c.Echostr) |
|||
msg = string(echoStr) |
|||
|
|||
var callbackLog wechatcallback.CallbackLog |
|||
callbackLog.MsgSignature = c.MsgSignature |
|||
TimestampInt, _ := strconv.ParseInt(c.Timestamp, 10, 64) |
|||
callbackLog.TimeStamp = TimestampInt |
|||
callbackLog.Nonce = c.Nonce |
|||
callbackLog.Echostr = c.Echostr |
|||
callbackLog.Xmlstr = string(echoStr) |
|||
// callbackLog.Reqdata = string(reqData)
|
|||
callbackLog.AddTime = time.Now().Unix() |
|||
overall.CONSTANT_DB_WECHAT_LOG.Create(&callbackLog) |
|||
default: |
|||
wxcptXml := wechatstatice.WechatDecryptXml(c.SystemApp) |
|||
echoStr, cryptErr := wxcptXml.VerifyURL(c.MsgSignature, c.Timestamp, c.Nonce, c.Echostr) |
|||
fmt.Printf("wxcptXml---------->%v------------echoStr------->%v----cryptErr---->%v-----MsgSignature--->%v----Timestamp---->%v-----Nonce--->%v-----Echostr--->%v\n", wxcptXml, string(echoStr), cryptErr, c.MsgSignature, c.Timestamp, c.Nonce, c.Echostr) |
|||
msg = string(echoStr) |
|||
var callbackLog wechatcallback.CallbackLog |
|||
callbackLog.MsgSignature = c.MsgSignature |
|||
TimestampInt, _ := strconv.ParseInt(c.Timestamp, 10, 64) |
|||
callbackLog.TimeStamp = TimestampInt |
|||
callbackLog.Nonce = c.Nonce |
|||
callbackLog.Echostr = c.Echostr |
|||
callbackLog.Xmlstr = string(echoStr) |
|||
// callbackLog.Reqdata = string(reqData)
|
|||
callbackLog.AddTime = time.Now().Unix() |
|||
overall.CONSTANT_DB_WECHAT_LOG.Create(&callbackLog) |
|||
} |
|||
|
|||
return |
|||
// fmt.Println(string(echoStr))
|
|||
// fmt.Print(string(echoStr))
|
|||
|
|||
} |
|||
@ -0,0 +1,27 @@ |
|||
package wechatcallback |
|||
|
|||
//企业微信回调
|
|||
type ApiRouter struct{} |
|||
|
|||
//企业微信回调基础参数
|
|||
type CallBackData struct { |
|||
MsgSignature string `json:"msg_signature"` |
|||
Timestamp string `json:"timestamp"` |
|||
Nonce string `json:"nonce"` |
|||
Echostr string `json:"echostr"` |
|||
ToUserName string `json:"tousername"` |
|||
AgentID string `json:"agentid"` |
|||
Encrypt string `json:"encrypt"` |
|||
DataType string `json:"datatype"` |
|||
SystemApp string `json:"systemapp"` |
|||
} |
|||
|
|||
type MsgContent struct { |
|||
ToUsername string `json:"ToUserName"` |
|||
FromUsername string `json:"FromUserName"` |
|||
CreateTime uint32 `json:"CreateTime"` |
|||
MsgType string `json:"MsgType"` |
|||
Content string `json:"Content"` |
|||
Msgid uint64 `json:"MsgId"` |
|||
Agentid uint32 `json:"AgentId"` |
|||
} |
|||
@ -0,0 +1,4 @@ |
|||
package wechatsendmsg |
|||
|
|||
//企业微信发送消息
|
|||
type ApiRouter struct{} |
|||
@ -0,0 +1,140 @@ |
|||
package wechatstatice |
|||
|
|||
import ( |
|||
"encoding/json" |
|||
"fmt" |
|||
"key_performance_indicators/middleware/grocerystore" |
|||
"key_performance_indicators/middleware/wechatapp/wxbizjsonmsgcrypt" |
|||
"key_performance_indicators/middleware/wechatapp/wxbizmsgcrypt" |
|||
"key_performance_indicators/overall" |
|||
"key_performance_indicators/overall/publicmethod" |
|||
) |
|||
|
|||
/* |
|||
获取企业微信token |
|||
@systemApp 系统 |
|||
*/ |
|||
func GetWechatToken(systemApp string) (token string, err error) { |
|||
redisClient := grocerystore.RunRedis(overall.CONSTANT_REDIS3) |
|||
companyId := overall.CONSTANT_CONFIG.WechatCompany.CompanyId |
|||
redisFileKey := fmt.Sprintf("Wechat:GainToken:%v_%v", overall.CONSTANT_CONFIG.RedisPrefixStr.Alias, companyId) |
|||
var secretStr string |
|||
switch systemApp { |
|||
case "kpi": |
|||
redisFileKey = fmt.Sprintf("%v_%v_%v", redisFileKey, systemApp, overall.CONSTANT_CONFIG.WechatKpi.Agentid) |
|||
secretStr = overall.CONSTANT_CONFIG.WechatKpi.Secret |
|||
case "school": |
|||
redisFileKey = fmt.Sprintf("%v_%v_%v", redisFileKey, systemApp, overall.CONSTANT_CONFIG.WechatSchool.Agentid) |
|||
secretStr = overall.CONSTANT_CONFIG.WechatSchool.Secret |
|||
default: |
|||
redisFileKey = fmt.Sprintf("%v_%v", redisFileKey, systemApp) |
|||
} |
|||
isTrue, token := redisClient.Get(redisFileKey) |
|||
if isTrue == true { |
|||
return |
|||
} |
|||
//重新获取token
|
|||
getTokenUrl := "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=" + companyId + "&corpsecret=" + secretStr |
|||
tokenByte := publicmethod.CurlGet(getTokenUrl) |
|||
var callBackCont weChatCallBack |
|||
err = json.Unmarshal(tokenByte, &callBackCont) |
|||
if err != nil { |
|||
return |
|||
} |
|||
if callBackCont.Errcode != 0 { |
|||
return |
|||
} |
|||
token = callBackCont.Accesstoken |
|||
redisClient.SetRedisTime(7200) |
|||
redisClient.Set(redisFileKey, token) |
|||
return |
|||
} |
|||
|
|||
/* |
|||
企业微信方式应用消息URL组装 |
|||
@systemApp 系统 |
|||
*/ |
|||
/** |
|||
@ 作者: 秦东 |
|||
@ 时间: 2022-09-27 10:41:33 |
|||
@ 功能: 企业微信发送应用消息URL组装 |
|||
@ 参数 |
|||
#systemApp 系统 |
|||
#class 类型 |
|||
@ 返回值 |
|||
#sendUrl 发送应用消息URL |
|||
#token token |
|||
#err 系统信息 |
|||
*/ |
|||
func GetSendMsgTokenUrl(systemApp, class string) (sendUrl string, token string, err error) { |
|||
token, err = GetWechatToken(systemApp) |
|||
if err != nil { |
|||
return |
|||
} |
|||
switch class { |
|||
case "update": |
|||
sendUrl = fmt.Sprintf("https://qyapi.weixin.qq.com/cgi-bin/message/update_template_card?access_token=%v", token) |
|||
case "recall": |
|||
sendUrl = fmt.Sprintf("https://qyapi.weixin.qq.com/cgi-bin/message/recall?access_token=%v", token) |
|||
default: |
|||
sendUrl = fmt.Sprintf("https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=%v", token) |
|||
} |
|||
return |
|||
} |
|||
|
|||
//企业微信解密
|
|||
/** |
|||
@ 作者: 秦东 |
|||
@ 时间: 2022-09-27 11:57:56 |
|||
@ 功能: 企业微信加解密-json |
|||
@ 参数 |
|||
#token 应用Token |
|||
#encodingAesKey 应用Encodingaeskey |
|||
@ 返回值 |
|||
#wxcpt 初始化加解密类 |
|||
*/ |
|||
func WechatDecryptJson(systemApp string) (wxcpt *wxbizjsonmsgcrypt.WXBizMsgCrypt) { |
|||
var token string |
|||
var encodingAesKey string |
|||
switch systemApp { |
|||
case "kpi": |
|||
token = overall.CONSTANT_CONFIG.WechatKpi.Token |
|||
encodingAesKey = overall.CONSTANT_CONFIG.WechatKpi.Encodingaeskey |
|||
default: |
|||
token = overall.CONSTANT_CONFIG.WechatSchool.Token |
|||
encodingAesKey = overall.CONSTANT_CONFIG.WechatSchool.Encodingaeskey |
|||
} |
|||
fmt.Printf("WechatDecryptJson------->%v------->%v------->%v\n", token, overall.CONSTANT_CONFIG.WechatCompany.CompanyId, encodingAesKey) |
|||
wxcpt = wxbizjsonmsgcrypt.NewWXBizMsgCrypt(token, encodingAesKey, overall.CONSTANT_CONFIG.WechatCompany.CompanyId, wxbizjsonmsgcrypt.JsonType) |
|||
return |
|||
} |
|||
|
|||
/* |
|||
* |
|||
@ 作者: 秦东 |
|||
@ 时间: 2022-09-27 13:21:07 |
|||
@ 功能: 企业微信加解密-XML |
|||
@ 参数 |
|||
|
|||
#token 应用Token |
|||
#encodingAesKey 应用Encodingaeskey |
|||
|
|||
@ 返回值 |
|||
|
|||
#wxcpt 初始化加解密类 |
|||
*/ |
|||
func WechatDecryptXml(systemApp string) (wxcpt *wxbizmsgcrypt.WXBizMsgCrypt) { |
|||
var token string |
|||
var encodingAesKey string |
|||
switch systemApp { |
|||
case "kpi": |
|||
token = overall.CONSTANT_CONFIG.WechatKpi.Token |
|||
encodingAesKey = overall.CONSTANT_CONFIG.WechatKpi.Encodingaeskey |
|||
default: |
|||
token = overall.CONSTANT_CONFIG.WechatSchool.Token |
|||
encodingAesKey = overall.CONSTANT_CONFIG.WechatSchool.Encodingaeskey |
|||
} |
|||
fmt.Printf("WechatDecryptXml------->%v------->%v------->%v\n", token, overall.CONSTANT_CONFIG.WechatCompany.CompanyId, encodingAesKey) |
|||
wxcpt = wxbizmsgcrypt.NewWXBizMsgCrypt(token, encodingAesKey, overall.CONSTANT_CONFIG.WechatCompany.CompanyId, wxbizmsgcrypt.XmlType) |
|||
return |
|||
} |
|||
@ -0,0 +1,10 @@ |
|||
package wechatstatice |
|||
|
|||
//组织架构返回统类
|
|||
type weChatCallBack struct { |
|||
Errcode int `json:"errcode"` |
|||
Errmsg string `json:"errmsg"` |
|||
Accesstoken string `json:"access_token"` |
|||
Expiresin int64 `json:"expires_in"` |
|||
Ticket string `json:"ticket"` |
|||
} |
|||
@ -0,0 +1,310 @@ |
|||
package wxbizjsonmsgcrypt |
|||
|
|||
import ( |
|||
"bytes" |
|||
"crypto/aes" |
|||
"crypto/cipher" |
|||
"crypto/sha1" |
|||
"encoding/base64" |
|||
"encoding/binary" |
|||
"encoding/json" |
|||
"fmt" |
|||
"math/rand" |
|||
"sort" |
|||
"strings" |
|||
) |
|||
|
|||
const letterBytes = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" |
|||
|
|||
const ( |
|||
ValidateSignatureError int = -40001 |
|||
ParseJsonError int = -40002 |
|||
ComputeSignatureError int = -40003 |
|||
IllegalAesKey int = -40004 |
|||
ValidateCorpidError int = -40005 |
|||
EncryptAESError int = -40006 |
|||
DecryptAESError int = -40007 |
|||
IllegalBuffer int = -40008 |
|||
EncodeBase64Error int = -40009 |
|||
DecodeBase64Error int = -40010 |
|||
GenJsonError int = -40011 |
|||
IllegalProtocolType int = -40012 |
|||
) |
|||
|
|||
type ProtocolType int |
|||
|
|||
const ( |
|||
JsonType ProtocolType = 1 |
|||
) |
|||
|
|||
type CryptError struct { |
|||
ErrCode int |
|||
ErrMsg string |
|||
} |
|||
|
|||
func NewCryptError(err_code int, err_msg string) *CryptError { |
|||
return &CryptError{ErrCode: err_code, ErrMsg: err_msg} |
|||
} |
|||
|
|||
type WXBizJsonMsg4Recv struct { |
|||
Tousername string `json:"tousername"` |
|||
Encrypt string `json:"encrypt"` |
|||
Agentid string `json:"agentid"` |
|||
} |
|||
|
|||
type WXBizJsonMsg4Send struct { |
|||
Encrypt string `json:"encrypt"` |
|||
Signature string `json:"msgsignature"` |
|||
Timestamp string `json:"timestamp"` |
|||
Nonce string `json:"nonce"` |
|||
} |
|||
|
|||
func NewWXBizJsonMsg4Send(encrypt, signature, timestamp, nonce string) *WXBizJsonMsg4Send { |
|||
return &WXBizJsonMsg4Send{Encrypt: encrypt, Signature: signature, Timestamp: timestamp, Nonce: nonce} |
|||
} |
|||
|
|||
type ProtocolProcessor interface { |
|||
parse(src_data []byte) (*WXBizJsonMsg4Recv, *CryptError) |
|||
serialize(msg_send *WXBizJsonMsg4Send) ([]byte, *CryptError) |
|||
} |
|||
|
|||
type WXBizMsgCrypt struct { |
|||
token string |
|||
encoding_aeskey string |
|||
receiver_id string |
|||
protocol_processor ProtocolProcessor |
|||
} |
|||
|
|||
type JsonProcessor struct { |
|||
} |
|||
|
|||
func (self *JsonProcessor) parse(src_data []byte) (*WXBizJsonMsg4Recv, *CryptError) { |
|||
var msg4_recv WXBizJsonMsg4Recv |
|||
err := json.Unmarshal(src_data, &msg4_recv) |
|||
if nil != err { |
|||
fmt.Println("Unmarshal fail", err) |
|||
return nil, NewCryptError(ParseJsonError, "json to msg fail") |
|||
} |
|||
return &msg4_recv, nil |
|||
} |
|||
|
|||
func (self *JsonProcessor) serialize(msg4_send *WXBizJsonMsg4Send) ([]byte, *CryptError) { |
|||
json_msg, err := json.Marshal(msg4_send) |
|||
if nil != err { |
|||
return nil, NewCryptError(GenJsonError, err.Error()) |
|||
} |
|||
|
|||
return json_msg, nil |
|||
} |
|||
|
|||
func NewWXBizMsgCrypt(token, encoding_aeskey, receiver_id string, protocol_type ProtocolType) *WXBizMsgCrypt { |
|||
var protocol_processor ProtocolProcessor |
|||
if protocol_type != JsonType { |
|||
panic("unsupport protocal") |
|||
} else { |
|||
protocol_processor = new(JsonProcessor) |
|||
} |
|||
|
|||
return &WXBizMsgCrypt{token: token, encoding_aeskey: (encoding_aeskey + "="), receiver_id: receiver_id, protocol_processor: protocol_processor} |
|||
} |
|||
|
|||
func (self *WXBizMsgCrypt) randString(n int) string { |
|||
b := make([]byte, n) |
|||
for i := range b { |
|||
b[i] = letterBytes[rand.Int63()%int64(len(letterBytes))] |
|||
} |
|||
return string(b) |
|||
} |
|||
|
|||
func (self *WXBizMsgCrypt) pKCS7Padding(plaintext string, block_size int) []byte { |
|||
padding := block_size - (len(plaintext) % block_size) |
|||
padtext := bytes.Repeat([]byte{byte(padding)}, padding) |
|||
var buffer bytes.Buffer |
|||
buffer.WriteString(plaintext) |
|||
buffer.Write(padtext) |
|||
return buffer.Bytes() |
|||
} |
|||
|
|||
func (self *WXBizMsgCrypt) pKCS7Unpadding(plaintext []byte, block_size int) ([]byte, *CryptError) { |
|||
plaintext_len := len(plaintext) |
|||
if nil == plaintext || plaintext_len == 0 { |
|||
return nil, NewCryptError(DecryptAESError, "pKCS7Unpadding error nil or zero") |
|||
} |
|||
if plaintext_len%block_size != 0 { |
|||
return nil, NewCryptError(DecryptAESError, "pKCS7Unpadding text not a multiple of the block size") |
|||
} |
|||
padding_len := int(plaintext[plaintext_len-1]) |
|||
return plaintext[:plaintext_len-padding_len], nil |
|||
} |
|||
|
|||
func (self *WXBizMsgCrypt) cbcEncrypter(plaintext string) ([]byte, *CryptError) { |
|||
aeskey, err := base64.StdEncoding.DecodeString(self.encoding_aeskey) |
|||
if nil != err { |
|||
return nil, NewCryptError(DecodeBase64Error, err.Error()) |
|||
} |
|||
const block_size = 32 |
|||
pad_msg := self.pKCS7Padding(plaintext, block_size) |
|||
|
|||
block, err := aes.NewCipher(aeskey) |
|||
if err != nil { |
|||
return nil, NewCryptError(EncryptAESError, err.Error()) |
|||
} |
|||
|
|||
ciphertext := make([]byte, len(pad_msg)) |
|||
iv := aeskey[:aes.BlockSize] |
|||
|
|||
mode := cipher.NewCBCEncrypter(block, iv) |
|||
|
|||
mode.CryptBlocks(ciphertext, pad_msg) |
|||
base64_msg := make([]byte, base64.StdEncoding.EncodedLen(len(ciphertext))) |
|||
base64.StdEncoding.Encode(base64_msg, ciphertext) |
|||
|
|||
return base64_msg, nil |
|||
} |
|||
|
|||
func (self *WXBizMsgCrypt) cbcDecrypter(base64_encrypt_msg string) ([]byte, *CryptError) { |
|||
aeskey, err := base64.StdEncoding.DecodeString(self.encoding_aeskey) |
|||
if nil != err { |
|||
return nil, NewCryptError(DecodeBase64Error, err.Error()) |
|||
} |
|||
|
|||
encrypt_msg, err := base64.StdEncoding.DecodeString(base64_encrypt_msg) |
|||
if nil != err { |
|||
return nil, NewCryptError(DecodeBase64Error, err.Error()) |
|||
} |
|||
|
|||
block, err := aes.NewCipher(aeskey) |
|||
if err != nil { |
|||
return nil, NewCryptError(DecryptAESError, err.Error()) |
|||
} |
|||
|
|||
if len(encrypt_msg) < aes.BlockSize { |
|||
return nil, NewCryptError(DecryptAESError, "encrypt_msg size is not valid") |
|||
} |
|||
|
|||
iv := aeskey[:aes.BlockSize] |
|||
|
|||
if len(encrypt_msg)%aes.BlockSize != 0 { |
|||
return nil, NewCryptError(DecryptAESError, "encrypt_msg not a multiple of the block size") |
|||
} |
|||
|
|||
mode := cipher.NewCBCDecrypter(block, iv) |
|||
|
|||
mode.CryptBlocks(encrypt_msg, encrypt_msg) |
|||
|
|||
return encrypt_msg, nil |
|||
} |
|||
|
|||
func (self *WXBizMsgCrypt) calSignature(timestamp, nonce, data string) string { |
|||
sort_arr := []string{self.token, timestamp, nonce, data} |
|||
sort.Strings(sort_arr) |
|||
var buffer bytes.Buffer |
|||
for _, value := range sort_arr { |
|||
buffer.WriteString(value) |
|||
} |
|||
|
|||
sha := sha1.New() |
|||
sha.Write(buffer.Bytes()) |
|||
signature := fmt.Sprintf("%x", sha.Sum(nil)) |
|||
return string(signature) |
|||
} |
|||
|
|||
func (self *WXBizMsgCrypt) ParsePlainText(plaintext []byte) ([]byte, uint32, []byte, []byte, *CryptError) { |
|||
const block_size = 32 |
|||
plaintext, err := self.pKCS7Unpadding(plaintext, block_size) |
|||
if nil != err { |
|||
return nil, 0, nil, nil, err |
|||
} |
|||
|
|||
text_len := uint32(len(plaintext)) |
|||
if text_len < 20 { |
|||
return nil, 0, nil, nil, NewCryptError(IllegalBuffer, "plain is to small 1") |
|||
} |
|||
random := plaintext[:16] |
|||
msg_len := binary.BigEndian.Uint32(plaintext[16:20]) |
|||
if text_len < (20 + msg_len) { |
|||
return nil, 0, nil, nil, NewCryptError(IllegalBuffer, "plain is to small 2") |
|||
} |
|||
|
|||
msg := plaintext[20 : 20+msg_len] |
|||
receiver_id := plaintext[20+msg_len:] |
|||
|
|||
return random, msg_len, msg, receiver_id, nil |
|||
} |
|||
|
|||
func (self *WXBizMsgCrypt) VerifyURL(msg_signature, timestamp, nonce, echostr string) ([]byte, *CryptError) { |
|||
signature := self.calSignature(timestamp, nonce, echostr) |
|||
|
|||
if strings.Compare(signature, msg_signature) != 0 { |
|||
return nil, NewCryptError(ValidateSignatureError, "signature not equal") |
|||
} |
|||
|
|||
plaintext, err := self.cbcDecrypter(echostr) |
|||
if nil != err { |
|||
return nil, err |
|||
} |
|||
|
|||
_, _, msg, receiver_id, err := self.ParsePlainText(plaintext) |
|||
if nil != err { |
|||
return nil, err |
|||
} |
|||
|
|||
if len(self.receiver_id) > 0 && strings.Compare(string(receiver_id), self.receiver_id) != 0 { |
|||
fmt.Println(string(receiver_id), self.receiver_id, len(receiver_id), len(self.receiver_id)) |
|||
return nil, NewCryptError(ValidateCorpidError, "receiver_id is not equil") |
|||
} |
|||
|
|||
return msg, nil |
|||
} |
|||
|
|||
func (self *WXBizMsgCrypt) EncryptMsg(reply_msg, timestamp, nonce string) ([]byte, *CryptError) { |
|||
rand_str := self.randString(16) |
|||
var buffer bytes.Buffer |
|||
buffer.WriteString(rand_str) |
|||
|
|||
msg_len_buf := make([]byte, 4) |
|||
binary.BigEndian.PutUint32(msg_len_buf, uint32(len(reply_msg))) |
|||
buffer.Write(msg_len_buf) |
|||
buffer.WriteString(reply_msg) |
|||
buffer.WriteString(self.receiver_id) |
|||
|
|||
tmp_ciphertext, err := self.cbcEncrypter(buffer.String()) |
|||
if nil != err { |
|||
return nil, err |
|||
} |
|||
ciphertext := string(tmp_ciphertext) |
|||
|
|||
signature := self.calSignature(timestamp, nonce, ciphertext) |
|||
|
|||
msg4_send := NewWXBizJsonMsg4Send(ciphertext, signature, timestamp, nonce) |
|||
return self.protocol_processor.serialize(msg4_send) |
|||
} |
|||
|
|||
func (self *WXBizMsgCrypt) DecryptMsg(msg_signature, timestamp, nonce string, post_data []byte) ([]byte, *CryptError) { |
|||
msg4_recv, crypt_err := self.protocol_processor.parse(post_data) |
|||
if nil != crypt_err { |
|||
return nil, crypt_err |
|||
} |
|||
|
|||
signature := self.calSignature(timestamp, nonce, msg4_recv.Encrypt) |
|||
|
|||
if strings.Compare(signature, msg_signature) != 0 { |
|||
return nil, NewCryptError(ValidateSignatureError, "signature not equal") |
|||
} |
|||
|
|||
plaintext, crypt_err := self.cbcDecrypter(msg4_recv.Encrypt) |
|||
if nil != crypt_err { |
|||
return nil, crypt_err |
|||
} |
|||
|
|||
_, _, msg, receiver_id, crypt_err := self.ParsePlainText(plaintext) |
|||
if nil != crypt_err { |
|||
return nil, crypt_err |
|||
} |
|||
|
|||
if len(self.receiver_id) > 0 && strings.Compare(string(receiver_id), self.receiver_id) != 0 { |
|||
return nil, NewCryptError(ValidateCorpidError, "receiver_id is not equil") |
|||
} |
|||
|
|||
return msg, nil |
|||
} |
|||
@ -0,0 +1,315 @@ |
|||
package wxbizmsgcrypt |
|||
|
|||
import ( |
|||
"bytes" |
|||
"crypto/aes" |
|||
"crypto/cipher" |
|||
"crypto/sha1" |
|||
"encoding/base64" |
|||
"encoding/binary" |
|||
"encoding/xml" |
|||
"fmt" |
|||
"math/rand" |
|||
"sort" |
|||
"strings" |
|||
) |
|||
|
|||
const letterBytes = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" |
|||
|
|||
const ( |
|||
ValidateSignatureError int = -40001 |
|||
ParseXmlError int = -40002 |
|||
ComputeSignatureError int = -40003 |
|||
IllegalAesKey int = -40004 |
|||
ValidateCorpidError int = -40005 |
|||
EncryptAESError int = -40006 |
|||
DecryptAESError int = -40007 |
|||
IllegalBuffer int = -40008 |
|||
EncodeBase64Error int = -40009 |
|||
DecodeBase64Error int = -40010 |
|||
GenXmlError int = -40010 |
|||
ParseJsonError int = -40012 |
|||
GenJsonError int = -40013 |
|||
IllegalProtocolType int = -40014 |
|||
) |
|||
|
|||
type ProtocolType int |
|||
|
|||
const ( |
|||
XmlType ProtocolType = 1 |
|||
) |
|||
|
|||
type CryptError struct { |
|||
ErrCode int |
|||
ErrMsg string |
|||
} |
|||
|
|||
func NewCryptError(err_code int, err_msg string) *CryptError { |
|||
return &CryptError{ErrCode: err_code, ErrMsg: err_msg} |
|||
} |
|||
|
|||
type WXBizMsg4Recv struct { |
|||
Tousername string `xml:"ToUserName"` |
|||
Encrypt string `xml:"Encrypt"` |
|||
Agentid string `xml:"AgentID"` |
|||
} |
|||
|
|||
type CDATA struct { |
|||
Value string `xml:",cdata"` |
|||
} |
|||
|
|||
type WXBizMsg4Send struct { |
|||
XMLName xml.Name `xml:"xml"` |
|||
Encrypt CDATA `xml:"Encrypt"` |
|||
Signature CDATA `xml:"MsgSignature"` |
|||
Timestamp string `xml:"TimeStamp"` |
|||
Nonce CDATA `xml:"Nonce"` |
|||
} |
|||
|
|||
func NewWXBizMsg4Send(encrypt, signature, timestamp, nonce string) *WXBizMsg4Send { |
|||
return &WXBizMsg4Send{Encrypt: CDATA{Value: encrypt}, Signature: CDATA{Value: signature}, Timestamp: timestamp, Nonce: CDATA{Value: nonce}} |
|||
} |
|||
|
|||
type ProtocolProcessor interface { |
|||
parse(src_data []byte) (*WXBizMsg4Recv, *CryptError) |
|||
serialize(msg_send *WXBizMsg4Send) ([]byte, *CryptError) |
|||
} |
|||
|
|||
type WXBizMsgCrypt struct { |
|||
token string |
|||
encoding_aeskey string |
|||
receiver_id string |
|||
protocol_processor ProtocolProcessor |
|||
} |
|||
|
|||
type XmlProcessor struct { |
|||
} |
|||
|
|||
func (self *XmlProcessor) parse(src_data []byte) (*WXBizMsg4Recv, *CryptError) { |
|||
var msg4_recv WXBizMsg4Recv |
|||
err := xml.Unmarshal(src_data, &msg4_recv) |
|||
if nil != err { |
|||
return nil, NewCryptError(ParseXmlError, "xml to msg fail") |
|||
} |
|||
return &msg4_recv, nil |
|||
} |
|||
|
|||
func (self *XmlProcessor) serialize(msg4_send *WXBizMsg4Send) ([]byte, *CryptError) { |
|||
xml_msg, err := xml.Marshal(msg4_send) |
|||
if nil != err { |
|||
return nil, NewCryptError(GenXmlError, err.Error()) |
|||
} |
|||
return xml_msg, nil |
|||
} |
|||
|
|||
func NewWXBizMsgCrypt(token, encoding_aeskey, receiver_id string, protocol_type ProtocolType) *WXBizMsgCrypt { |
|||
var protocol_processor ProtocolProcessor |
|||
if protocol_type != XmlType { |
|||
panic("unsupport protocal") |
|||
} else { |
|||
protocol_processor = new(XmlProcessor) |
|||
} |
|||
|
|||
return &WXBizMsgCrypt{token: token, encoding_aeskey: (encoding_aeskey + "="), receiver_id: receiver_id, protocol_processor: protocol_processor} |
|||
} |
|||
|
|||
func (self *WXBizMsgCrypt) randString(n int) string { |
|||
b := make([]byte, n) |
|||
for i := range b { |
|||
b[i] = letterBytes[rand.Int63()%int64(len(letterBytes))] |
|||
} |
|||
return string(b) |
|||
} |
|||
|
|||
func (self *WXBizMsgCrypt) pKCS7Padding(plaintext string, block_size int) []byte { |
|||
padding := block_size - (len(plaintext) % block_size) |
|||
padtext := bytes.Repeat([]byte{byte(padding)}, padding) |
|||
var buffer bytes.Buffer |
|||
buffer.WriteString(plaintext) |
|||
buffer.Write(padtext) |
|||
return buffer.Bytes() |
|||
} |
|||
|
|||
func (self *WXBizMsgCrypt) pKCS7Unpadding(plaintext []byte, block_size int) ([]byte, *CryptError) { |
|||
plaintext_len := len(plaintext) |
|||
if nil == plaintext || plaintext_len == 0 { |
|||
return nil, NewCryptError(DecryptAESError, "pKCS7Unpadding error nil or zero") |
|||
} |
|||
if plaintext_len%block_size != 0 { |
|||
return nil, NewCryptError(DecryptAESError, "pKCS7Unpadding text not a multiple of the block size") |
|||
} |
|||
padding_len := int(plaintext[plaintext_len-1]) |
|||
return plaintext[:plaintext_len-padding_len], nil |
|||
} |
|||
|
|||
func (self *WXBizMsgCrypt) cbcEncrypter(plaintext string) ([]byte, *CryptError) { |
|||
aeskey, err := base64.StdEncoding.DecodeString(self.encoding_aeskey) |
|||
if nil != err { |
|||
return nil, NewCryptError(DecodeBase64Error, err.Error()) |
|||
} |
|||
const block_size = 32 |
|||
pad_msg := self.pKCS7Padding(plaintext, block_size) |
|||
|
|||
block, err := aes.NewCipher(aeskey) |
|||
if err != nil { |
|||
return nil, NewCryptError(EncryptAESError, err.Error()) |
|||
} |
|||
|
|||
ciphertext := make([]byte, len(pad_msg)) |
|||
iv := aeskey[:aes.BlockSize] |
|||
|
|||
mode := cipher.NewCBCEncrypter(block, iv) |
|||
|
|||
mode.CryptBlocks(ciphertext, pad_msg) |
|||
base64_msg := make([]byte, base64.StdEncoding.EncodedLen(len(ciphertext))) |
|||
base64.StdEncoding.Encode(base64_msg, ciphertext) |
|||
|
|||
return base64_msg, nil |
|||
} |
|||
|
|||
func (self *WXBizMsgCrypt) cbcDecrypter(base64_encrypt_msg string) ([]byte, *CryptError) { |
|||
aeskey, err := base64.StdEncoding.DecodeString(self.encoding_aeskey) |
|||
if nil != err { |
|||
return nil, NewCryptError(DecodeBase64Error, err.Error()) |
|||
} |
|||
|
|||
encrypt_msg, err := base64.StdEncoding.DecodeString(base64_encrypt_msg) |
|||
if nil != err { |
|||
return nil, NewCryptError(DecodeBase64Error, err.Error()) |
|||
} |
|||
|
|||
block, err := aes.NewCipher(aeskey) |
|||
if err != nil { |
|||
return nil, NewCryptError(DecryptAESError, err.Error()) |
|||
} |
|||
|
|||
if len(encrypt_msg) < aes.BlockSize { |
|||
return nil, NewCryptError(DecryptAESError, "encrypt_msg size is not valid") |
|||
} |
|||
|
|||
iv := aeskey[:aes.BlockSize] |
|||
|
|||
if len(encrypt_msg)%aes.BlockSize != 0 { |
|||
return nil, NewCryptError(DecryptAESError, "encrypt_msg not a multiple of the block size") |
|||
} |
|||
|
|||
mode := cipher.NewCBCDecrypter(block, iv) |
|||
|
|||
mode.CryptBlocks(encrypt_msg, encrypt_msg) |
|||
|
|||
return encrypt_msg, nil |
|||
} |
|||
|
|||
func (self *WXBizMsgCrypt) calSignature(timestamp, nonce, data string) string { |
|||
sort_arr := []string{self.token, timestamp, nonce, data} |
|||
sort.Strings(sort_arr) |
|||
var buffer bytes.Buffer |
|||
for _, value := range sort_arr { |
|||
buffer.WriteString(value) |
|||
} |
|||
|
|||
sha := sha1.New() |
|||
sha.Write(buffer.Bytes()) |
|||
signature := fmt.Sprintf("%x", sha.Sum(nil)) |
|||
return string(signature) |
|||
} |
|||
|
|||
func (self *WXBizMsgCrypt) ParsePlainText(plaintext []byte) ([]byte, uint32, []byte, []byte, *CryptError) { |
|||
const block_size = 32 |
|||
plaintext, err := self.pKCS7Unpadding(plaintext, block_size) |
|||
if nil != err { |
|||
return nil, 0, nil, nil, err |
|||
} |
|||
|
|||
text_len := uint32(len(plaintext)) |
|||
if text_len < 20 { |
|||
return nil, 0, nil, nil, NewCryptError(IllegalBuffer, "plain is to small 1") |
|||
} |
|||
random := plaintext[:16] |
|||
msg_len := binary.BigEndian.Uint32(plaintext[16:20]) |
|||
if text_len < (20 + msg_len) { |
|||
return nil, 0, nil, nil, NewCryptError(IllegalBuffer, "plain is to small 2") |
|||
} |
|||
|
|||
msg := plaintext[20 : 20+msg_len] |
|||
receiver_id := plaintext[20+msg_len:] |
|||
|
|||
return random, msg_len, msg, receiver_id, nil |
|||
} |
|||
|
|||
func (self *WXBizMsgCrypt) VerifyURL(msg_signature, timestamp, nonce, echostr string) ([]byte, *CryptError) { |
|||
signature := self.calSignature(timestamp, nonce, echostr) |
|||
|
|||
if strings.Compare(signature, msg_signature) != 0 { |
|||
return nil, NewCryptError(ValidateSignatureError, "signature not equal") |
|||
} |
|||
|
|||
plaintext, err := self.cbcDecrypter(echostr) |
|||
if nil != err { |
|||
return nil, err |
|||
} |
|||
|
|||
_, _, msg, receiver_id, err := self.ParsePlainText(plaintext) |
|||
if nil != err { |
|||
return nil, err |
|||
} |
|||
|
|||
if len(self.receiver_id) > 0 && strings.Compare(string(receiver_id), self.receiver_id) != 0 { |
|||
fmt.Println(string(receiver_id), self.receiver_id, len(receiver_id), len(self.receiver_id)) |
|||
return nil, NewCryptError(ValidateCorpidError, "receiver_id is not equil") |
|||
} |
|||
|
|||
return msg, nil |
|||
} |
|||
|
|||
func (self *WXBizMsgCrypt) EncryptMsg(reply_msg, timestamp, nonce string) ([]byte, *CryptError) { |
|||
rand_str := self.randString(16) |
|||
var buffer bytes.Buffer |
|||
buffer.WriteString(rand_str) |
|||
|
|||
msg_len_buf := make([]byte, 4) |
|||
binary.BigEndian.PutUint32(msg_len_buf, uint32(len(reply_msg))) |
|||
buffer.Write(msg_len_buf) |
|||
buffer.WriteString(reply_msg) |
|||
buffer.WriteString(self.receiver_id) |
|||
|
|||
tmp_ciphertext, err := self.cbcEncrypter(buffer.String()) |
|||
if nil != err { |
|||
return nil, err |
|||
} |
|||
ciphertext := string(tmp_ciphertext) |
|||
|
|||
signature := self.calSignature(timestamp, nonce, ciphertext) |
|||
|
|||
msg4_send := NewWXBizMsg4Send(ciphertext, signature, timestamp, nonce) |
|||
return self.protocol_processor.serialize(msg4_send) |
|||
} |
|||
|
|||
func (self *WXBizMsgCrypt) DecryptMsg(msg_signature, timestamp, nonce string, post_data []byte) ([]byte, *CryptError) { |
|||
msg4_recv, crypt_err := self.protocol_processor.parse(post_data) |
|||
if nil != crypt_err { |
|||
return nil, crypt_err |
|||
} |
|||
|
|||
signature := self.calSignature(timestamp, nonce, msg4_recv.Encrypt) |
|||
|
|||
if strings.Compare(signature, msg_signature) != 0 { |
|||
return nil, NewCryptError(ValidateSignatureError, "signature not equal") |
|||
} |
|||
|
|||
plaintext, crypt_err := self.cbcDecrypter(msg4_recv.Encrypt) |
|||
if nil != crypt_err { |
|||
return nil, crypt_err |
|||
} |
|||
|
|||
_, _, msg, receiver_id, crypt_err := self.ParsePlainText(plaintext) |
|||
if nil != crypt_err { |
|||
return nil, crypt_err |
|||
} |
|||
|
|||
if len(self.receiver_id) > 0 && strings.Compare(string(receiver_id), self.receiver_id) != 0 { |
|||
return nil, NewCryptError(ValidateCorpidError, "receiver_id is not equil") |
|||
} |
|||
|
|||
return msg, nil |
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
package wechatcallback |
|||
|
|||
//企业微信回调记录
|
|||
type CallbackLog struct { |
|||
Id int64 `json:"id" gorm:"column:id;type:bigint(20);;primaryKey;unique;not null;autoIncrement;index"` |
|||
MsgSignature string `json:"msgSignature" gorm:"column:msg_signature;type:varchar(255);not null;comment:组织名称"` |
|||
TimeStamp int64 `json:"timestamp" gorm:"column:timestamp;type:bigint(20) unsigned;default:0;not null;comment:编辑时间"` |
|||
Nonce string `json:"nonce" gorm:"column:nonce;type:varchar(255);not null;comment:组织名称"` |
|||
Echostr string `json:"echostr" gorm:"column:echostr;type:varchar(255);not null;comment:组织名称"` |
|||
Xmlstr string `json:"xmlstr" gorm:"column:xmlstr;type:text;comment:组织名称"` |
|||
Jsonstr string `json:"jsonstr" gorm:"column:jsonstr;type:text;comment:组织名称"` |
|||
AddTime int64 `json:"addtime" gorm:"column:addtime;type:bigint(20) unsigned;default:0;not null;comment:编辑时间"` |
|||
Reqdata string `json:"reqdata" gorm:"column:reqdata;type:text;comment:组织名称"` |
|||
} |
|||
|
|||
func (CallbackLog *CallbackLog) TableName() string { |
|||
return "callback_log" |
|||
} |
|||
Loading…
Reference in new issue