feat: Add encryption service with AES-GCM, field-level encryption, and PBKDF2 key derivation

- Add EncryptionService struct with masterKey and gcm cipher fields
- Add EncryptionConfig with MasterKey and KeyDerivationSalt fields
- Add NewEncryptionService constructor with PBKDF2 key derivation, 100000 iterations, and AES-GCM cipher initialization
- Add Encrypt and Decrypt methods with nonce generation and validation
- Add EncryptString and DecryptString methods with base64 encoding
- Add FieldEncryptor struct
This commit is contained in:
nutcas3
2026-05-03 10:51:47 +03:00
parent 772c564a18
commit 79fa56f25d
@@ -0,0 +1,152 @@
package security
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"errors"
"io"
"golang.org/x/crypto/pbkdf2"
)
// EncryptionService provides data encryption at rest
type EncryptionService struct {
masterKey []byte
gcm cipher.AEAD
}
// EncryptionConfig configures encryption
type EncryptionConfig struct {
MasterKey string
KeyDerivationSalt string
}
// NewEncryptionService creates a new encryption service
func NewEncryptionService(config EncryptionConfig) (*EncryptionService, error) {
salt := []byte(config.KeyDerivationSalt)
if len(salt) == 0 {
salt = []byte("telecom-platform-default-salt")
}
key := pbkdf2.Key([]byte(config.MasterKey), salt, 100000, 32, sha256.New)
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
return &EncryptionService{
masterKey: key,
gcm: gcm,
}, nil
}
// Encrypt encrypts plaintext data
func (e *EncryptionService) Encrypt(plaintext []byte) ([]byte, error) {
nonce := make([]byte, e.gcm.NonceSize())
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
return nil, err
}
ciphertext := e.gcm.Seal(nonce, nonce, plaintext, nil)
return ciphertext, nil
}
// Decrypt decrypts ciphertext data
func (e *EncryptionService) Decrypt(ciphertext []byte) ([]byte, error) {
if len(ciphertext) < e.gcm.NonceSize() {
return nil, errors.New("ciphertext too short")
}
nonce := ciphertext[:e.gcm.NonceSize()]
ciphertext = ciphertext[e.gcm.NonceSize():]
plaintext, err := e.gcm.Open(nil, nonce, ciphertext, nil)
if err != nil {
return nil, err
}
return plaintext, nil
}
// EncryptString encrypts a string and returns base64
func (e *EncryptionService) EncryptString(plaintext string) (string, error) {
encrypted, err := e.Encrypt([]byte(plaintext))
if err != nil {
return "", err
}
return base64.StdEncoding.EncodeToString(encrypted), nil
}
// DecryptString decrypts a base64 string
func (e *EncryptionService) DecryptString(ciphertext string) (string, error) {
data, err := base64.StdEncoding.DecodeString(ciphertext)
if err != nil {
return "", err
}
decrypted, err := e.Decrypt(data)
if err != nil {
return "", err
}
return string(decrypted), nil
}
// FieldEncryptor provides field-level encryption for sensitive data
type FieldEncryptor struct {
service *EncryptionService
fields map[string]bool
}
// NewFieldEncryptor creates a field encryptor
func NewFieldEncryptor(service *EncryptionService, sensitiveFields []string) *FieldEncryptor {
fields := make(map[string]bool)
for _, f := range sensitiveFields {
fields[f] = true
}
return &FieldEncryptor{
service: service,
fields: fields,
}
}
// IsSensitive checks if a field is sensitive
func (fe *FieldEncryptor) IsSensitive(field string) bool {
return fe.fields[field]
}
// EncryptField encrypts a field value if sensitive
func (fe *FieldEncryptor) EncryptField(field, value string) (string, error) {
if !fe.IsSensitive(field) {
return value, nil
}
return fe.service.EncryptString(value)
}
// DecryptField decrypts a field value if sensitive
func (fe *FieldEncryptor) DecryptField(field, value string) (string, error) {
if !fe.IsSensitive(field) {
return value, nil
}
return fe.service.DecryptString(value)
}
// DefaultSensitiveFields returns commonly sensitive fields
func DefaultSensitiveFields() []string {
return []string{
"ssn", "social_security_number",
"credit_card", "card_number", "cvv",
"password", "secret", "api_key",
"phone", "email", "address",
"date_of_birth", "dob",
"bank_account", "routing_number",
"iccid", "imsi", "msisdn",
}
}