适用于 PowerShell 5.1 及以上版本(Windows)
在自动化脚本中处理密码、API 密钥、连接字符串等敏感信息时,直接将明文写入脚本或配置文件是非常危险的做法。一旦代码仓库泄露或日志被不当收集,这些凭据就会暴露无遗。PowerShell 提供了 SecureString 类型,它使用 Windows 数据保护 API(DPAPI)对内存中的字符串进行加密,使得敏感数据不会以明文形式驻留在进程内存中。
虽然 SecureString 并非银弹——在 .NET Core 跨平台场景下其保护能力有所减弱——但在 Windows 环境中,结合 DPAPI 的用户/机器级密钥,它仍然是脚本开发中最实用的凭据保护手段之一。本文将系统介绍 SecureString 的创建、转换、持久化以及凭据对象的完整使用流程,帮助你在自动化场景中更安全地管理敏感信息。
创建 SecureString
SecureString 有多种创建方式,应根据使用场景选择合适的方法。对于交互式脚本,推荐使用 Read-Host 弹出提示让用户输入;对于自动化场景,可以从已加密的文件中还原。
下面的示例展示了三种常见的创建方式:
1 2 3 4 5 6 7 8 9 10
| $securePwd = Read-Host -Prompt "请输入密码" -AsSecureString
$plainText = "MySecretPass123!" $securePwd = $plainText | ConvertTo-SecureString -AsPlainText -Force
$encryptedString = Get-Content -Path "C:\Vault\pwd.enc.txt" -Raw $securePwd = $encryptedString | ConvertTo-SecureString
|
执行结果示例:
1 2 3
| 请输入密码: ************
(使用 -AsSecureString 时,输入内容以星号显示,不会回显明文)
|
注意方式二使用了 -AsPlainText -Force 参数,它会将明文字符串直接转换为 SecureString。由于明文在转换前会短暂出现在内存中,这种方式仅适合开发测试,不建议在生产环境中使用。
持久化加密字符串
在自动化场景中,我们需要将密码保存到文件以便脚本无人值守运行。ConvertFrom-SecureString 可以将 SecureString 导出为加密后的十六进制字符串。默认情况下使用当前用户的 DPAPI 密钥加密,这意味着只有同一个 Windows 用户账户才能解密。
1 2 3 4 5
| $cred = Get-Credential -Message "输入需要持久化的凭据" $cred.Password | ConvertFrom-SecureString | Set-Content -Path "C:\Vault\encrypted_pwd.txt"
Write-Host "密码已加密保存到 C:\Vault\encrypted_pwd.txt"
|
执行结果示例:
1 2 3 4 5 6 7 8 9
| cmdlet Get-Credential at command pipeline position 1 Supply values for the following parameters: Message: 输入需要持久化的凭据
Windows PowerShell credential request. 输入需要持久化的凭据 User: admin Password for user admin: ************ 密码已加密保存到 C:\Vault\encrypted_pwd.txt
|
保存后的文件内容是一串十六进制字符,例如:
1
| 01000000d08c9ddf0115d1118c7a00c04fc297eb01000000...
|
这段密文绑定到当前 Windows 用户的 DPAPI 密钥,其他用户即使拿到文件也无法解密。
使用自定义密钥跨机器复用
默认的 DPAPI 加密方式仅限当前用户解密,如果需要在不同机器或不同用户之间共享加密凭据,可以使用自定义密钥(AES-256)进行加密。密钥本身需要通过安全的渠道分发(如密钥管理系统或环境变量),而不是硬编码在脚本中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| $key = New-Object byte[] 32 $rng = [System.Security.Cryptography.RandomNumberGenerator]::Create() $rng.GetBytes($key)
$plainText = "SuperSecretAPIKey2025!" $securePwd = $plainText | ConvertTo-SecureString -AsPlainText -Force $encrypted = $securePwd | ConvertFrom-SecureString -Key $key $encrypted | Set-Content -Path "C:\Vault\api_key.enc.txt"
$key | Set-Content -Path "C:\Vault\aes_key.bin" -Encoding Byte
Write-Host "已使用 AES-256 密钥加密并保存"
|
执行结果示例:
在目标机器上,只需要加载相同的密钥文件即可还原密码:
1 2 3 4 5 6 7 8 9 10
| $key = Get-Content -Path "C:\Vault\aes_key.bin" -Encoding Byte -ReadCount 0 $encrypted = Get-Content -Path "C:\Vault\api_key.enc.txt" -Raw $securePwd = $encrypted | ConvertTo-SecureString -Key $key
$BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($securePwd) $plainText = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)
Write-Host "还原后的 API 密钥: $($plainText.Substring(0,8))..."
|
执行结果示例:
1
| 还原后的 API 密钥: SuperSec...
|
构建 PSCredential 对象
在实际工作中,我们很少单独使用 SecureString,更多时候需要将它封装为 PSCredential 对象。PSCredential 是 PowerShell 中标准的凭据类型,几乎所有需要身份验证的 cmdlet(如 Invoke-Command、Enter-PSSession、New-SmbMapping)都接受此类型作为 -Credential 参数。
1 2 3 4 5 6 7 8 9 10 11
| $username = "DOMAIN\ServiceAccount" $encryptedPwd = Get-Content -Path "C:\Vault\encrypted_pwd.txt" -Raw $securePwd = $encryptedPwd | ConvertTo-SecureString
$credential = New-Object System.Management.Automation.PSCredential($username, $securePwd)
Write-Host "用户名: $($credential.UserName)" Write-Host "密码长度: $($credential.GetNetworkCredential().Password.Length) 个字符" Write-Host "是否已加密: $($credential.Password.IsReadOnly())"
|
执行结果示例:
1 2 3
| 用户名: DOMAIN\ServiceAccount 密码长度: 17 个字符 是否已加密: True
|
构建好 PSCredential 对象后,就可以直接传递给需要身份验证的命令:
1 2 3 4 5 6 7 8 9 10 11
| $session = New-PSSession -ComputerName "SRV01" -Credential $credential $result = Invoke-Command -Session $session -ScriptBlock { Get-Process | Sort-Object -Property WorkingSet64 -Descending | Select-Object -First 5 } Remove-PSSession -Session $session
foreach ($proc in $result) { Write-Host "$($proc.Name) - $([math]::Round($proc.WorkingSet64 / 1MB, 2)) MB" }
|
执行结果示例:
1 2 3 4 5
| sqlservr - 2456.32 MB w3wp - 856.71 MB Microsoft.Exchange - 412.48 MB MsMpEng - 198.15 MB System - 45.89 MB
|
批量管理多个凭据
在管理多台服务器或多个服务时,往往需要维护多组凭据。可以将凭据信息存储在结构化的配置文件中,通过循环批量加载使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| $services = @( @{ Name = "DatabaseServer"; User = "sa"; EncryptedFile = "C:\Vault\db_pwd.txt" } @{ Name = "FileShare"; User = "fileadmin"; EncryptedFile = "C:\Vault\fs_pwd.txt" } @{ Name = "BackupService"; User = "backup"; EncryptedFile = "C:\Vault\bk_pwd.txt" } )
$credentials = @{} foreach ($svc in $services) { $encryptedPwd = Get-Content -Path $svc.EncryptedFile -Raw if ($encryptedPwd) { $securePwd = $encryptedPwd | ConvertTo-SecureString $cred = New-Object System.Management.Automation.PSCredential($svc.User, $securePwd) $credentials[$svc.Name] = $cred Write-Host "[OK] 已加载凭据: $($svc.Name) ($($svc.User))" } else { Write-Host "[WARN] 凭据文件为空: $($svc.EncryptedFile)" } }
Write-Host "`n共加载 $($credentials.Count) 组凭据"
|
执行结果示例:
1 2 3 4 5
| [OK] 已加载凭据: DatabaseServer (sa) [OK] 已加载凭据: FileShare (fileadmin) [OK] 已加载凭据: BackupService (backup)
共加载 3 组凭据
|
通过哈希表存储凭据对象,在后续脚本中可以按服务名称快速查找并使用对应的凭据,既清晰又安全。
注意事项
- DPAPI 绑定用户和机器:默认的
ConvertFrom-SecureString 使用 DPAPI 加密,密文与当前 Windows 用户账户绑定。如果切换用户或在其他机器上运行,将无法解密。跨机器场景请使用自定义密钥模式。
- 避免在代码中硬编码明文:
ConvertTo-SecureString -AsPlainText -Force 虽然方便,但明文会在脚本文件和进程内存中暴露。生产环境应使用 Get-Credential 或从加密文件加载。
- 自定义密钥的安全分发:使用
-Key 参数时,密钥文件本身需要妥善保管。建议通过环境变量、Azure Key Vault、HashiCorp Vault 等专业密钥管理服务分发,而非将密钥文件提交到代码仓库。
- 及时释放 BSTR 指针:使用
[Marshal]::SecureStringToBSTR() 还原明文后,应调用 [Marshal]::ZeroFreeBSTR() 释放内存,避免明文在内存中残留时间过长。
- 跨平台限制:在 PowerShell 7 的 Linux/macOS 上,
SecureString 的加密机制不同于 Windows DPAPI,保护效果有限。如果在非 Windows 平台运行,建议使用平台原生的密钥管理工具。
- 日志脱敏:确保脚本中的
Write-Host、Write-Verbose 等输出语句不会打印 SecureString 的内容。PSCredential 对象的 .Password 属性在字符串上下文中会显示为 System.Security.SecureString,不会泄露明文,但仍需避免调用 .GetNetworkCredential().Password 后输出结果。