package crypto import ( "crypto/aes" "crypto/cipher" "crypto/rand" "crypto/sha256" "encoding/base64" "encoding/hex" "errors" "io" "os" ) func defaultKey() []byte { key := os.Getenv("ENCRYPT_KEY") if key == "" { key = "opencatd-default-key-change-me" } h := sha256.Sum256([]byte(key)) return h[:] // 32 bytes } // Encrypt encrypts plaintext using AES-GCM with the default key func Encrypt(plaintext string) (string, error) { enc, err := NewEncryptor(defaultKey()) if err != nil { return "", err } return enc.Encrypt(plaintext) } // Decrypt decrypts ciphertext using AES-GCM with the default key func Decrypt(encoded string) (string, error) { enc, err := NewEncryptor(defaultKey()) if err != nil { return "", err } return enc.Decrypt(encoded) } // Sha256Hex is a convenience wrapper for SHA-256 hex hashing func Sha256Hex(data string) string { h := sha256.Sum256([]byte(data)) return hex.EncodeToString(h[:]) } // Encryptor AES-GCM 加密器 type Encryptor struct { key []byte } // NewEncryptor 创建加密器(key 为 16/24/32 字节) func NewEncryptor(key []byte) (*Encryptor, error) { switch len(key) { case 16, 24, 32: default: return nil, errors.New("crypto: invalid key length, must be 16, 24, or 32 bytes") } return &Encryptor{key: key}, nil } // Encrypt AES-GCM 加密,返回 base64 编码的密文 func (e *Encryptor) Encrypt(plaintext string) (string, error) { block, err := aes.NewCipher(e.key) if err != nil { return "", err } gcm, err := cipher.NewGCM(block) if err != nil { return "", err } nonce := make([]byte, gcm.NonceSize()) if _, err := io.ReadFull(rand.Reader, nonce); err != nil { return "", err } ciphertext := gcm.Seal(nonce, nonce, []byte(plaintext), nil) return base64.StdEncoding.EncodeToString(ciphertext), nil } // Decrypt AES-GCM 解密 func (e *Encryptor) Decrypt(encoded string) (string, error) { data, err := base64.StdEncoding.DecodeString(encoded) if err != nil { return "", err } block, err := aes.NewCipher(e.key) if err != nil { return "", err } gcm, err := cipher.NewGCM(block) if err != nil { return "", err } nonceSize := gcm.NonceSize() if len(data) < nonceSize { return "", errors.New("ciphertext too short") } nonce, ciphertext := data[:nonceSize], data[nonceSize:] plaintext, err := gcm.Open(nil, nonce, ciphertext, nil) if err != nil { return "", err } return string(plaintext), nil }