Files
nofx/crypto/encryption.go
T

374 lines
9.3 KiB
Go

package crypto
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/binary"
"encoding/pem"
"errors"
"fmt"
"io"
"log"
"os"
"sync"
)
// EncryptionManager 加密管理器(單例模式)
type EncryptionManager struct {
privateKey *rsa.PrivateKey
publicKeyPEM string
masterKey []byte // 用於數據庫加密的主密鑰
mu sync.RWMutex
}
var (
instance *EncryptionManager
once sync.Once
)
// GetEncryptionManager 獲取加密管理器實例
func GetEncryptionManager() (*EncryptionManager, error) {
var initErr error
once.Do(func() {
instance, initErr = newEncryptionManager()
})
return instance, initErr
}
// newEncryptionManager 初始化加密管理器
func newEncryptionManager() (*EncryptionManager, error) {
em := &EncryptionManager{}
// 1. 加載或生成 RSA 密鑰對
if err := em.loadOrGenerateRSAKeyPair(); err != nil {
return nil, fmt.Errorf("初始化 RSA 密鑰失敗: %w", err)
}
// 2. 加載或生成數據庫主密鑰
if err := em.loadOrGenerateMasterKey(); err != nil {
return nil, fmt.Errorf("初始化主密鑰失敗: %w", err)
}
log.Println("🔐 加密管理器初始化成功")
return em, nil
}
// ==================== RSA 密鑰管理 ====================
const (
rsaKeySize = 4096
rsaPrivateKeyFile = ".secrets/rsa_private.pem"
rsaPublicKeyFile = ".secrets/rsa_public.pem"
masterKeyFile = ".secrets/master.key"
)
// loadOrGenerateRSAKeyPair 加載或生成 RSA 密鑰對
func (em *EncryptionManager) loadOrGenerateRSAKeyPair() error {
// 確保 .secrets 目錄存在
if err := os.MkdirAll(".secrets", 0700); err != nil {
return err
}
// 嘗試加載現有密鑰
if _, err := os.Stat(rsaPrivateKeyFile); err == nil {
return em.loadRSAKeyPair()
}
// 生成新密鑰對
log.Println("🔑 生成新的 RSA-4096 密鑰對...")
privateKey, err := rsa.GenerateKey(rand.Reader, rsaKeySize)
if err != nil {
return err
}
em.privateKey = privateKey
// 保存私鑰
privateKeyBytes := x509.MarshalPKCS1PrivateKey(privateKey)
privateKeyPEM := pem.EncodeToMemory(&pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: privateKeyBytes,
})
if err := os.WriteFile(rsaPrivateKeyFile, privateKeyPEM, 0600); err != nil {
return err
}
// 保存公鑰
publicKeyBytes, err := x509.MarshalPKIXPublicKey(&privateKey.PublicKey)
if err != nil {
return err
}
publicKeyPEM := pem.EncodeToMemory(&pem.Block{
Type: "PUBLIC KEY",
Bytes: publicKeyBytes,
})
if err := os.WriteFile(rsaPublicKeyFile, publicKeyPEM, 0644); err != nil {
return err
}
em.publicKeyPEM = string(publicKeyPEM)
log.Println("✅ RSA 密鑰對已生成並保存")
return nil
}
// loadRSAKeyPair 加載 RSA 密鑰對
func (em *EncryptionManager) loadRSAKeyPair() error {
// 加載私鑰
privateKeyPEM, err := os.ReadFile(rsaPrivateKeyFile)
if err != nil {
return err
}
block, _ := pem.Decode(privateKeyPEM)
if block == nil || block.Type != "RSA PRIVATE KEY" {
return errors.New("無效的私鑰 PEM 格式")
}
privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return err
}
em.privateKey = privateKey
// 加載公鑰
publicKeyPEM, err := os.ReadFile(rsaPublicKeyFile)
if err != nil {
return err
}
em.publicKeyPEM = string(publicKeyPEM)
log.Println("✅ RSA 密鑰對已加載")
return nil
}
// GetPublicKeyPEM 獲取公鑰 (PEM 格式)
func (em *EncryptionManager) GetPublicKeyPEM() string {
em.mu.RLock()
defer em.mu.RUnlock()
return em.publicKeyPEM
}
// ==================== 混合解密 (RSA + AES) ====================
// DecryptWithPrivateKey 使用私鑰解密數據
// 數據格式: [加密的 AES 密鑰長度(4字節)] + [加密的 AES 密鑰] + [IV(12字節)] + [加密數據]
func (em *EncryptionManager) DecryptWithPrivateKey(encryptedBase64 string) (string, error) {
em.mu.RLock()
defer em.mu.RUnlock()
// Base64 解碼
encryptedData, err := base64.StdEncoding.DecodeString(encryptedBase64)
if err != nil {
return "", fmt.Errorf("Base64 解碼失敗: %w", err)
}
if len(encryptedData) < 4+256+12 { // 最小長度檢查
return "", errors.New("加密數據長度不足")
}
// 1. 讀取加密的 AES 密鑰長度
aesKeyLen := binary.BigEndian.Uint32(encryptedData[:4])
if aesKeyLen > 1024 { // 防止過大的長度值
return "", errors.New("無效的 AES 密鑰長度")
}
offset := 4
// 2. 提取加密的 AES 密鑰
encryptedAESKey := encryptedData[offset : offset+int(aesKeyLen)]
offset += int(aesKeyLen)
// 3. 使用 RSA 私鑰解密 AES 密鑰
aesKey, err := rsa.DecryptOAEP(sha256.New(), rand.Reader, em.privateKey, encryptedAESKey, nil)
if err != nil {
return "", fmt.Errorf("RSA 解密失敗: %w", err)
}
// 4. 提取 IV
iv := encryptedData[offset : offset+12]
offset += 12
// 5. 提取加密數據
ciphertext := encryptedData[offset:]
// 6. 使用 AES-GCM 解密
block, err := aes.NewCipher(aesKey)
if err != nil {
return "", err
}
aesGCM, err := cipher.NewGCM(block)
if err != nil {
return "", err
}
plaintext, err := aesGCM.Open(nil, iv, ciphertext, nil)
if err != nil {
return "", fmt.Errorf("AES 解密失敗: %w", err)
}
// 清除敏感數據
for i := range aesKey {
aesKey[i] = 0
}
return string(plaintext), nil
}
// ==================== 數據庫加密 (AES-256-GCM) ====================
// loadOrGenerateMasterKey 加載或生成數據庫主密鑰
func (em *EncryptionManager) loadOrGenerateMasterKey() error {
// 優先從環境變數加載
if envKey := os.Getenv("NOFX_MASTER_KEY"); envKey != "" {
decoded, err := base64.StdEncoding.DecodeString(envKey)
if err == nil && len(decoded) == 32 {
em.masterKey = decoded
log.Println("✅ 從環境變數加載主密鑰")
return nil
}
log.Println("⚠️ 環境變數中的主密鑰無效,使用文件密鑰")
}
// 嘗試從文件加載
if _, err := os.Stat(masterKeyFile); err == nil {
keyBytes, err := os.ReadFile(masterKeyFile)
if err != nil {
return err
}
decoded, err := base64.StdEncoding.DecodeString(string(keyBytes))
if err != nil || len(decoded) != 32 {
return errors.New("主密鑰文件損壞")
}
em.masterKey = decoded
log.Println("✅ 從文件加載主密鑰")
return nil
}
// 生成新主密鑰
log.Println("🔑 生成新的數據庫主密鑰 (AES-256)...")
masterKey := make([]byte, 32)
if _, err := io.ReadFull(rand.Reader, masterKey); err != nil {
return err
}
em.masterKey = masterKey
// 保存到文件
encoded := base64.StdEncoding.EncodeToString(masterKey)
if err := os.WriteFile(masterKeyFile, []byte(encoded), 0600); err != nil {
return err
}
log.Println("✅ 主密鑰已生成並保存")
log.Printf("📁 主密鑰文件位置: %s (權限: 0600)", masterKeyFile)
log.Println("🔐 生產環境請設置環境變數: NOFX_MASTER_KEY=<從文件讀取>")
log.Println("⚠️ 請妥善保管 .secrets 目錄,切勿將密鑰提交到版本控制系統")
return nil
}
// EncryptForDatabase 使用主密鑰加密數據(用於數據庫存儲)
func (em *EncryptionManager) EncryptForDatabase(plaintext string) (string, error) {
em.mu.RLock()
defer em.mu.RUnlock()
block, err := aes.NewCipher(em.masterKey)
if err != nil {
return "", err
}
aesGCM, err := cipher.NewGCM(block)
if err != nil {
return "", err
}
nonce := make([]byte, aesGCM.NonceSize())
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
return "", err
}
ciphertext := aesGCM.Seal(nonce, nonce, []byte(plaintext), nil)
return base64.StdEncoding.EncodeToString(ciphertext), nil
}
// DecryptFromDatabase 使用主密鑰解密數據(從數據庫讀取)
func (em *EncryptionManager) DecryptFromDatabase(encryptedBase64 string) (string, error) {
em.mu.RLock()
defer em.mu.RUnlock()
// 處理空字符串(未加密的舊數據)
if encryptedBase64 == "" {
return "", nil
}
ciphertext, err := base64.StdEncoding.DecodeString(encryptedBase64)
if err != nil {
return "", err
}
block, err := aes.NewCipher(em.masterKey)
if err != nil {
return "", err
}
aesGCM, err := cipher.NewGCM(block)
if err != nil {
return "", err
}
nonceSize := aesGCM.NonceSize()
if len(ciphertext) < nonceSize {
return "", errors.New("加密數據過短")
}
nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:]
plaintext, err := aesGCM.Open(nil, nonce, ciphertext, nil)
if err != nil {
return "", err
}
return string(plaintext), nil
}
// ==================== 密鑰輪換 ====================
// RotateMasterKey 輪換主密鑰(需要重新加密所有數據)
func (em *EncryptionManager) RotateMasterKey() error {
em.mu.Lock()
defer em.mu.Unlock()
log.Println("🔄 開始輪換主密鑰...")
// 生成新主密鑰
newMasterKey := make([]byte, 32)
if _, err := io.ReadFull(rand.Reader, newMasterKey); err != nil {
return err
}
// 備份舊密鑰
oldMasterKey := em.masterKey
// 更新密鑰
em.masterKey = newMasterKey
// 保存新密鑰
encoded := base64.StdEncoding.EncodeToString(newMasterKey)
backupFile := fmt.Sprintf("%s.backup.%d", masterKeyFile, os.Getpid())
if err := os.WriteFile(backupFile, []byte(base64.StdEncoding.EncodeToString(oldMasterKey)), 0600); err != nil {
return err
}
if err := os.WriteFile(masterKeyFile, []byte(encoded), 0600); err != nil {
return err
}
log.Println("✅ 主密鑰已輪換")
log.Printf("⚠️ 舊密鑰已備份到: %s", backupFile)
log.Printf("🔐 新主密鑰: %s", encoded)
return nil
}