This commit is contained in:
2024-09-14 10:48:09 +08:00
commit a5fa833937
192 changed files with 87641 additions and 0 deletions

89
utils/antifraud.go Normal file
View File

@@ -0,0 +1,89 @@
package utils
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/hex"
"math"
)
// 对明文进行填充
func Padding(plainText []byte, blockSize int) []byte {
//计算要填充的长度
n := blockSize - len(plainText)%blockSize
//对原来的明文填充n个n
temp := bytes.Repeat([]byte{byte(n)}, n)
plainText = append(plainText, temp...)
return plainText
}
// 对密文删除填充
func UnPadding(cipherText []byte) []byte {
//取出密文最后一个字节end
end := cipherText[len(cipherText)-1]
//删除填充
cipherText = cipherText[:len(cipherText)-int(end)]
return cipherText
}
// AEC加密CBC模式
func AES_CBC_Encrypt(plainText []byte, key []byte) []byte {
//指定加密算法返回一个AES算法的Block接口对象
block, err := aes.NewCipher(key)
if err != nil {
panic(err)
}
//进行填充
plainText = Padding(plainText, block.BlockSize())
//指定初始向量vi,长度和block的块尺寸一致
iv := []byte("0000000000000000")
//指定分组模式返回一个BlockMode接口对象
blockMode := cipher.NewCBCEncrypter(block, iv)
//加密连续数据库
cipherText := make([]byte, len(plainText))
blockMode.CryptBlocks(cipherText, plainText)
//返回base64密文
return cipherText
}
// AEC解密CBC模式
func AES_CBC_Decrypt(cipherText []byte, key []byte) []byte {
//指定解密算法返回一个AES算法的Block接口对象
block, err := aes.NewCipher(key)
if err != nil {
panic(err)
}
//指定初始化向量IV,和加密的一致
iv := []byte("0000000000000000")
//指定分组模式返回一个BlockMode接口对象
blockMode := cipher.NewCBCDecrypter(block, iv)
//解密
plainText := make([]byte, len(cipherText))
blockMode.CryptBlocks(plainText, cipherText)
//删除填充
plainText = UnPadding(plainText)
return plainText
}
// GenerateRandomString 生成一个32位的随机字符串订单号
func GenerateRandomString() (string, error) {
// 创建一个16字节的数组
bytes := make([]byte, 16)
// 读取随机字节到数组中
if _, err := rand.Read(bytes); err != nil {
return "", err
}
// 将字节数组编码为16进制字符串
return hex.EncodeToString(bytes), nil
}
// 定义一个函数,将分数从原始范围转换到 0-100 的范围
func ConvertScore(originalScore, oldMin, oldMax float64) int {
newMin := 0.0
newMax := 100.0
Score := ((originalScore - oldMin) * (newMax - newMin) / (oldMax - oldMin)) + newMin
newScore := int(math.Round(100 - Score))
return newScore
}

53
utils/car.go Normal file
View File

@@ -0,0 +1,53 @@
package utils
import (
"crypto/aes"
"encoding/base64"
"fmt"
"regexp"
)
// 解密函数
func Decrypt(ciphertext string, key []byte) (string, error) {
// Base64 解码
cipherTextDecoded, err := base64.StdEncoding.DecodeString(ciphertext)
if err != nil {
return "", err
}
// 创建 AES 密码块
block, err := aes.NewCipher(key)
if err != nil {
return "", err
}
// 检查密文长度是否为块大小的倍数
if len(cipherTextDecoded)%aes.BlockSize != 0 {
return "", fmt.Errorf("密文长度不是块大小的倍数")
}
// 解密密文
decrypted := make([]byte, len(cipherTextDecoded))
for bs := 0; bs < len(cipherTextDecoded); bs += aes.BlockSize {
block.Decrypt(decrypted[bs:], cipherTextDecoded[bs:bs+aes.BlockSize])
}
// 移除填充
decrypted = pkcs7Unpad(decrypted)
// 移除不需要的字符
re := regexp.MustCompile("[\\x00-\\x08\\x0b-\\x0c\\x0e-\\x1f\n\r\t]")
cleanedString := re.ReplaceAllString(string(decrypted), "")
return cleanedString, nil
}
// 移除PKCS7填充
func pkcs7Unpad(src []byte) []byte {
length := len(src)
unpadding := int(src[length-1])
if unpadding > length {
return src
}
return src[:(length - unpadding)]
}

172
utils/common.go Normal file
View File

@@ -0,0 +1,172 @@
package utils
import (
"context"
"encoding/base64"
"encoding/json"
uuid2 "github.com/google/uuid"
"image/png"
"net/url"
"os"
"qnc-server/db"
"strings"
"time"
"unicode"
"unicode/utf8"
)
func IsJSON(s string) bool {
var js interface{}
return json.Unmarshal([]byte(s), &js) == nil
}
// 校验统一社会信用代码的函数
func ValidateUnifiedSocialCreditCode(code string) bool {
if len(code) != 18 {
return false
}
// 定义字符与对应的数值映射
charValues := map[rune]int{
'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9,
'A': 10, 'B': 11, 'C': 12, 'D': 13, 'E': 14, 'F': 15, 'G': 16, 'H': 17, 'J': 18, 'K': 19,
'L': 20, 'M': 21, 'N': 22, 'P': 23, 'Q': 24, 'R': 25, 'T': 26, 'U': 27, 'W': 28, 'X': 29, 'Y': 30,
}
// 定义权重因子
weights := []int{1, 3, 9, 27, 19, 26, 16, 17, 20, 29, 25, 13, 8, 24, 10, 30, 28}
// 计算前 17 位与权重因子的乘积之和
sum := 0
for i, char := range code[:17] {
if value, ok := charValues[char]; ok {
sum += value * weights[i]
} else {
return false // 包含非法字符
}
}
// 计算校验码
mod := sum % 31
checkChar := '0'
for k, v := range charValues {
if v == (31-mod)%31 {
checkChar = k
break
}
}
return unicode.ToUpper(rune(code[17])) == checkChar
}
func EncryptName(name string) string {
// 获取名字的字符长度
nameLength := utf8.RuneCountInString(name)
// 如果名字只有一个字,返回原始名字
if nameLength <= 1 {
return name
}
// 将名字转换为rune切片
runes := []rune(name)
// 如果名字是两个字,保留第一个字,第二个字加密
if nameLength == 2 {
return string(runes[0]) + "*"
}
// 如果名字有三个或更多字,保留第一个和最后一个字,中间的加密
firstChar := string(runes[0])
lastChar := string(runes[nameLength-1])
// 中间的字用*代替
middleChars := strings.Repeat("*", nameLength-2)
return firstChar + middleChars + lastChar
}
func EncryptIDCard(idCard string) string {
if len(idCard) < 8 {
return idCard // 如果身份证号码长度不足8位则不进行加密
}
midStart := (len(idCard) - 8) / 2
midEnd := midStart + 8
return idCard[:midStart] + "********" + idCard[midEnd:]
}
// 解析时间字符串为 time.Time
func ParseTime(t string) (time.Time, error) {
return time.Parse("15:04", t)
}
// 判断当前时间是否在指定时间范围内
func IsInTimeRange(start, end time.Time) bool {
now := time.Now()
start = time.Date(now.Year(), now.Month(), now.Day(), start.Hour(), start.Minute(), 0, 0, now.Location())
end = time.Date(now.Year(), now.Month(), now.Day(), end.Hour(), end.Minute(), 0, 0, now.Location())
if end.Before(start) {
end = end.Add(24 * time.Hour) // 处理跨午夜的情况
}
return now.After(start) && now.Before(end)
}
// decodeBase64ToPNG 将 Base64 字符串解码为 PNG 图片并保存为临时文件
func DecodeBase64ToPNG(encodedBase64Str string, fileName string) (string, error) {
// 去掉 Base64 前缀,如 "data:image/png;base64,"
encodedBase64Str = strings.TrimPrefix(encodedBase64Str, "data:image/png;base64,")
// 进行 URL 解码,恢复 encodeURIComponent 编码的数据
base64Str, err := url.QueryUnescape(encodedBase64Str)
if err != nil {
return "", err
}
// 解码 Base64 数据
data, err := base64.StdEncoding.DecodeString(base64Str)
if err != nil {
return "", err
}
// 创建 PNG 文件
file, err := os.Create(fileName)
if err != nil {
return "", err
}
defer file.Close()
// 将解码后的字节流写入图片文件
img, err := png.Decode(strings.NewReader(string(data)))
if err != nil {
return "", err
}
// 将 PNG 编码写入文件
err = png.Encode(file, img)
if err != nil {
return "", err
}
return fileName, nil
}
func SetCacheData(ctx context.Context, data interface{}) (uuid string) {
jsonData, _ := json.Marshal(data)
// 生成一个唯一的UUID
uniqueID := uuid2.New().String()
db.RedisClient.Set(ctx, uniqueID, jsonData, 5*time.Minute)
return uniqueID
}
func GetCacheData(ctx context.Context, uniqueID string) (data interface{}, err error) {
val, err := db.RedisClient.Get(ctx, uniqueID).Result()
if err != nil {
return nil, err
}
err = json.Unmarshal([]byte(val), &data)
if err != nil {
return nil, err
}
return data, nil
}

78
utils/encrypt.go Normal file
View File

@@ -0,0 +1,78 @@
package utils
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/base64"
"fmt"
"io"
)
// 加密函数
func EncryptCode(plainText string, key string) (string, error) {
// 创建 AES 块加密器
block, err := aes.NewCipher([]byte(key))
if err != nil {
return "", err
}
// 将原始文本转为字节数组
plainTextBytes := []byte(plainText)
// 创建加密用的 GCM (Galois/Counter Mode)
aesGCM, err := cipher.NewGCM(block)
if err != nil {
return "", err
}
// 创建随机的 nonce长度必须为 GCM 的 nonceSize
nonce := make([]byte, aesGCM.NonceSize())
if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
return "", err
}
// 加密数据并附带 nonce
cipherText := aesGCM.Seal(nonce, nonce, plainTextBytes, nil)
// 将加密后的内容用 base64 编码
return base64.StdEncoding.EncodeToString(cipherText), nil
}
// 解密函数
func DecryptCode(cipherText string, key string) (string, error) {
// 解码 base64 编码的密文
cipherTextBytes, err := base64.StdEncoding.DecodeString(cipherText)
if err != nil {
return "", err
}
// 创建 AES 块加密器
block, err := aes.NewCipher([]byte(key))
if err != nil {
return "", err
}
// 创建加密用的 GCM
aesGCM, err := cipher.NewGCM(block)
if err != nil {
return "", err
}
// 分离 nonce 和加密数据
nonceSize := aesGCM.NonceSize()
if len(cipherTextBytes) < nonceSize {
return "", fmt.Errorf("cipherText too short")
}
nonce, cipherTextBytes := cipherTextBytes[:nonceSize], cipherTextBytes[nonceSize:]
// 解密数据
plainTextBytes, err := aesGCM.Open(nil, nonce, cipherTextBytes, nil)
if err != nil {
return "", err
}
// 返回解密后的明文
return string(plainTextBytes), nil
}

10
utils/errors.go Normal file
View File

@@ -0,0 +1,10 @@
package utils
import "errors"
// 定义常见的错误
var (
ErrNoRecord = errors.New("no record found")
ErrSystemError = errors.New("system error")
ErrUnmashal = errors.New("unmashal error")
)

164
utils/jwt.go Normal file
View File

@@ -0,0 +1,164 @@
package utils
import (
"errors"
"fmt"
"github.com/gin-gonic/gin"
jwt "github.com/golang-jwt/jwt/v5"
"golang.org/x/sync/singleflight"
"log"
"net"
"qnc-server/config"
model "qnc-server/model/common"
"strconv"
"strings"
"time"
)
type JWT struct {
SigningKey []byte
}
var (
TokenExpired = errors.New("Token is expired")
TokenNotValidYet = errors.New("Token not active yet")
TokenMalformed = errors.New("That's not even a token")
TokenInvalid = errors.New("Couldn't handle this token:")
)
func NewJWT() *JWT {
return &JWT{
[]byte(config.ConfigData.JWT.SigningKey),
}
}
func (j *JWT) CreateClaims(baseClaims model.BaseClaims) model.CustomClaims {
bf, _ := ParseDuration(config.ConfigData.JWT.BufferTime)
ep, _ := ParseDuration(config.ConfigData.JWT.ExpiresTime)
claims := model.CustomClaims{
BaseClaims: baseClaims,
BufferTime: int64(bf / time.Second), // 缓冲时间1天 缓冲时间内会获得新的token刷新令牌 此时一个用户会存在两个有效令牌 但是前端只留一个 另一个会丢失
RegisteredClaims: jwt.RegisteredClaims{
Audience: jwt.ClaimStrings{"FM"}, // 受众
NotBefore: jwt.NewNumericDate(time.Now().Add(-1000)), // 签名生效时间
ExpiresAt: jwt.NewNumericDate(time.Now().Add(ep)), // 过期时间 7天 配置文件
Issuer: config.ConfigData.JWT.Issuer, // 签名的发行者
},
}
return claims
}
// 创建一个token
func (j *JWT) CreateToken(claims model.CustomClaims) (string, error) {
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString(j.SigningKey)
}
var concurrencyControl = &singleflight.Group{}
// CreateTokenByOldToken 旧token 换新token 使用归并回源避免并发问题
func (j *JWT) CreateTokenByOldToken(oldToken string, claims model.CustomClaims) (string, error) {
v, err, _ := concurrencyControl.Do("JWT:"+oldToken, func() (interface{}, error) {
return j.CreateToken(claims)
})
return v.(string), err
}
// ParseToken 解析并验证 JWT 字符串
func (j *JWT) ParseToken(tokenString string) (*model.CustomClaims, error) {
token, err := jwt.ParseWithClaims(tokenString, &model.CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
// 检查签名方法
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
}
return j.SigningKey, nil
})
if err != nil {
return nil, err
}
if claims, ok := token.Claims.(*model.CustomClaims); ok && token.Valid {
return claims, nil
} else {
return nil, errors.New("invalid token")
}
}
// 解析时间字符
func ParseDuration(d string) (time.Duration, error) {
d = strings.TrimSpace(d)
dr, err := time.ParseDuration(d)
if err == nil {
return dr, nil
}
if strings.Contains(d, "d") {
index := strings.Index(d, "d")
hour, _ := strconv.Atoi(d[:index])
dr = time.Hour * 24 * time.Duration(hour)
ndr, err := time.ParseDuration(d[index+1:])
if err != nil {
return dr, nil
}
return dr + ndr, nil
}
dv, err := strconv.ParseInt(d, 10, 64)
return time.Duration(dv), err
}
func SetToken(c *gin.Context, token string, maxAge int) {
// 增加cookie x-token 向来源的web添加
host, _, err := net.SplitHostPort(c.Request.Host)
if err != nil {
host = c.Request.Host
}
if net.ParseIP(host) != nil {
c.SetCookie("x-token", token, maxAge, "/", "", false, false)
} else {
c.SetCookie("x-token", token, maxAge, "/", host, false, false)
}
}
func ClearToken(c *gin.Context) {
// 增加cookie x-token 向来源的web添加
host, _, err := net.SplitHostPort(c.Request.Host)
if err != nil {
host = c.Request.Host
}
if net.ParseIP(host) != nil {
c.SetCookie("x-token", "", -1, "/", "", false, false)
} else {
c.SetCookie("x-token", "", -1, "/", host, false, false)
}
}
func GetToken(c *gin.Context) string {
token, _ := c.Cookie("x-token")
if token == "" {
token = c.Request.Header.Get("x-token")
}
return token
}
func GetClaims(c *gin.Context) (*model.CustomClaims, error) {
token := GetToken(c)
j := NewJWT()
claims, err := j.ParseToken(token)
if err != nil {
log.Println("从Gin的Context中获取从jwt解析信息失败, 请检查请求头是否存在x-token且claims是否为规定结构")
}
return claims, err
}
func GetUserID(c *gin.Context) uint {
if claims, exists := c.Get("claims"); !exists {
if cl, err := GetClaims(c); err != nil {
return 0
} else {
return cl.BaseClaims.Userid
}
} else {
waitUse := claims.(*model.CustomClaims)
return waitUse.BaseClaims.Userid
}
}

22
utils/lawsuitQuery.go Normal file
View File

@@ -0,0 +1,22 @@
package utils
import (
"crypto/md5"
"encoding/hex"
"time"
)
func GenerateMD5() string {
currentTime := time.Now()
dateString := currentTime.Format("20060102")
inputString := "6tj4u" + dateString
hasher := md5.New()
hasher.Write([]byte(inputString))
hashBytes := hasher.Sum(nil)
hashString := hex.EncodeToString(hashBytes)
return hashString
}

64
utils/notify.go Normal file
View File

@@ -0,0 +1,64 @@
package utils
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/base64"
"errors"
"fmt"
"io"
)
// 将整数转换为固定长度的字符串
func intToFixedLengthString(num int, length int) string {
return fmt.Sprintf("%0*d", length, num)
}
// 加密函数
func IDEncrypt(plainText, key string) (string, error) {
block, err := aes.NewCipher([]byte(key))
if err != nil {
return "", err
}
plainTextBytes := []byte(plainText)
cipherText := make([]byte, aes.BlockSize+len(plainTextBytes))
iv := cipherText[:aes.BlockSize]
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
return "", err
}
stream := cipher.NewCFBEncrypter(block, iv)
stream.XORKeyStream(cipherText[aes.BlockSize:], plainTextBytes)
// 使用URL安全的Base64编码
encrypted := base64.URLEncoding.EncodeToString(cipherText)
return encrypted, nil
}
// 解密函数
func IDDecrypt(encryptedText, key string) (string, error) {
cipherText, err := base64.URLEncoding.DecodeString(encryptedText)
if err != nil {
return "", err
}
block, err := aes.NewCipher([]byte(key))
if err != nil {
return "", err
}
if len(cipherText) < aes.BlockSize {
return "", errors.New("cipherText too short")
}
iv := cipherText[:aes.BlockSize]
cipherText = cipherText[aes.BlockSize:]
stream := cipher.NewCFBDecrypter(block, iv)
stream.XORKeyStream(cipherText, cipherText)
return string(cipherText), nil
}

75
utils/parseIdCard.go Normal file
View File

@@ -0,0 +1,75 @@
package utils
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"qnc-server/model/model"
"regexp"
"strconv"
"time"
)
func loadRegions(filename string) (map[string]model.Region, error) {
file, err := os.Open(filename)
if err != nil {
return nil, err
}
defer file.Close()
byteValue, _ := ioutil.ReadAll(file)
var regions map[string]model.Region
json.Unmarshal(byteValue, &regions)
return regions, nil
}
func ParseIDCard(id string) (model.IDInfo, error) {
if len(id) != 18 {
return model.IDInfo{}, fmt.Errorf("Invalid ID length")
}
// Validate the ID card number
validID, _ := regexp.MatchString(`^\d{17}[\dX]$`, id)
if !validID {
return model.IDInfo{}, fmt.Errorf("Invalid ID format")
}
regionCode := id[:6]
birthDate := id[6:14]
genderCode := id[16:17]
regions, err := loadRegions("processed.json")
if err != nil {
return model.IDInfo{}, fmt.Errorf("loadRegions error: %v", err)
}
region, exists := regions[regionCode]
if !exists {
return model.IDInfo{}, fmt.Errorf("Region code not found")
}
birth, err := time.Parse("20060102", birthDate)
if err != nil {
return model.IDInfo{}, fmt.Errorf("Invalid birth date")
}
gender, err := strconv.Atoi(genderCode)
if err != nil {
return model.IDInfo{}, fmt.Errorf("Invalid gender code")
}
genderStr := "男"
if gender%2 == 0 {
genderStr = "女"
}
return model.IDInfo{
Location: region.FullRegion,
BirthDate: birth.Format("2006年01月02日"),
Gender: genderStr,
}, nil
}

46
utils/pay.go Normal file
View File

@@ -0,0 +1,46 @@
package utils
import (
"fmt"
"math/rand"
"time"
)
func GenerateOrderNumber() string {
const prefix = "order"
const length = 24
rand.Seed(time.Now().UnixNano())
// 生成后缀的长度
suffixLength := length - len(prefix)
suffix := make([]byte, suffixLength)
// 填充随机数字
for i := 0; i < suffixLength; i++ {
suffix[i] = byte(rand.Intn(10)) + '0'
}
return prefix + string(suffix)
}
func GenerateOrderRefundNumber() string {
const prefix = "orderRefund"
const length = 24
rand.Seed(time.Now().UnixNano())
// 生成后缀的长度
suffixLength := length - len(prefix)
suffix := make([]byte, suffixLength)
// 填充随机数字
for i := 0; i < suffixLength; i++ {
suffix[i] = byte(rand.Intn(10)) + '0'
}
return prefix + string(suffix)
}
// ConvertCentsToYuan 将金额从分转换为元并保留两位小数 阿里支付用
func ConvertCentsToYuan(amountInCents int) string {
amountInYuan := float64(amountInCents) / 100
return fmt.Sprintf("%.2f", amountInYuan)
}

78
utils/veriryCode.go Normal file
View File

@@ -0,0 +1,78 @@
package utils
import (
"context"
"errors"
"fmt"
"github.com/redis/go-redis/v9"
"math/rand"
"qnc-server/config"
"qnc-server/db"
"regexp"
"time"
)
var ctx = context.Background()
func GenerateVerificationCode() string {
r := rand.New(rand.NewSource(time.Now().UnixNano()))
code := fmt.Sprintf("%06d", r.Intn(1000000))
return code
}
// 验证手机号码格式的函数
func IsValidPhoneNumber(phone string) bool {
re := regexp.MustCompile(`^1[3-9]\d{9}$`)
return re.MatchString(phone)
}
// StoreVerificationCode 存储验证码到 Redis
func StoreVerificationCode(phoneNumber, code string) error {
err := db.RedisClient.Set(ctx, phoneNumber, code, config.ConfigData.VerifyCode.Expiration).Err()
if err != nil {
return err
}
err = SetThrottle(phoneNumber)
if err != nil {
return err
}
return nil
}
// VerifyCode 验证验证码
func VerifyCode(phoneNumber, code string) (bool, error) {
//if phoneNumber == "18276151590" {
// return true, nil
//}
storedCode, err := db.RedisClient.Get(ctx, phoneNumber).Result()
if errors.Is(err, redis.Nil) {
return false, fmt.Errorf("验证码不存在或已过期")
} else if err != nil {
return false, err
}
if storedCode == code {
return true, nil
} else {
return false, nil
}
}
// CanRequestCode 检查用户是否可以请求新的验证码
func CanRequestCode(phoneNumber string) (bool, error) {
key := "throttle:" + phoneNumber
exists, err := db.RedisClient.Exists(ctx, key).Result()
if err != nil {
return false, err
}
return exists == 1, nil
}
// SetThrottle 设置用户请求的节流
func SetThrottle(phoneNumber string) error {
key := "throttle:" + phoneNumber
err := db.RedisClient.Set(ctx, key, "1", config.ConfigData.VerifyCode.ThrottleTime).Err()
if err != nil {
return err
}
return nil
}

150
utils/westdex.go Normal file
View File

@@ -0,0 +1,150 @@
package utils
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/sha1"
"encoding/base64"
)
const (
KEY_SIZE = 16 // AES-128, 16 bytes
)
// Encrypt encrypts the given data using AES encryption in ECB mode with PKCS5 padding
func WestDexEncrypt(data, secretKey string) (string, error) {
key := generateAESKey(KEY_SIZE*8, []byte(secretKey))
ciphertext, err := aesEncrypt([]byte(data), key)
if err != nil {
return "", err
}
return base64.StdEncoding.EncodeToString(ciphertext), nil
}
// Decrypt decrypts the given base64-encoded string using AES encryption in ECB mode with PKCS5 padding
func WestDexDecrypt(encodedData, secretKey string) (string, error) {
ciphertext, err := base64.StdEncoding.DecodeString(encodedData)
if err != nil {
return "", err
}
key := generateAESKey(KEY_SIZE*8, []byte(secretKey))
plaintext, err := aesDecrypt(ciphertext, key)
if err != nil {
return "", err
}
return string(plaintext), nil
}
// generateAESKey generates a key for AES encryption using a SHA-1 based PRNG
func generateAESKey(length int, password []byte) []byte {
h := sha1.New()
h.Write(password)
state := h.Sum(nil)
keyBytes := make([]byte, 0, length/8)
for len(keyBytes) < length/8 {
h := sha1.New()
h.Write(state)
state = h.Sum(nil)
keyBytes = append(keyBytes, state...)
}
return keyBytes[:length/8]
}
// aesEncrypt encrypts plaintext using AES in ECB mode with PKCS5 padding
func aesEncrypt(plaintext, key []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
paddedPlaintext := pkcs5Padding(plaintext, block.BlockSize())
ciphertext := make([]byte, len(paddedPlaintext))
mode := newECBEncrypter(block)
mode.CryptBlocks(ciphertext, paddedPlaintext)
return ciphertext, nil
}
// aesDecrypt decrypts ciphertext using AES in ECB mode with PKCS5 padding
func aesDecrypt(ciphertext, key []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
plaintext := make([]byte, len(ciphertext))
mode := newECBDecrypter(block)
mode.CryptBlocks(plaintext, ciphertext)
return pkcs5Unpadding(plaintext), nil
}
// pkcs5Padding pads the input to a multiple of the block size using PKCS5 padding
func pkcs5Padding(src []byte, blockSize int) []byte {
padding := blockSize - len(src)%blockSize
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
return append(src, padtext...)
}
// pkcs5Unpadding removes PKCS5 padding from the input
func pkcs5Unpadding(src []byte) []byte {
length := len(src)
unpadding := int(src[length-1])
return src[:(length - unpadding)]
}
// ECB mode encryption/decryption
type ecb struct {
b cipher.Block
blockSize int
}
func newECB(b cipher.Block) *ecb {
return &ecb{
b: b,
blockSize: b.BlockSize(),
}
}
type ecbEncrypter ecb
func newECBEncrypter(b cipher.Block) cipher.BlockMode {
return (*ecbEncrypter)(newECB(b))
}
func (x *ecbEncrypter) BlockSize() int { return x.blockSize }
func (x *ecbEncrypter) 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:]
}
}
type ecbDecrypter ecb
func newECBDecrypter(b cipher.Block) cipher.BlockMode {
return (*ecbDecrypter)(newECB(b))
}
func (x *ecbDecrypter) BlockSize() int { return x.blockSize }
func (x *ecbDecrypter) 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:]
}
}