Basdat/internal/utils/encryption.go
2025-12-20 00:01:08 +07:00

183 lines
4.0 KiB
Go

// 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
}