forked from golang/hotime
333 lines
8.5 KiB
Go
333 lines
8.5 KiB
Go
|
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)]
|
|||
|
}
|