mirror of
https://github.com/warmbly/warmbly.git
synced 2026-08-19 08:01:16 +00:00
131 lines
3.6 KiB
Go
131 lines
3.6 KiB
Go
package encrypt
|
|
|
|
import (
|
|
"crypto/aes"
|
|
"crypto/cipher"
|
|
"crypto/rand"
|
|
"encoding/hex"
|
|
"errors"
|
|
"io"
|
|
)
|
|
|
|
// Encrypted holds the hex-encoded ciphertext and nonce.
|
|
// It is JSON-marshalable and can be stored in PostgreSQL (JSONB) or Redis.
|
|
type Encrypted struct {
|
|
Ciphertext string `json:"ct" db:"ciphertext"` // hex
|
|
Nonce string `json:"n" db:"nonce"` // hex
|
|
}
|
|
|
|
// Encrypter performs AES-GCM encryption with a fixed 32-byte key.
|
|
type Encrypter struct {
|
|
aead cipher.AEAD
|
|
}
|
|
|
|
// NewEncrypterFromHex creates an Encrypter from a 64-character hex key.
|
|
func NewEncrypterFromHex(hexKey string) (*Encrypter, error) {
|
|
key, err := hex.DecodeString(hexKey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(key) != 32 {
|
|
return nil, errors.New("key must be 32 bytes (64 hex chars)")
|
|
}
|
|
return NewEncrypter(key)
|
|
}
|
|
|
|
// NewEncrypter creates an Encrypter from a raw 32-byte key.
|
|
func NewEncrypter(key []byte) (*Encrypter, error) {
|
|
if len(key) != 32 {
|
|
return nil, errors.New("key must be 32 bytes")
|
|
}
|
|
block, err := aes.NewCipher(key)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
aead, err := cipher.NewGCM(block)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &Encrypter{aead: aead}, nil
|
|
}
|
|
|
|
// Encrypt returns a hex-encoded ciphertext that contains the nonce prefix.
|
|
func (e *Encrypter) Encrypt(plaintext string) (string, error) {
|
|
enc, err := e.EncryptBytes([]byte(plaintext))
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return enc.Hex(), nil
|
|
}
|
|
|
|
// Decrypt accepts the hex string produced by Encrypt.
|
|
func (e *Encrypter) Decrypt(hexStr string) (string, error) {
|
|
enc, err := ParseHex(hexStr)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
plain, err := e.DecryptEncrypted(enc)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return string(plain), nil
|
|
}
|
|
|
|
// ---------------------------------------------------------------------
|
|
// Low-level byte-oriented API (preferred for services/repositories)
|
|
// ---------------------------------------------------------------------
|
|
|
|
// EncryptBytes encrypts a byte slice and returns a structured Encrypted.
|
|
func (e *Encrypter) EncryptBytes(plain []byte) (*Encrypted, error) {
|
|
nonce := make([]byte, e.aead.NonceSize())
|
|
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
|
|
return nil, err
|
|
}
|
|
ct := e.aead.Seal(nil, nonce, plain, nil)
|
|
return &Encrypted{
|
|
Ciphertext: hex.EncodeToString(ct),
|
|
Nonce: hex.EncodeToString(nonce),
|
|
}, nil
|
|
}
|
|
|
|
// DecryptEncrypted decrypts a structured Encrypted value.
|
|
func (e *Encrypter) DecryptEncrypted(enc *Encrypted) ([]byte, error) {
|
|
nonce, err := hex.DecodeString(enc.Nonce)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ct, err := hex.DecodeString(enc.Ciphertext)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return e.aead.Open(nil, nonce, ct, nil)
|
|
}
|
|
|
|
// ---------------------------------------------------------------------
|
|
// Helper for the old "single hex string" format
|
|
// ---------------------------------------------------------------------
|
|
|
|
// Hex returns ciphertext||nonce as a single hex string (same as your original code).
|
|
func (e *Encrypted) Hex() string {
|
|
// nonce (12 bytes) + ciphertext
|
|
rawNonce, _ := hex.DecodeString(e.Nonce)
|
|
rawCt, _ := hex.DecodeString(e.Ciphertext)
|
|
return hex.EncodeToString(append(rawNonce, rawCt...))
|
|
}
|
|
|
|
// ParseHex restores an Encrypted from the single-hex format.
|
|
func ParseHex(hexStr string) (*Encrypted, error) {
|
|
data, err := hex.DecodeString(hexStr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
nonceSize := 12 // GCM standard nonce
|
|
if len(data) < nonceSize {
|
|
return nil, errors.New("ciphertext too short")
|
|
}
|
|
return &Encrypted{
|
|
Nonce: hex.EncodeToString(data[:nonceSize]),
|
|
Ciphertext: hex.EncodeToString(data[nonceSize:]),
|
|
}, nil
|
|
}
|