package crypto import ( "crypto/aes" "crypto/cipher" "crypto/rand" "encoding/base64" "fmt" "io" "os" "path/filepath" "sync" ) // 旧版硬编码密钥(用于兼容迁移已有加密数据) var legacyKey = []byte("go-desk-db-cli-key-32bytes123456") var ( encryptionKey []byte keyOnce sync.Once keyInitErr error ) // getKey 获取或创建机器唯一密钥 // 首次启动时生成并持久化到用户配置目录,后续直接读取 func getKey() ([]byte, error) { keyOnce.Do(func() { keyFile, err := getKeyFilePath() if err != nil { keyInitErr = fmt.Errorf("获取密钥路径失败: %v", err) return } // 尝试读取已有密钥 if data, err := os.ReadFile(keyFile); err == nil && len(data) == 32 { encryptionKey = data return } // 生成新密钥 newKey := make([]byte, 32) if _, err := io.ReadFull(rand.Reader, newKey); err != nil { keyInitErr = fmt.Errorf("生成密钥失败: %v", err) return } // 持久化密钥 dir := filepath.Dir(keyFile) if err := os.MkdirAll(dir, 0700); err != nil { keyInitErr = fmt.Errorf("创建密钥目录失败: %v", err) return } if err := os.WriteFile(keyFile, newKey, 0600); err != nil { keyInitErr = fmt.Errorf("保存密钥失败: %v", err) return } encryptionKey = newKey }) return encryptionKey, keyInitErr } // getKeyFilePath 返回密钥文件路径 func getKeyFilePath() (string, error) { configDir, err := os.UserConfigDir() if err != nil { return "", err } return filepath.Join(configDir, "u-desk", ".aes-key"), nil } // DecryptPasswordV2 使用指定密钥解密(用于密钥迁移) func DecryptPasswordV2(encryptedPassword string, key []byte) (string, error) { if encryptedPassword == "" { return "", nil } if len(encryptedPassword) < 10 { return "", nil } ciphertext, err := base64.StdEncoding.DecodeString(encryptedPassword) if err != nil { return "", fmt.Errorf("解码失败: %v", err) } block, err := aes.NewCipher(key) if err != nil { return "", fmt.Errorf("创建解密器失败: %v", err) } aesGCM, err := cipher.NewGCM(block) if err != nil { return "", fmt.Errorf("创建 GCM 失败: %v", err) } nonceSize := aesGCM.NonceSize() if len(ciphertext) < nonceSize { return "", fmt.Errorf("密文长度不足") } nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:] plaintext, err := aesGCM.Open(nil, nonce, ciphertext, nil) if err != nil { return "", fmt.Errorf("解密失败: %v", err) } return string(plaintext), nil } // EncryptPassword 加密密码 func EncryptPassword(password string) (string, error) { if password == "" { return "", nil } key, err := getKey() if err != nil { return "", fmt.Errorf("获取加密密钥失败: %v", err) } block, err := aes.NewCipher(key) if err != nil { return "", fmt.Errorf("创建加密器失败: %v", err) } // 使用 GCM 模式 aesGCM, err := cipher.NewGCM(block) if err != nil { return "", fmt.Errorf("创建 GCM 失败: %v", err) } // 生成随机 nonce nonce := make([]byte, aesGCM.NonceSize()) if _, err := io.ReadFull(rand.Reader, nonce); err != nil { return "", fmt.Errorf("生成 nonce 失败: %v", err) } // 加密 ciphertext := aesGCM.Seal(nonce, nonce, []byte(password), nil) // Base64 编码 return base64.StdEncoding.EncodeToString(ciphertext), nil } // DecryptPassword 解密密码(自动回退旧密钥兼容旧数据) func DecryptPassword(encryptedPassword string) (string, error) { if encryptedPassword == "" { return "", nil } if len(encryptedPassword) < 10 { return "", nil } key, err := getKey() if err != nil { return "", fmt.Errorf("获取解密密钥失败: %v", err) } // 先用新密钥尝试解密 result, err := DecryptPasswordV2(encryptedPassword, key) if err == nil { return result, nil } // 新密钥失败,尝试旧密钥(兼容已迁移的旧数据) result, err = DecryptPasswordV2(encryptedPassword, legacyKey) if err == nil { return result, nil } // 两种密钥都失败 return "", fmt.Errorf("解密失败: %v", err) }