| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332 |
- 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)]
- }
|