mirror of
https://github.com/laoxong/nofx.git
synced 2026-06-04 09:58:22 +08:00
374 lines
9.3 KiB
Go
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
|
|
}
|