package crypto import ( "bytes" "crypto/aes" "crypto/cipher" "crypto/rand" "crypto/sha1" "encoding/base64" "encoding/binary" "errors" "fmt" r "math/rand" "sort" "strings" "time" ) type DingTalkCrypto struct { Token string EncodingAESKey string SuiteKey string BKey []byte Block cipher.Block } func NewDingTalkCrypto(token, encodingAESKey, suiteKey string) *DingTalkCrypto { fmt.Println(len(encodingAESKey)) if len(encodingAESKey) != int(43) { panic("不合法的EncodingAESKey") } bkey, err := base64.StdEncoding.DecodeString(encodingAESKey + "=") if err != nil { panic(err.Error()) } block, err := aes.NewCipher(bkey) if err != nil { panic(err.Error()) } c := &DingTalkCrypto{ Token: token, EncodingAESKey: encodingAESKey, SuiteKey: suiteKey, BKey: bkey, Block: block, } return c } func (c *DingTalkCrypto) GetDecryptMsg(signature, timestamp, nonce, secretMsg string) (string, error) { if !c.VerificationSignature(c.Token, timestamp, nonce, secretMsg, signature) { return "", errors.New("ERROR: 签名不匹配") } decode, err := base64.StdEncoding.DecodeString(secretMsg) if err != nil { return "", err } if len(decode) < aes.BlockSize { return "", errors.New("ERROR: 密文太短") } blockMode := cipher.NewCBCDecrypter(c.Block, c.BKey[:c.Block.BlockSize()]) plantText := make([]byte, len(decode)) blockMode.CryptBlocks(plantText, decode) plantText = pkCS7UnPadding(plantText) size := binary.BigEndian.Uint32(plantText[16:20]) plantText = plantText[20:] corpID := plantText[size:] if string(corpID) != c.SuiteKey { return "", errors.New("ERROR: CorpID匹配不正确") } return string(plantText[:size]), nil } func (c *DingTalkCrypto) GetEncryptMsg(msg string) (map[string]string, error) { var timestamp = time.Now().UnixMilli() var nonce = randomString(12) str, sign, err := c.GetEncryptMsgDetail(msg, fmt.Sprint(timestamp), nonce) return map[string]string{"nonce": nonce, "timeStamp": fmt.Sprint(timestamp), "encrypt": str, "msg_signature": sign}, err } func (c *DingTalkCrypto) GetEncryptMsgDetail(msg, timestamp, nonce string) (string, string, error) { size := make([]byte, 4) binary.BigEndian.PutUint32(size, uint32(len(msg))) msg = randomString(16) + string(size) + msg + c.SuiteKey plantText := pkCS7Padding([]byte(msg), c.Block.BlockSize()) if len(plantText)%aes.BlockSize != 0 { return "", "", errors.New("ERROR: 消息体size不为16的倍数") } blockMode := cipher.NewCBCEncrypter(c.Block, c.BKey[:c.Block.BlockSize()]) chipherText := make([]byte, len(plantText)) blockMode.CryptBlocks(chipherText, plantText) outMsg := base64.StdEncoding.EncodeToString(chipherText) signature := c.CreateSignature(c.Token, timestamp, nonce, string(outMsg)) return string(outMsg), signature, nil } func sha1Sign(s string) string { // The pattern for generating a hash is `sha1.New()`, // `sha1.Write(bytes)`, then `sha1.Sum([]byte{})`. // Here we start with a new hash. h := sha1.New() // `Write` expects bytes. If you have a string `s`, // use `[]byte(s)` to coerce it to bytes. h.Write([]byte(s)) // This gets the finalized hash result as a byte // slice. The argument to `Sum` can be used to append // to an existing byte slice: it usually isn't needed. bs := h.Sum(nil) // SHA1 values are often printed in hex, for example // in git commits. Use the `%x` format verb to convert // a hash results to a hex string. return fmt.Sprintf("%x", bs) } // CreateSignature 数据签名 func (c *DingTalkCrypto) CreateSignature(token, timestamp, nonce, msg string) string { params := make([]string, 0) params = append(params, token) params = append(params, timestamp) params = append(params, nonce) params = append(params, msg) sort.Strings(params) return sha1Sign(strings.Join(params, "")) } // VerificationSignature 验证数据签名 func (c *DingTalkCrypto) VerificationSignature(token, timestamp, nonce, msg, sigture string) bool { return c.CreateSignature(token, timestamp, nonce, msg) == sigture } // 解密补位 func pkCS7UnPadding(plantText []byte) []byte { length := len(plantText) unpadding := int(plantText[length-1]) return plantText[:(length - unpadding)] } // 加密补位 func pkCS7Padding(ciphertext []byte, blockSize int) []byte { padding := blockSize - len(ciphertext)%blockSize padtext := bytes.Repeat([]byte{byte(padding)}, padding) return append(ciphertext, padtext...) } // 随机字符串 func randomString(n int, alphabets ...byte) string { const alphanum = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" var bytes = make([]byte, n) var randby bool if num, err := rand.Read(bytes); num != n || err != nil { r.Seed(time.Now().UnixNano()) randby = true } for i, b := range bytes { if len(alphabets) == 0 { if randby { bytes[i] = alphanum[r.Intn(len(alphanum))] } else { bytes[i] = alphanum[b%byte(len(alphanum))] } } else { if randby { bytes[i] = alphabets[r.Intn(len(alphabets))] } else { bytes[i] = alphabets[b%byte(len(alphabets))] } } } return string(bytes) }