// internal/utils/encryption.go package utils import ( "crypto/aes" "crypto/cipher" "crypto/rand" "encoding/base64" "errors" "io" "os" ) // ✅ KRITERIA BASDAT: Keamanan Data - Enkripsi untuk Data Sensitif (5%) var encryptionKey []byte // InitEncryption inisialisasi encryption key dari environment func InitEncryption() error { key := os.Getenv("ENCRYPTION_KEY") if key == "" { // Generate random key untuk development (TIDAK untuk production!) key = "32-byte-long-encryption-key!!" // 32 bytes untuk AES-256 } if len(key) != 32 { return errors.New("encryption key must be exactly 32 bytes for AES-256") } encryptionKey = []byte(key) return nil } // EncryptString mengenkripsi string menggunakan AES-256-GCM // Digunakan untuk data sensitif: password, NRP, phone, contact info func EncryptString(plaintext string) (string, error) { if encryptionKey == nil { if err := InitEncryption(); err != nil { return "", err } } // Create AES cipher block, err := aes.NewCipher(encryptionKey) if err != nil { return "", err } // Create GCM mode (Galois/Counter Mode) - authenticated encryption gcm, err := cipher.NewGCM(block) if err != nil { return "", err } // Generate nonce (number used once) nonce := make([]byte, gcm.NonceSize()) if _, err := io.ReadFull(rand.Reader, nonce); err != nil { return "", err } // Encrypt and authenticate ciphertext := gcm.Seal(nonce, nonce, []byte(plaintext), nil) // Encode to base64 untuk storage di database return base64.StdEncoding.EncodeToString(ciphertext), nil } // DecryptString mendekripsi string yang sudah dienkripsi func DecryptString(encrypted string) (string, error) { if encryptionKey == nil { if err := InitEncryption(); err != nil { return "", err } } // Decode from base64 ciphertext, err := base64.StdEncoding.DecodeString(encrypted) if err != nil { return "", err } // Create AES cipher block, err := aes.NewCipher(encryptionKey) if err != nil { return "", err } // Create GCM mode gcm, err := cipher.NewGCM(block) if err != nil { return "", err } // Extract nonce nonceSize := gcm.NonceSize() if len(ciphertext) < nonceSize { return "", errors.New("ciphertext too short") } nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:] // Decrypt and verify plaintext, err := gcm.Open(nil, nonce, ciphertext, nil) if err != nil { return "", err } return string(plaintext), nil } // EncryptSensitiveFields helper untuk encrypt multiple fields func EncryptSensitiveFields(fields map[string]string) (map[string]string, error) { encrypted := make(map[string]string) for key, value := range fields { if value == "" { encrypted[key] = "" continue } encValue, err := EncryptString(value) if err != nil { return nil, err } encrypted[key] = encValue } return encrypted, nil } // DecryptSensitiveFields helper untuk decrypt multiple fields func DecryptSensitiveFields(fields map[string]string) (map[string]string, error) { decrypted := make(map[string]string) for key, value := range fields { if value == "" { decrypted[key] = "" continue } decValue, err := DecryptString(value) if err != nil { return nil, err } decrypted[key] = decValue } return decrypted, nil } // MaskSensitiveData untuk logging (mask sebagian data) // Contoh: "081234567890" -> "0812****7890" func MaskSensitiveData(data string) string { if len(data) <= 8 { return "****" } prefix := data[:4] suffix := data[len(data)-4:] return prefix + "****" + suffix } // ValidateEncryption test apakah encryption bekerja func ValidateEncryption() error { testData := "test-sensitive-data-12345" encrypted, err := EncryptString(testData) if err != nil { return err } decrypted, err := DecryptString(encrypted) if err != nil { return err } if decrypted != testData { return errors.New("encryption validation failed: data mismatch") } return nil }