mirror of
https://github.com/Awuqing/BackupX.git
synced 2026-05-12 19:40:46 +08:00
156 lines
3.5 KiB
Go
156 lines
3.5 KiB
Go
package codec
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/aes"
|
|
"crypto/cipher"
|
|
"crypto/rand"
|
|
"crypto/sha256"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
)
|
|
|
|
const maskedValue = "********"
|
|
|
|
type ConfigCipher struct {
|
|
key []byte
|
|
}
|
|
|
|
type Cipher = ConfigCipher
|
|
|
|
func New(secret string) *ConfigCipher {
|
|
hash := sha256.Sum256([]byte(secret))
|
|
return &ConfigCipher{key: hash[:]}
|
|
}
|
|
|
|
func NewConfigCipher(secret string) *ConfigCipher {
|
|
return New(secret)
|
|
}
|
|
|
|
func (c *ConfigCipher) Key() []byte {
|
|
copyKey := make([]byte, len(c.key))
|
|
copy(copyKey, c.key)
|
|
return copyKey
|
|
}
|
|
|
|
func (c *ConfigCipher) Encrypt(raw []byte) (string, error) {
|
|
block, err := aes.NewCipher(c.key)
|
|
if err != nil {
|
|
return "", fmt.Errorf("create cipher: %w", err)
|
|
}
|
|
gcm, err := cipher.NewGCM(block)
|
|
if err != nil {
|
|
return "", fmt.Errorf("create gcm: %w", err)
|
|
}
|
|
nonce := make([]byte, gcm.NonceSize())
|
|
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
|
|
return "", fmt.Errorf("generate nonce: %w", err)
|
|
}
|
|
sealed := gcm.Seal(nonce, nonce, raw, nil)
|
|
return base64.RawURLEncoding.EncodeToString(sealed), nil
|
|
}
|
|
|
|
func (c *ConfigCipher) Decrypt(encoded string) ([]byte, error) {
|
|
payload, err := base64.RawURLEncoding.DecodeString(encoded)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("decode ciphertext: %w", err)
|
|
}
|
|
block, err := aes.NewCipher(c.key)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("create cipher: %w", err)
|
|
}
|
|
gcm, err := cipher.NewGCM(block)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("create gcm: %w", err)
|
|
}
|
|
if len(payload) < gcm.NonceSize() {
|
|
return nil, fmt.Errorf("ciphertext too short")
|
|
}
|
|
nonce, ciphertext := payload[:gcm.NonceSize()], payload[gcm.NonceSize():]
|
|
plain, err := gcm.Open(nil, nonce, ciphertext, nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("decrypt ciphertext: %w", err)
|
|
}
|
|
return plain, nil
|
|
}
|
|
|
|
func (c *ConfigCipher) EncryptJSON(value any) (string, error) {
|
|
plain, err := json.Marshal(value)
|
|
if err != nil {
|
|
return "", fmt.Errorf("marshal plaintext: %w", err)
|
|
}
|
|
return c.Encrypt(plain)
|
|
}
|
|
|
|
func (c *ConfigCipher) DecryptJSON(encoded string, out any) error {
|
|
plain, err := c.Decrypt(encoded)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := json.Unmarshal(plain, out); err != nil {
|
|
return fmt.Errorf("unmarshal plaintext: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *ConfigCipher) EncryptValue(value any) (string, error) {
|
|
return c.EncryptJSON(value)
|
|
}
|
|
|
|
func (c *ConfigCipher) DecryptValue(encoded string, out any) error {
|
|
return c.DecryptJSON(encoded, out)
|
|
}
|
|
|
|
func MaskConfig(raw map[string]any, sensitiveFields []string) map[string]any {
|
|
masked := cloneMap(raw)
|
|
for _, field := range sensitiveFields {
|
|
value, ok := masked[field]
|
|
if !ok {
|
|
continue
|
|
}
|
|
switch actual := value.(type) {
|
|
case string:
|
|
if actual != "" {
|
|
masked[field] = maskedValue
|
|
}
|
|
default:
|
|
masked[field] = maskedValue
|
|
}
|
|
}
|
|
return masked
|
|
}
|
|
|
|
func MergeMaskedConfig(next map[string]any, existing map[string]any, sensitiveFields []string) map[string]any {
|
|
merged := cloneMap(existing)
|
|
for key, value := range next {
|
|
merged[key] = value
|
|
}
|
|
for _, field := range sensitiveFields {
|
|
value, ok := merged[field]
|
|
if !ok {
|
|
continue
|
|
}
|
|
switch actual := value.(type) {
|
|
case string:
|
|
if actual == "" || actual == maskedValue {
|
|
merged[field] = existing[field]
|
|
}
|
|
}
|
|
}
|
|
return merged
|
|
}
|
|
|
|
func IsMaskedString(value string) bool {
|
|
return bytes.Equal([]byte(value), []byte(maskedValue))
|
|
}
|
|
|
|
func cloneMap(source map[string]any) map[string]any {
|
|
result := make(map[string]any, len(source))
|
|
for key, value := range source {
|
|
result[key] = value
|
|
}
|
|
return result
|
|
}
|