package comm import ( "crypto/aes" "crypto/cipher" cryptorand "crypto/rand" "encoding/base64" "fmt" "io" ) // AES-256-GCM 字段加密:console 后台与业务服务共用的单一实现。 // // 「应用的服务配置」(app_service_config) 由 console 前端写、业务服务启动时读/回写,两端读写同一张加密表, // 故加密算法必须完全一致——统一收敛到本文件(console 的 svcEncrypt/svcDecrypt、业务侧 seed 均委托这里), // 避免各处复制导致密文格式漂移。密钥由 ${FIELD_ENCRYPT_KEY} 注入,console 与业务服务必须配同一值。 // EncMask 加密字段返回前端时的脱敏占位符;更新时前端回传该占位/空串则保留原密文。 const EncMask = "[encrypted]" // normAESKey 把任意长度的密钥规整为 32 字节(AES-256):不足补零、超长截断。 func normAESKey(key string) []byte { k := make([]byte, 32) copy(k, []byte(key)) return k } // Encrypt 用 AES-256-GCM 加密明文,返回 base64(nonce‖ciphertext)。空明文返回空串。 func Encrypt(key, plaintext string) (string, error) { if plaintext == "" { return "", nil } block, err := aes.NewCipher(normAESKey(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(cryptorand.Reader, nonce); err != nil { return "", err } ct := gcm.Seal(nonce, nonce, []byte(plaintext), nil) return base64.StdEncoding.EncodeToString(ct), nil } // Decrypt 解密 Encrypt 产出的密文,返回明文。 func Decrypt(key, ciphertext string) (string, error) { if ciphertext == "" { return "", nil } raw, err := base64.StdEncoding.DecodeString(ciphertext) if err != nil { return "", fmt.Errorf("base64 解码失败: %w", err) } block, err := aes.NewCipher(normAESKey(key)) if err != nil { return "", err } gcm, err := cipher.NewGCM(block) if err != nil { return "", err } ns := gcm.NonceSize() if len(raw) < ns { return "", fmt.Errorf("密文过短") } plain, err := gcm.Open(nil, raw[:ns], raw[ns:], nil) if err != nil { return "", fmt.Errorf("GCM 解密失败: %w", err) } return string(plain), nil }