// Copyright © 2023 OpenIM open source community. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package util import ( "crypto/aes" "crypto/cipher" "crypto/rand" "crypto/sha256" "encoding/base64" "fmt" "io" "time" "github.com/openimsdk/tools/errs" ) const ( // AES密钥字符串 aesKeyString = "F9cLjK3FgkvcR6vaWZKEj3DYJfwrIHMULLwpmDAj553mpmlvMYoLNIqajLnhIsWq" ) // DecryptVerifyCode 解密验证码 // encryptedText: Base64编码的加密字符串 // 返回: 解密后的明文(格式:验证码-时间戳) func DecryptVerifyCode(encryptedText string) (string, error) { // 解码Base64 encrypted, err := base64.StdEncoding.DecodeString(encryptedText) if err != nil { return "", errs.WrapMsg(err, "base64解码失败") } // 使用SHA-256哈希密钥字符串 keyBytes := sha256.Sum256([]byte(aesKeyString)) key := keyBytes[:] // 创建AES cipher block, err := aes.NewCipher(key) if err != nil { return "", errs.WrapMsg(err, "创建AES cipher失败") } // 检查密文长度 if len(encrypted) < aes.BlockSize { return "", errs.New("密文长度不足") } // 使用CBC模式,固定IV(全零) iv := make([]byte, aes.BlockSize) // 全零IV mode := cipher.NewCBCDecrypter(block, iv) // 解密 decrypted := make([]byte, len(encrypted)) mode.CryptBlocks(decrypted, encrypted) // 去除PKCS7填充 decrypted = unpadPKCS7(decrypted) return string(decrypted), nil } // unpadPKCS7 去除PKCS7填充 func unpadPKCS7(data []byte) []byte { if len(data) == 0 { return data } padLen := int(data[len(data)-1]) if padLen > len(data) || padLen == 0 { return data } // 验证填充 for i := len(data) - padLen; i < len(data); i++ { if data[i] != byte(padLen) { return data } } return data[:len(data)-padLen] } // ParseVerifyCodeWithTimestamp 解析验证码和时间戳 // format: "验证码-时间戳" // 返回: 验证码字符串和时间戳(毫秒) func ParseVerifyCodeWithTimestamp(decryptedText string) (string, int64, error) { var code string var timestamp int64 // 查找最后一个 "-" 分隔符 lastDashIndex := -1 for i := len(decryptedText) - 1; i >= 0; i-- { if decryptedText[i] == '-' { lastDashIndex = i break } } if lastDashIndex == -1 { return "", 0, errs.New("格式错误:未找到时间戳分隔符") } code = decryptedText[:lastDashIndex] timestampStr := decryptedText[lastDashIndex+1:] // 解析时间戳 _, err := fmt.Sscanf(timestampStr, "%d", ×tamp) if err != nil { return "", 0, errs.WrapMsg(err, "解析时间戳失败") } return code, timestamp, nil } // ValidateTimestamp 验证时间戳是否在有效期内 // timestamp: 毫秒级时间戳 // maxAgeSeconds: 最大允许的秒数(允许的时间差,默认30秒) // 返回: 是否有效 func ValidateTimestamp(timestamp int64, maxAgeSeconds int64) bool { if timestamp <= 0 { return false } currentTime := getCurrentTimestamp() diff := currentTime - timestamp // 转换为毫秒 maxAgeMillis := maxAgeSeconds * 1000 // 时间戳不能是未来的(允许5秒的时钟偏差) if diff < -5000 { return false } // 时间差不能超过maxAgeSeconds秒 if diff > maxAgeMillis { return false } return true } // getCurrentTimestamp 获取当前时间戳(毫秒) func getCurrentTimestamp() int64 { return time.Now().UnixMilli() } // padPKCS7 添加PKCS7填充 func padPKCS7(data []byte, blockSize int) []byte { padLen := blockSize - len(data)%blockSize pad := make([]byte, padLen) for i := range pad { pad[i] = byte(padLen) } return append(data, pad...) } // EncryptPhone 加密手机号 // phoneNumber: 手机号明文 // 返回: Base64编码的加密字符串 func EncryptPhone(phoneNumber string) (string, error) { if phoneNumber == "" { return "", errs.New("手机号不能为空") } // 使用SHA-256哈希密钥字符串 keyBytes := sha256.Sum256([]byte(aesKeyString)) key := keyBytes[:] // 创建AES cipher block, err := aes.NewCipher(key) if err != nil { return "", errs.WrapMsg(err, "创建AES cipher失败") } // 使用CBC模式,固定IV(全零) iv := make([]byte, aes.BlockSize) // 全零IV // 添加PKCS7填充 plaintext := padPKCS7([]byte(phoneNumber), aes.BlockSize) // 加密 mode := cipher.NewCBCEncrypter(block, iv) encrypted := make([]byte, len(plaintext)) mode.CryptBlocks(encrypted, plaintext) // 编码为Base64 return base64.StdEncoding.EncodeToString(encrypted), nil } // DecryptPhone 解密手机号 // encryptedText: Base64编码的加密字符串 // 返回: 解密后的手机号明文 func DecryptPhone(encryptedText string) (string, error) { // 复用DecryptVerifyCode的解密逻辑(手机号没有时间戳格式,直接解密) return DecryptVerifyCode(encryptedText) } // EncryptRealNameAuthData 加密实名认证数据(使用 AES-GCM 模式) // data: JSON字符串,例如: {"cardNo":"330329199001021122","realName":"李四"} // key: AES密钥字符串(与服务端相同的默认密钥) // 返回: Base64编码的加密字符串 func EncryptRealNameAuthData(data string, key string) (string, error) { if data == "" { return "", errs.New("数据不能为空") } if key == "" { return "", errs.New("密钥不能为空") } // 使用 SHA256 哈希密钥字符串(与服务端保持一致) // 注意:直接对密钥字符串进行哈希,而不是先转换为字节 hash := sha256.Sum256([]byte(key)) aesKey := hash[:] // 创建AES cipher block, err := aes.NewCipher(aesKey) if err != nil { return "", errs.WrapMsg(err, "创建AES cipher失败") } // 使用 GCM 模式 gcm, err := cipher.NewGCM(block) if err != nil { return "", errs.WrapMsg(err, "创建 GCM 失败") } // 生成随机 nonce nonce := make([]byte, gcm.NonceSize()) if _, err := io.ReadFull(rand.Reader, nonce); err != nil { return "", errs.WrapMsg(err, "生成 nonce 失败") } // 加密 ciphertext := gcm.Seal(nonce, nonce, []byte(data), nil) // 编码为Base64 return base64.StdEncoding.EncodeToString(ciphertext), nil }