<# .SYNOPSIS Bookworm Portable - 凭证加密工具 .DESCRIPTION 将 API Key 和 MCP 凭证加密为 secrets.enc, 存放于 Gitea/USB 上, 安装时解密为进程级环境变量. .USAGE # 交互式创建加密凭证文件 .\encrypt-secrets.ps1 # 从现有 .env 文件加密 .\encrypt-secrets.ps1 -FromFile "C:\path\to\.env" # 解密验证 .\encrypt-secrets.ps1 -Decrypt #> param( [string]$FromFile, [switch]$Decrypt ) $ScriptDir = if ($MyInvocation.MyCommand.Path) { Split-Path -Parent $MyInvocation.MyCommand.Path } else { $PWD.Path } $SecretsEnc = Join-Path $ScriptDir "secrets.enc" $TempFile = Join-Path $env:TEMP "bw-secrets-$([guid]::NewGuid().ToString('N').Substring(0,8)).tmp" # 检查 openssl $opensslCmd = Get-Command openssl -ErrorAction SilentlyContinue if (-not $opensslCmd) { # 搜索常见 Git 安装路径 $searchPaths = @( "C:\Program Files\Git\usr\bin\openssl.exe", "D:\Git\usr\bin\openssl.exe", "D:\Git\mingw64\bin\openssl.exe", "C:\Program Files\Git\mingw64\bin\openssl.exe", "C:\Program Files (x86)\Git\usr\bin\openssl.exe" ) $found = $searchPaths | Where-Object { Test-Path $_ } | Select-Object -First 1 if ($found) { $opensslCmd = $found Write-Host "[INFO] 使用 Git 内置 openssl: $found" -ForegroundColor Gray } else { Write-Host "[ERROR] openssl 未找到。请确认 Git for Windows 已安装。" -ForegroundColor Red exit 1 } } else { $opensslCmd = $opensslCmd.Source } # ─── 解密模式 ───────────────────────────────────────── if ($Decrypt) { if (-not (Test-Path $SecretsEnc)) { Write-Host "[ERROR] secrets.enc 不存在" -ForegroundColor Red exit 1 } $password = Read-Host "输入主密码" -AsSecureString $bstr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($password) $plainPwd = [System.Runtime.InteropServices.Marshal]::PtrToStringBSTR($bstr) Write-Host "`n=== 解密内容 ===" -ForegroundColor Cyan # 通过 stdin 传入密码,避免进程列表泄露 $plainPwd | & $opensslCmd enc -aes-256-cbc -d -pbkdf2 -iter 600000 -in $SecretsEnc -pass stdin Write-Host "`n=== 结束 ===" -ForegroundColor Cyan $plainPwd = $null [System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr) exit 0 } # ─── 加密模式 ───────────────────────────────────────── Write-Host "" Write-Host " Bookworm Portable - 凭证加密工具" -ForegroundColor Cyan Write-Host " =================================" -ForegroundColor Cyan Write-Host "" try { if ($FromFile -and (Test-Path $FromFile)) { Write-Host "从文件加载: $FromFile" -ForegroundColor Gray $invalidLines = Get-Content $FromFile | Where-Object { $_.Trim() -and $_ -notmatch '^[A-Z][A-Z0-9_]+=.+$' } if ($invalidLines) { Write-Host "[WARN] 以下行格式不正确 (应为 KEY=value):" -ForegroundColor Yellow $invalidLines | ForEach-Object { Write-Host " $_" -ForegroundColor Yellow } } Copy-Item $FromFile $TempFile } else { Write-Host "请输入凭证 (key=value 格式, 每行一个, 空行结束):" -ForegroundColor White Write-Host "常见 Key:" -ForegroundColor Gray Write-Host " ANTHROPIC_API_KEY=sk-ant-..." -ForegroundColor DarkGray Write-Host " ANTHROPIC_BASE_URL=https://your-relay.com/v1" -ForegroundColor DarkGray Write-Host " GITHUB_PERSONAL_ACCESS_TOKEN=ghp_..." -ForegroundColor DarkGray Write-Host " SLACK_BOT_TOKEN=xoxb-..." -ForegroundColor DarkGray Write-Host " ATLASSIAN_API_TOKEN=..." -ForegroundColor DarkGray Write-Host " BROWSERBASE_API_KEY=..." -ForegroundColor DarkGray Write-Host " BROWSERBASE_PROJECT_ID=..." -ForegroundColor DarkGray Write-Host " FIRECRAWL_API_KEY=..." -ForegroundColor DarkGray Write-Host "" $lines = @() while ($true) { $line = Read-Host ">" if ([string]::IsNullOrWhiteSpace($line)) { break } if ($line -match '^[A-Z][A-Z0-9_]+=.+$') { $lines += $line $key = $line.Substring(0, $line.IndexOf('=')) Write-Host " [+] $key" -ForegroundColor Green } else { Write-Host " [!] 格式不正确,应为 KEY=value" -ForegroundColor Yellow } } if ($lines.Count -eq 0) { Write-Host "[!] 未输入任何凭证,退出" -ForegroundColor Yellow exit 0 } $lines -join "`n" | Set-Content $TempFile -NoNewline } # 设置密码并加密 Write-Host "" $password1 = Read-Host "设置主密码 (用于解密, 至少 12 位)" -AsSecureString $password2 = Read-Host "确认主密码" -AsSecureString $bstr1 = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($password1) $bstr2 = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($password2) $pwd1 = [System.Runtime.InteropServices.Marshal]::PtrToStringBSTR($bstr1) $pwd2 = [System.Runtime.InteropServices.Marshal]::PtrToStringBSTR($bstr2) if ($pwd1 -ne $pwd2) { Write-Host "[ERROR] 密码不匹配" -ForegroundColor Red exit 1 } if ($pwd1.Length -lt 12) { Write-Host "[ERROR] 密码至少 12 位 (推荐 16+ 位混合字符)" -ForegroundColor Red exit 1 } # AES-256-CBC 加密, PBKDF2 600000 迭代 (OWASP 2023), 通过 stdin 传入密码 $pwd1 | & $opensslCmd enc -aes-256-cbc -pbkdf2 -iter 600000 -salt -in $TempFile -out $SecretsEnc -pass stdin # 安全删除临时文件 if (Test-Path $TempFile) { $bytes = [System.IO.File]::ReadAllBytes($TempFile) [Array]::Clear($bytes, 0, $bytes.Length) [System.IO.File]::WriteAllBytes($TempFile, $bytes) Remove-Item $TempFile -Force } # 清除内存 $pwd1 = $null; $pwd2 = $null [System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr1) [System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr2) $size = (Get-Item $SecretsEnc).Length Write-Host "" Write-Host " [OK] secrets.enc 已生成 ($size bytes)" -ForegroundColor Green Write-Host " 加密: AES-256-CBC + PBKDF2 (600000 迭代)" -ForegroundColor Gray Write-Host " 路径: $SecretsEnc" -ForegroundColor Gray Write-Host "" Write-Host " 重要提醒:" -ForegroundColor Yellow Write-Host " - 主密码无法找回,请牢记" -ForegroundColor Yellow Write-Host " - 推送到 Gitea bookworm-boot 仓库即可" -ForegroundColor Yellow Write-Host " - 验证: .\encrypt-secrets.ps1 -Decrypt" -ForegroundColor Yellow } finally { # 确保任何退出路径都清理临时文件 if (Test-Path $TempFile -ErrorAction SilentlyContinue) { Remove-Item $TempFile -Force -ErrorAction SilentlyContinue } }