Files
code-utils/synccrypto.go
2026-08-14 07:52:01 +08:00

76 lines
1.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}