package main // synccrypto.go 提供 API Key 加密同步所需的密码学原语: // 密钥由登录密码 + 每用户随机盐经 PBKDF2-SHA256 派生(服务器只存盐,无法还原密钥), // 密文使用 AES-256-GCM,格式为 base64(nonce || ciphertext)。 import ( "crypto/aes" "crypto/cipher" "crypto/rand" "crypto/sha256" "encoding/base64" "encoding/hex" "errors" "golang.org/x/crypto/pbkdf2" ) const encKeyIterations = 120_000 // deriveEncKey 由登录密码和随机盐派生 32 字节 AES 密钥。 func deriveEncKey(password string, salt []byte) []byte { return pbkdf2.Key([]byte(password), salt, encKeyIterations, 32, sha256.New) } // newSaltHex 生成 16 字节随机盐(hex 编码)。 func newSaltHex() (string, error) { salt := make([]byte, 16) if _, e := rand.Read(salt); e != nil { return "", e } return hex.EncodeToString(salt), nil } // encryptWithKey 用 AES-256-GCM 加密,返回 base64(nonce || ciphertext)。 func encryptWithKey(key []byte, plaintext string) (string, error) { block, e := aes.NewCipher(key) if e != nil { return "", e } gcm, e := cipher.NewGCM(block) if e != nil { return "", e } nonce := make([]byte, gcm.NonceSize()) if _, e := rand.Read(nonce); e != nil { return "", e } sealed := gcm.Seal(nonce, nonce, []byte(plaintext), nil) return base64.StdEncoding.EncodeToString(sealed), nil } // decryptWithKey 解密 encryptWithKey 的输出;密钥不符或数据被篡改时报错。 func decryptWithKey(key []byte, blob string) (string, error) { raw, e := base64.StdEncoding.DecodeString(blob) if e != nil { return "", e } block, e := aes.NewCipher(key) if e != nil { return "", e } gcm, e := cipher.NewGCM(block) if e != nil { return "", e } if len(raw) < gcm.NonceSize() { return "", errors.New("ciphertext too short") } plain, e := gcm.Open(nil, raw[:gcm.NonceSize()], raw[gcm.NonceSize():], nil) if e != nil { return "", e } return string(plain), nil }