183 lines
4.0 KiB
Go
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
|
|
} |