PowerShell 技能连载 - 加密与数据保护

适用于 PowerShell 5.1 及以上版本

在现代运维和自动化场景中,敏感数据几乎无处不在——数据库连接字符串、API 密钥、用户个人信息、财务记录等。如果这些数据以明文形式存储或传输,一旦系统被入侵或日志泄露,后果将不堪设想。加密(Encryption)是保护数据机密性的最后一道防线。

PowerShell 依托 .NET Framework / .NET 的 System.Security.Cryptography 命名空间,提供了从对称加密、非对称加密到哈希校验、数字签名的完整加密工具链。无论是在脚本中保护凭据、验证文件完整性,还是对发布脚本进行代码签名,这些能力都是编写安全自动化脚本的基础。

本文将通过三个实战场景,分别演示如何使用 AES 对称加密保护文件和字符串、利用哈希算法验证数据完整性,以及通过数字证书实现代码签名与验证。

对称加密与文件保护

对称加密使用同一把密钥进行加密和解密,适合对大量数据进行快速加解密。AES(Advanced Encryption Standard)是目前最广泛使用的对称加密算法,密钥长度支持 128/192/256 位。以下脚本封装了 AES 加密和解密的核心逻辑,并提供了文件级别的加密保护功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# AES 对称加密工具函数
function New-AesKey {
param([int]$KeySize = 256)
$aes = [System.Security.Cryptography.Aes]::Create()
$aes.KeySize = $KeySize
$aes.GenerateKey()
$aes.GenerateIV()
return @{
Key = $aes.Key
IV = $aes.IV
}
}

function Protect-StringWithAes {
param(
[Parameter(Mandatory)][string]$PlainText,
[Parameter(Mandatory)][byte[]]$Key,
[Parameter(Mandatory)][byte[]]$IV
)
$aes = [System.Security.Cryptography.Aes]::Create()
$aes.Key = $Key
$aes.IV = $IV
$encryptor = $aes.CreateEncryptor()
$plainBytes = [System.Text.Encoding]::UTF8.GetBytes($PlainText)
$encryptedBytes = $encryptor.TransformFinalBlock($plainBytes, 0, $plainBytes.Length)
return [Convert]::ToBase64String($encryptedBytes)
}

function Unprotect-StringWithAes {
param(
[Parameter(Mandatory)][string]$CipherText,
[Parameter(Mandatory)][byte[]]$Key,
[Parameter(Mandatory)][byte[]]$IV
)
$aes = [System.Security.Cryptography.Aes]::Create()
$aes.Key = $Key
$aes.IV = $IV
$decryptor = $aes.CreateDecryptor()
$cipherBytes = [Convert]::FromBase64String($CipherText)
$decryptedBytes = $decryptor.TransformFinalBlock($cipherBytes, 0, $cipherBytes.Length)
return [System.Text.Encoding]::UTF8.GetString($decryptedBytes)
}

# 生成密钥对
$keyInfo = New-AesKey -KeySize 256
Write-Host "AES 密钥长度: $($keyInfo.Key.Length * 8) 位"
Write-Host "初始化向量长度: $($keyInfo.IV.Length * 8) 位"

# 加密敏感字符串
$secret = "Server=db.prod.example.com;User=admin;Password=S3cretP@ss!"
$encrypted = Protect-StringWithAes -PlainText $secret -Key $keyInfo.Key -IV $keyInfo.IV
Write-Host "`n加密结果: $encrypted"

# 解密还原
$decrypted = Unprotect-StringWithAes -CipherText $encrypted -Key $keyInfo.Key -IV $keyInfo.IV
Write-Host "解密结果: $decrypted"

# 将密钥安全存储到文件(仅当前用户可访问)
$keyPath = "$env:USERPROFILE\.secrets\aes.key"
$ivPath = "$env:USERPROFILE\.secrets\aes.iv"
New-Item -ItemType Directory -Path (Split-Path $keyPath) -Force | Out-Null
[System.IO.File]::WriteAllBytes($keyPath, $keyInfo.Key)
[System.IO.File]::WriteAllBytes($ivPath, $keyInfo.IV)
Write-Host "`n密钥已保存至 $keyPath"

执行结果示例:

1
2
3
4
5
6
7
8
AES 密钥长度: 256
初始化向量长度: 128

加密结果: q8Jx3LmN9vT2kR5wY8aBcDeFgHiJkLmNoPqRsTuVwXyZ0123456789abcdefg==

解密结果: Server=db.prod.example.com;User=admin;Password=S3cretP@ss!

密钥已保存至 C:\Users\admin\.secrets\aes.key

哈希与完整性验证

哈希算法将任意长度的数据映射为固定长度的摘要值,具有单向性和抗碰撞性,广泛用于数据完整性校验和消息认证。SHA-256 和 SHA-512 是目前推荐的哈希算法。HMAC(Hash-based Message Authentication Code)则在此基础上加入密钥,既能验证完整性又能验证消息来源。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# 文件哈希计算与完整性校验
function Get-FileHashEx {
param(
[Parameter(Mandatory)][string]$Path,
[ValidateSet('SHA256', 'SHA384', 'SHA512', 'MD5')]
[string]$Algorithm = 'SHA256'
)
if (-not (Test-Path $Path)) {
throw "文件不存在: $Path"
}
$hashAlg = [System.Security.Cryptography.HashAlgorithm]::Create($Algorithm)
$fileStream = [System.IO.File]::OpenRead((Resolve-Path $Path).Path)
try {
$hashBytes = $hashAlg.ComputeHash($fileStream)
$hashHex = [BitConverter]::ToString($hashBytes) -replace '-', ''
return @{
Path = $Path
Algorithm = $Algorithm
Hash = $hashHex
Size = $fileStream.Length
}
}
finally {
$fileStream.Dispose()
}
}

# 计算文件哈希
$result = Get-FileHashEx -Path $PSCommandPath -Algorithm SHA256
Write-Host "文件: $($result.Path)"
Write-Host "算法: $($result.Algorithm)"
Write-Host "哈希: $($result.Hash)"
Write-Host "大小: $($result.Size) 字节"

# HMAC 消息认证码
function Get-HmacHash {
param(
[Parameter(Mandatory)][string]$Message,
[Parameter(Mandatory)][string]$Secret
)
$keyBytes = [System.Text.Encoding]::UTF8.GetBytes($Secret)
$messageBytes = [System.Text.Encoding]::UTF8.GetBytes($Message)
$hmac = New-Object System.Security.Cryptography.HMACSHA256 -ArgumentList @(,$keyBytes)
$hashBytes = $hmac.ComputeHash($messageBytes)
return [BitConverter]::ToString($hashBytes) -replace '-', ''
}

# 模拟 API 签名验证场景
$apiPayload = '{"action":"transfer","amount":5000,"to":"account-12345"}'
$apiSecret = "my-api-secret-key-2026"
$signature = Get-HmacHash -Message $apiPayload -Secret $apiSecret
Write-Host "`nAPI 载荷: $apiPayload"
Write-Host "HMAC 签名: $signature"

# 接收方验证签名
$receivedSig = Get-HmacHash -Message $apiPayload -Secret $apiSecret
$isValid = ($signature -eq $receivedSig)
Write-Host "签名验证: $(if ($isValid) { '通过' } else { '失败' })"

# 批量文件完整性基线
$baselineFile = "$env:TEMP\hash-baseline.csv"
$monitorPath = $PWD.Path
$files = Get-ChildItem -Path $monitorPath -Filter "*.ps1" -File
$baseline = foreach ($f in $files) {
$h = Get-FileHashEx -Path $f.FullName -Algorithm SHA256
[PSCustomObject]@{
File = $f.Name
SHA256 = $h.Hash
Timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
}
}
$baseline | Export-Csv -Path $baselineFile -NoTypeInformation -Encoding UTF8
Write-Host "`n已生成 $($baseline.Count) 个文件的完整性基线: $baselineFile"

执行结果示例:

1
2
3
4
5
6
7
8
9
10
文件: C:\Scripts\audit.ps1
算法: SHA256
哈希: A1B2C3D4E5F6789012345678901234567890ABCDEF1234567890ABCDEF123456
大小: 4096 字节

API 载荷: {"action":"transfer","amount":5000,"to":"account-12345"}
HMAC 签名: 7F8E9D0C1B2A3948567E6F5D4C3B2A10987654321F0E1D2C3B4A59687012345
签名验证: 通过

已生成 12 个文件的完整性基线: C:\Users\admin\AppData\Local\Temp\hash-baseline.csv

数字签名与证书操作

数字签名结合了非对称加密和哈希算法,不仅能验证数据的完整性,还能确认数据的来源身份。在 PowerShell 中,代码签名是脚本安全策略的核心环节——只有受信任的发布者签名的脚本才能在受限执行策略下运行。此外,证书操作还广泛用于 TLS/SSL 通信和文档签名。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# 查看本机代码签名证书
$codeCerts = Get-ChildItem -Path Cert:\CurrentUser\My -CodeSigningCert
if ($codeCerts) {
foreach ($cert in $codeCerts) {
Write-Host "证书主题: $($cert.Subject)"
Write-Host "指纹: $($cert.Thumbprint)"
Write-Host "有效期: $($cert.NotBefore) ~ $($cert.NotAfter)"
Write-Host "颁发者: $($cert.Issuer)"
Write-Host "---"
}
}
else {
Write-Host "未找到代码签名证书,创建自签名证书用于测试..."
$testCert = New-SelfSignedCertificate `
-Type CodeSigningCert `
-Subject "CN=PowerShell Test Signing" `
-CertStoreLocation "Cert:\CurrentUser\My" `
-NotAfter (Get-Date).AddYears(1)
Write-Host "已创建测试证书: $($testCert.Subject)"
Write-Host "指纹: $($testCert.Thumbprint)"
}

# 对脚本进行数字签名
function Set-ScriptSignature {
param(
[Parameter(Mandatory)][string]$ScriptPath,
[Parameter(Mandatory)][string]$CertThumbprint
)
$cert = Get-Item -Path "Cert:\CurrentUser\My\$CertThumbprint"
if (-not $cert) {
throw "未找到指纹为 $CertThumbprint 的证书"
}
$status = Get-AuthenticodeSignature -FilePath $ScriptPath
if ($status.Status -eq 'Valid') {
Write-Host "脚本已签名且签名有效: $ScriptPath"
return
}
Set-AuthenticodeSignature -FilePath $ScriptPath -Certificate $cert | Out-Null
$newStatus = Get-AuthenticodeSignature -FilePath $ScriptPath
Write-Host "签名状态: $($newStatus.Status)"
Write-Host "签名者: $($newStatus.SignerCertificate.Subject)"
}

# 证书链验证
function Test-CertificateChain {
param([Parameter(Mandatory)][string]$CertThumbprint)
$cert = Get-Item -Path "Cert:\CurrentUser\My\$CertThumbprint"
if (-not $cert) {
throw "未找到证书: $CertThumbprint"
}
$chain = New-Object System.Security.Cryptography.X509Certificates.X509Chain
$chain.ChainPolicy.RevocationMode = [System.Security.Cryptography.X509Certificates.X509RevocationMode]::Online
$chain.ChainPolicy.RevocationFlag = [System.Security.Cryptography.X509Certificates.X509RevocationFlag]::ExcludeRoot
$isValid = $chain.Build($cert)
Write-Host "证书: $($cert.Subject)"
Write-Host "链验证: $(if ($isValid) { '通过' } else { '失败' })"
if (-not $isValid) {
foreach ($element in $chain.ChainElements) {
Write-Host " 链节点: $($element.Certificate.Subject)"
}
foreach ($status in $chain.ChainStatus) {
Write-Host " 状态: $($status.StatusInformation)"
}
}
return $isValid
}

# 导出证书公钥(用于分发)
$pubCertPath = "$env:TEMP\PowerShellSigning.cer"
if ($testCert) {
Export-Certificate -Cert $testCert -FilePath $pubCertPath | Out-Null
Write-Host "`n公钥已导出至: $pubCertPath"
Write-Host "文件大小: $((Get-Item $pubCertPath).Length) 字节"
}

执行结果示例:

1
2
3
4
5
6
7
8
9
10
11
12
未找到代码签名证书,创建自签名证书用于测试...
已创建测试证书: CN=PowerShell Test Signing
指纹: A1B2C3D4E5F6789012345678901234567890ABCD

签名状态: Valid
签名者: CN=PowerShell Test Signing

证书: CN=PowerShell Test Signing
链验证: 通过

公钥已导出至: C:\Users\admin\AppData\Local\Temp\PowerShellSigning.cer
文件大小: 814 字节

注意事项

  1. 密钥管理是安全的基石:再强的加密算法,如果密钥管理不当(如硬编码在脚本中、存储在无权限控制的文件中),整个安全体系形同虚设。生产环境建议使用 Windows DPAPI(ConvertTo-SecureString)、Azure Key Vault 或 HashiCorp Vault 等专业密钥管理方案。

  2. 优先使用 AES 和 SHA-256 及以上算法:DES、3DES、MD5、SHA-1 等算法已被证实存在安全弱点,新项目应一律避免使用。AES-256 是对称加密的首选,SHA-256/SHA-512 是哈希的首选。

  3. 自签名证书仅用于测试:生产环境应使用来自受信任 CA(如企业 AD CS、Let’s Encrypt、DigiCert 等)颁发的证书。自签名证书无法被其他系统自动信任,且无法提供真正的身份验证保障。

  4. 加密数据时要同时保护 IV:AES 加密中 IV(初始化向量)不需要保密,但必须不可预测且每次加密都不同。如果重复使用相同的 Key 和 IV 加密不同数据,会大幅降低安全性。

  5. HMAC 密钥长度应与哈希输出长度匹配:HMAC-SHA256 的密钥至少应为 32 字节,HMAC-SHA512 的密钥至少应为 64 字节。过短的密钥会削弱消息认证的安全性。

  6. 注意 .NET 版本差异:部分加密 API 在 .NET Framework 和 .NET(Core)之间行为略有不同。例如 Aes.Create() 在两个运行时都可用,但某些过时的算法类(如 RijndaelManaged)在 .NET 6+ 中可能已标记为过时。建议始终使用 AesSHA256 等抽象工厂方法创建加密对象。

PowerShell 技能连载 - 加密与哈希

适用于 PowerShell 5.1 及以上版本

在运维脚本中,加密和哈希操作无处不在——验证文件完整性、保护敏感配置、安全传输数据、存储密码哈希。.NET 的 System.Security.Cryptography 命名空间提供了丰富的加密算法,PowerShell 可以直接调用这些类实现各种加密操作。理解哈希(不可逆)和加密(可逆)的区别,以及对称加密和非对称加密的适用场景,是编写安全脚本的基础。

本文将讲解 PowerShell 中的加密与哈希操作,以及实用的安全工具。

哈希计算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# 计算字符串哈希
function Get-StringHash {
param(
[Parameter(Mandatory)][string]$Text,
[ValidateSet("MD5", "SHA1", "SHA256", "SHA384", "SHA512")]
[string]$Algorithm = "SHA256"
)

$bytes = [System.Text.Encoding]::UTF8.GetBytes($Text)
$hashAlg = [System.Security.Cryptography.HashAlgorithm]::Create($Algorithm)
$hashBytes = $hashAlg.ComputeHash($bytes)
$hashHex = [BitConverter]::ToString($hashBytes) -replace '-', ''

return $hashHex.ToLower()
}

$password = "MyP@ssw0rd"
Write-Host "原文:$password"
Write-Host "MD5:$(Get-StringHash $password MD5)"
Write-Host "SHA1:$(Get-StringHash $password SHA1)"
Write-Host "SHA256:$(Get-StringHash $password SHA256)"

# 计算文件哈希(验证完整性)
$fileHash = Get-FileHash "C:\Windows\notepad.exe" -Algorithm SHA256
Write-Host "`n文件哈希:" -ForegroundColor Cyan
Write-Host " 文件:$($fileHash.Path)"
Write-Host " SHA256:$($fileHash.Hash)"

# 批量计算目录文件哈希
function Get-DirectoryHash {
param([string]$Path = ".")

Get-ChildItem $Path -File | ForEach-Object {
$hash = (Get-FileHash $_.FullName -Algorithm SHA256).Hash
[PSCustomObject]@{
File = $_.Name
Size = $_.Length
SHA256 = $hash.Substring(0, 16) + "..."
}
} | Format-Table -AutoSize
}

Get-DirectoryHash -Path "C:\Scripts"

# HMAC 哈希(带密钥的哈希,用于消息认证)
function Get-HmacHash {
param([string]$Message, [string]$Secret)

$key = [System.Text.Encoding]::UTF8.GetBytes($Secret)
$hmac = [System.Security.Cryptography.HMACSHA256]::new($key)
$msgBytes = [System.Text.Encoding]::UTF8.GetBytes($Message)
$hashBytes = $hmac.ComputeHash($msgBytes)
return [BitConverter]::ToString($hashBytes) -replace '-', ''
}

$hmac = Get-HmacHash -Message "api call data" -Secret "my-secret-key"
Write-Host "HMAC-SHA256:$($hmac.Substring(0, 16))..."

执行结果示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
原文:MyP@ssw0rd
MD5:a3bf2a57d727b47e9c8...
SHA1:482c811da5d5b4bc6d49...
SHA256:a765f9e2a8b7c6d5e4f3...

文件哈希:
文件:C:\Windows\notepad.exe
SHA256:A1B2C3D4E5F6...

File Size SHA256
---- ---- ------
deploy.ps1 10240 a1b2c3d4e5f6...
config.json 2048 d4e5f6a7b8c9...
HMAC-SHA256:e5f6a7b8c9d0...

对称加密(AES)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# AES 对称加密/解密
function Protect-AesString {
param(
[Parameter(Mandatory)][string]$PlainText,
[Parameter(Mandatory)][string]$Key
)

# 从密钥生成 256 位 AES 密钥和 IV
$keyBytes = [System.Security.Cryptography.SHA256]::Create().ComputeHash(
[System.Text.Encoding]::UTF8.GetBytes($Key)
)
$aes = [System.Security.Cryptography.Aes]::Create()
$aes.Key = $keyBytes
$aes.GenerateIV()

$encryptor = $aes.CreateEncryptor()
$plainBytes = [System.Text.Encoding]::UTF8.GetBytes($PlainText)
$encryptedBytes = $encryptor.TransformFinalBlock($plainBytes, 0, $plainBytes.Length)

# IV + 密文合并,Base64 输出
$result = New-Object byte[] ($aes.IV.Length + $encryptedBytes.Length)
[System.Array]::Copy($aes.IV, $result, $aes.IV.Length)
[System.Array]::Copy($encryptedBytes, 0, $result, $aes.IV.Length, $encryptedBytes.Length)

return [Convert]::ToBase64String($result)
}

function Unprotect-AesString {
param(
[Parameter(Mandatory)][string]$EncryptedText,
[Parameter(Mandatory)][string]$Key
)

$keyBytes = [System.Security.Cryptography.SHA256]::Create().ComputeHash(
[System.Text.Encoding]::UTF8.GetBytes($Key)
)
$allBytes = [Convert]::FromBase64String($EncryptedText)

$aes = [System.Security.Cryptography.Aes]::Create()
$aes.Key = $keyBytes

# 提取 IV(前 16 字节)
$iv = New-Object byte[] $aes.BlockSize.DivRem(8)[0]
[System.Array]::Copy($allBytes, $iv, $iv.Length)
$aes.IV = $iv

# 提取密文
$cipherBytes = New-Object byte[] ($allBytes.Length - $iv.Length)
[System.Array]::Copy($allBytes, $iv.Length, $cipherBytes, 0, $cipherBytes.Length)

$decryptor = $aes.CreateDecryptor()
$decryptedBytes = $decryptor.TransformFinalBlock($cipherBytes, 0, $cipherBytes.Length)

return [System.Text.Encoding]::UTF8.GetString($decryptedBytes)
}

# 加密/解密示例
$secret = "数据库连接字符串:Server=prod-db;Password=P@ssw0rd"
$encryptionKey = "MyApp-Encryption-Key-2025"

$encrypted = Protect-AesString -PlainText $secret -Key $encryptionKey
Write-Host "加密后:$($encrypted.Substring(0, 30))..." -ForegroundColor Cyan

$decrypted = Unprotect-AesString -EncryptedText $encrypted -Key $encryptionKey
Write-Host "解密后:$decrypted" -ForegroundColor Green

执行结果示例:

1
2
加密后:a1b2c3d4e5f67890abcdef...
解密后:数据库连接字符串:Server=prod-db;Password=P@ssw0rd

文件加密

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# 文件加密/解密
function Protect-File {
param(
[Parameter(Mandatory)][string]$Path,
[Parameter(Mandatory)][string]$Key
)

$keyBytes = [System.Security.Cryptography.SHA256]::Create().ComputeHash(
[System.Text.Encoding]::UTF8.GetBytes($Key)
)
$aes = [System.Security.Cryptography.Aes]::Create()
$aes.Key = $keyBytes
$aes.GenerateIV()

$plainBytes = [System.IO.File]::ReadAllBytes($Path)

$encryptor = $aes.CreateEncryptor()
$encryptedBytes = $encryptor.TransformFinalBlock($plainBytes, 0, $plainBytes.Length)

$output = New-Object byte[] ($aes.IV.Length + $encryptedBytes.Length)
[System.Array]::Copy($aes.IV, $output, $aes.IV.Length)
[System.Array]::Copy($encryptedBytes, 0, $output, $aes.IV.Length, $encryptedBytes.Length)

$encPath = $Path + ".enc"
[System.IO.File]::WriteAllBytes($encPath, $output)
Write-Host "已加密:$Path => $encPath" -ForegroundColor Green
}

function Unprotect-File {
param(
[Parameter(Mandatory)][string]$Path,
[Parameter(Mandatory)][string]$Key,
[string]$OutputPath
)

$keyBytes = [System.Security.Cryptography.SHA256]::Create().ComputeHash(
[System.Text.Encoding]::UTF8.GetBytes($Key)
)
$allBytes = [System.IO.File]::ReadAllBytes($Path)

$aes = [System.Security.Cryptography.Aes]::Create()
$aes.Key = $keyBytes

$iv = New-Object byte[] ($aes.BlockSize / 8)
[System.Array]::Copy($allBytes, $iv, $iv.Length)
$aes.IV = $iv

$cipherBytes = New-Object byte[] ($allBytes.Length - $iv.Length)
[System.Array]::Copy($allBytes, $iv.Length, $cipherBytes, 0, $cipherBytes.Length)

$decryptor = $aes.CreateDecryptor()
$decryptedBytes = $decryptor.TransformFinalBlock($cipherBytes, 0, $cipherBytes.Length)

$outPath = if ($OutputPath) { $OutputPath } else { $Path -replace '\.enc$', '' }
[System.IO.File]::WriteAllBytes($outPath, $decryptedBytes)
Write-Host "已解密:$Path => $outPath" -ForegroundColor Green
}

# 加密配置文件
Protect-File -Path "C:\MyApp\appsettings.json" -Key "Production-Key-2025"
Unprotect-File -Path "C:\MyApp\appsettings.json.enc" -Key "Production-Key-2025"

执行结果示例:

1
2
已加密:C:\MyApp\appsettings.json => C:\MyApp\appsettings.json.enc
已解密:C:\MyApp\appsettings.json.enc => C:\MyApp\appsettings.json

密码安全工具

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# 安全密码哈希(用于存储密码)
function New-PasswordHash {
param(
[Parameter(Mandatory)][string]$Password,
[int]$SaltSize = 16,
[int]$Iterations = 100000
)

$salt = New-Object byte[] $SaltSize
[System.Security.Cryptography.RandomNumberGenerator]::Create().GetBytes($salt)

$pbkdf2 = New-Object System.Security.Cryptography.Rfc2898DeriveBytes(
$Password, $salt, $Iterations, [System.Security.Cryptography.HashAlgorithmName]::SHA256
)
$hash = $pbkdf2.GetBytes(32)

$result = "PBKDF2-SHA256:$Iterations:$([Convert]::ToBase64String($salt)):$([Convert]::ToBase64String($hash))"
return $result
}

function Test-PasswordHash {
param(
[Parameter(Mandatory)][string]$Password,
[Parameter(Mandatory)][string]$StoredHash
)

$parts = $StoredHash -split ':'
$iterations = [int]$parts[1]
$salt = [Convert]::FromBase64String($parts[2])
$storedKey = [Convert]::FromBase64String($parts[3])

$pbkdf2 = New-Object System.Security.Cryptography.Rfc2898DeriveBytes(
$Password, $salt, $iterations, [System.Security.Cryptography.HashAlgorithmName]::SHA256
)
$testKey = $pbkdf2.GetBytes(32)

# 常量时间比较防止时序攻击
$diff = 0
for ($i = 0; $i -lt $storedKey.Length; $i++) {
$diff = $diff -bor ($storedKey[$i] -bxor $testKey[$i])
}
return ($diff -eq 0)
}

# 创建和验证密码哈希
$hash = New-PasswordHash -Password "MySecureP@ss"
Write-Host "密码哈希:$($hash.Substring(0, 40))..." -ForegroundColor Cyan

$valid = Test-PasswordHash -Password "MySecureP@ss" -StoredHash $hash
Write-Host "正确密码验证:$valid" -ForegroundColor Green

$invalid = Test-PasswordHash -Password "WrongPassword" -StoredHash $hash
Write-Host "错误密码验证:$invalid" -ForegroundColor Red

执行结果示例:

1
2
3
密码哈希:PBKDF2-SHA256:100000:a1b2c3d4e5f6g7h8...
正确密码验证:True
错误密码验证:False

注意事项

  1. 哈希 vs 加密:哈希是单向的(用于验证),加密是双向的(用于保护数据)。密码存储用哈希,配置保护用加密
  2. 不要用 MD5:MD5 和 SHA1 已被证明不安全,新项目应使用 SHA256 或更强的算法
  3. 密钥管理:加密的安全性取决于密钥的安全性。密钥不要硬编码在脚本中
  4. IV 唯一性:AES 加密每次应使用随机 IV,否则相同明文会产生相同密文
  5. 密码哈希:存储密码使用 PBKDF2、bcrypt 或 Argon2,不要用简单的 SHA256
  6. DPAPI:Windows 环境可以使用 DPAPI(Protect-CmsMessage)利用系统级密钥保护数据