package util import ( "bytes" "crypto/aes" "crypto/cipher" "crypto/hmac" "crypto/md5" "crypto/sha256" "encoding/base64" "encoding/hex" "errors" "fmt" "hash" "strings" ) // 微信签名算法方式 const ( SignTypeMD5 = `MD5` SignTypeHMACSHA256 = `HMAC-SHA256` ) // EncryptMsg 加密消息 func EncryptMsg(random, rawXMLMsg []byte, appID, aesKey string) (encrtptMsg []byte, err error) { defer func() { if e := recover(); e != nil { err = fmt.Errorf("panic error: err=%v", e) return } }() var key []byte key, err = aesKeyDecode(aesKey) if err != nil { panic(err) } ciphertext := AESEncryptMsg(random, rawXMLMsg, appID, key) encrtptMsg = []byte(base64.StdEncoding.EncodeToString(ciphertext)) return } // AESEncryptMsg ciphertext = AES_Encrypt[random(16B) + msg_len(4B) + rawXMLMsg + appId] // 参考:github.com/chanxuehong/wechat.v2 func AESEncryptMsg(random, rawXMLMsg []byte, appID string, aesKey []byte) (ciphertext []byte) { const ( BlockSize = 32 // PKCS#7 BlockMask = BlockSize - 1 // BLOCK_SIZE 为 2^n 时, 可以用 mask 获取针对 BLOCK_SIZE 的余数 ) appIDOffset := 20 + len(rawXMLMsg) contentLen := appIDOffset + len(appID) amountToPad := BlockSize - contentLen&BlockMask plaintextLen := contentLen + amountToPad plaintext := make([]byte, plaintextLen) // 拼接 copy(plaintext[:16], random) encodeNetworkByteOrder(plaintext[16:20], uint32(len(rawXMLMsg))) copy(plaintext[20:], rawXMLMsg) copy(plaintext[appIDOffset:], appID) // PKCS#7 补位 for i := contentLen; i < plaintextLen; i++ { plaintext[i] = byte(amountToPad) } // 加密 block, err := aes.NewCipher(aesKey) if err != nil { panic(err) } mode := cipher.NewCBCEncrypter(block, aesKey[:16]) mode.CryptBlocks(plaintext, plaintext) ciphertext = plaintext return } // DecryptMsg 消息解密 func DecryptMsg(appID, encryptedMsg, aesKey string) (random, rawMsgXMLBytes []byte, err error) { defer func() { if e := recover(); e != nil { err = fmt.Errorf("panic error: err=%v", e) return } }() var encryptedMsgBytes, key, getAppIDBytes []byte encryptedMsgBytes, err = base64.StdEncoding.DecodeString(encryptedMsg) if err != nil { return } key, err = aesKeyDecode(aesKey) if err != nil { panic(err) } random, rawMsgXMLBytes, getAppIDBytes, err = AESDecryptMsg(encryptedMsgBytes, key) if err != nil { err = fmt.Errorf("消息解密失败,%v", err) return } if appID != string(getAppIDBytes) { err = fmt.Errorf("消息解密校验APPID失败") return } return } func aesKeyDecode(encodedAESKey string) (key []byte, err error) { if len(encodedAESKey) != 43 { err = fmt.Errorf("the length of encodedAESKey must be equal to 43") return } key, err = base64.StdEncoding.DecodeString(encodedAESKey + "=") if err != nil { return } if len(key) != 32 { err = fmt.Errorf("encodingAESKey invalid") return } return } // AESDecryptMsg ciphertext = AES_Encrypt[random(16B) + msg_len(4B) + rawXMLMsg + appId] // 参考:github.com/chanxuehong/wechat.v2 func AESDecryptMsg(ciphertext []byte, aesKey []byte) (random, rawXMLMsg, appID []byte, err error) { const ( BlockSize = 32 // PKCS#7 BlockMask = BlockSize - 1 // BLOCK_SIZE 为 2^n 时, 可以用 mask 获取针对 BLOCK_SIZE 的余数 ) if len(ciphertext) < BlockSize { err = fmt.Errorf("the length of ciphertext too short: %d", len(ciphertext)) return } if len(ciphertext)&BlockMask != 0 { err = fmt.Errorf("ciphertext is not a multiple of the block size, the length is %d", len(ciphertext)) return } plaintext := make([]byte, len(ciphertext)) // len(plaintext) >= BLOCK_SIZE // 解密 block, err := aes.NewCipher(aesKey) if err != nil { panic(err) } mode := cipher.NewCBCDecrypter(block, aesKey[:16]) mode.CryptBlocks(plaintext, ciphertext) // PKCS#7 去除补位 amountToPad := int(plaintext[len(plaintext)-1]) if amountToPad < 1 || amountToPad > BlockSize { err = fmt.Errorf("the amount to pad is incorrect: %d", amountToPad) return } plaintext = plaintext[:len(plaintext)-amountToPad] // 反拼接 // len(plaintext) == 16+4+len(rawXMLMsg)+len(appId) if len(plaintext) <= 20 { err = fmt.Errorf("plaintext too short, the length is %d", len(plaintext)) return } rawXMLMsgLen := int(decodeNetworkByteOrder(plaintext[16:20])) if rawXMLMsgLen < 0 { err = fmt.Errorf("incorrect msg length: %d", rawXMLMsgLen) return } appIDOffset := 20 + rawXMLMsgLen if len(plaintext) <= appIDOffset { err = fmt.Errorf("msg length too large: %d", rawXMLMsgLen) return } random = plaintext[:16:20] rawXMLMsg = plaintext[20:appIDOffset:appIDOffset] appID = plaintext[appIDOffset:] return } // 把整数 n 格式化成 4 字节的网络字节序 func encodeNetworkByteOrder(orderBytes []byte, n uint32) { orderBytes[0] = byte(n >> 24) orderBytes[1] = byte(n >> 16) orderBytes[2] = byte(n >> 8) orderBytes[3] = byte(n) } // 从 4 字节的网络字节序里解析出整数 func decodeNetworkByteOrder(orderBytes []byte) (n uint32) { return uint32(orderBytes[0])<<24 | uint32(orderBytes[1])<<16 | uint32(orderBytes[2])<<8 | uint32(orderBytes[3]) } // CalculateSign 计算签名 func CalculateSign(content, signType, key string) (string, error) { var h hash.Hash if signType == SignTypeHMACSHA256 { h = hmac.New(sha256.New, []byte(key)) } else { h = md5.New() } if _, err := h.Write([]byte(content)); err != nil { return ``, err } return strings.ToUpper(hex.EncodeToString(h.Sum(nil))), nil } // ParamSign 计算所传参数的签名 func ParamSign(p map[string]string, key string) (string, error) { bizKey := "&key=" + key str := OrderParam(p, bizKey) var signType string switch p["sign_type"] { case SignTypeMD5, SignTypeHMACSHA256: signType = p["sign_type"] case ``: signType = SignTypeMD5 default: return ``, errors.New(`invalid sign_type`) } return CalculateSign(str, signType, key) } // ECB provides confidentiality by assigning a fixed ciphertext block to each plaintext block. // See NIST SP 800-38A, pp 08-09 // reference: https://codereview.appspot.com/7860047/patch/23001/24001 type ecb struct { b cipher.Block blockSize int } func newECB(b cipher.Block) *ecb { return &ecb{ b: b, blockSize: b.BlockSize(), } } // ECBEncryptor - type ECBEncryptor ecb // NewECBEncryptor returns a BlockMode which encrypts in electronic code book mode, using the given Block. func NewECBEncryptor(b cipher.Block) cipher.BlockMode { return (*ECBEncryptor)(newECB(b)) } // BlockSize implement BlockMode.BlockSize func (x *ECBEncryptor) BlockSize() int { return x.blockSize } // CryptBlocks implement BlockMode.CryptBlocks func (x *ECBEncryptor) CryptBlocks(dst, src []byte) { if len(src)%x.blockSize != 0 { panic("crypto/cipher: input not full blocks") } if len(dst) < len(src) { panic("crypto/cipher: output smaller than input") } for len(src) > 0 { x.b.Encrypt(dst, src[:x.blockSize]) src = src[x.blockSize:] dst = dst[x.blockSize:] } } // ECBDecryptor - type ECBDecryptor ecb // NewECBDecryptor returns a BlockMode which decrypts in electronic code book mode, using the given Block. func NewECBDecryptor(b cipher.Block) cipher.BlockMode { return (*ECBDecryptor)(newECB(b)) } // BlockSize implement BlockMode.BlockSize func (x *ECBDecryptor) BlockSize() int { return x.blockSize } // CryptBlocks implement BlockMode.CryptBlocks func (x *ECBDecryptor) CryptBlocks(dst, src []byte) { if len(src)%x.blockSize != 0 { panic("crypto/cipher: input not full blocks") } if len(dst) < len(src) { panic("crypto/cipher: output smaller than input") } for len(src) > 0 { x.b.Decrypt(dst, src[:x.blockSize]) src = src[x.blockSize:] dst = dst[x.blockSize:] } } // AesECBDecrypt will decrypt data with PKCS5Padding func AesECBDecrypt(ciphertext []byte, aesKey []byte) ([]byte, error) { if len(ciphertext) < aes.BlockSize { return nil, errors.New("ciphertext too short") } // ECB mode always works in whole blocks. if len(ciphertext)%aes.BlockSize != 0 { return nil, errors.New("ciphertext is not a multiple of the block size") } block, err := aes.NewCipher(aesKey) if err != nil { return nil, err } NewECBDecryptor(block).CryptBlocks(ciphertext, ciphertext) return PKCS5UnPadding(ciphertext), nil } // PKCS5Padding - func PKCS5Padding(ciphertext []byte, blockSize int) []byte { padding := blockSize - len(ciphertext)%blockSize padText := bytes.Repeat([]byte{byte(padding)}, padding) return append(ciphertext, padText...) } // PKCS5UnPadding - func PKCS5UnPadding(origData []byte) []byte { length := len(origData) unPadding := int(origData[length-1]) return origData[:(length - unPadding)] }