一、为什么需要给PowerShell脚本签名?

想象你收到一个同事发来的脚本文件,怎么确定它没被篡改过?就像快递包裹上的防拆封条,脚本签名就是给代码贴的"封条"。Windows默认会阻止运行未签名脚本,这是因为它无法确认这个脚本是你写的,还是黑客伪造的。

签名过程就像给文件盖电子公章:

  1. 作者用专属的数字证书对脚本加密
  2. 系统通过证书验证作者身份
  3. 运行前检查脚本内容是否被修改

比如你下载了个自动化部署脚本,看到签名显示来自微软官方,是不是比无名脚本放心多了?

二、签名实战四步走

1. 准备数字证书

# 技术栈:PowerShell 5.1
# 创建自签名证书(测试环境用)
$cert = New-SelfSignedCertificate `
    -Type CodeSigningCert `
    -Subject "CN=MyScripts" `
    -KeyUsage DigitalSignature `
    -KeyAlgorithm RSA `
    -KeyLength 2048 `
    -CertStoreLocation "Cert:\CurrentUser\My"

# 查看证书指纹
$cert.Thumbprint

2. 给脚本签名

# 对test.ps1文件签名
Set-AuthenticodeSignature -FilePath .\test.ps1 `
    -Certificate $cert `
    -TimestampServer "http://timestamp.digicert.com"

# 验证签名状态
Get-AuthenticodeSignature -FilePath .\test.ps1 | 
    Select Status,StatusMessage

3. 设置执行策略

# 只允许运行已签名脚本
Set-ExecutionPolicy AllSigned -Scope CurrentUser

# 查看当前策略
Get-ExecutionPolicy -List

4. 自动验证示例

# 运行前自动验证的函数
function SafeRun {
    param($scriptPath)
    $sig = Get-AuthenticodeSignature $scriptPath
    if($sig.Status -eq "Valid"){
        & $scriptPath
    } else {
        Write-Warning "脚本验证失败:$($sig.StatusMessage)"
    }
}

SafeRun .\test.ps1

三、企业级应用方案

在团队协作时,可以这样管理签名:

  1. 使用AD CS(Active Directory证书服务)颁发统一证书
  2. 通过组策略分发受信任的根证书
  3. 搭建内部时间戳服务器
# 企业环境签名示例
$companyCert = Get-ChildItem Cert:\CurrentUser\My |
    Where { $_.Subject -match "CN=公司IT部门" }

# 批量签名目录下所有脚本
Get-ChildItem .\Scripts\*.ps1 | ForEach-Object {
    Set-AuthenticodeSignature -FilePath $_.FullName `
        -Certificate $companyCert `
        -TimestampServer "http://timestamp.internal.com"
}

四、常见问题排雷指南

场景1:证书过期怎么办?

# 续期后重新签名
$renewedCert = Get-ChildItem Cert:\CurrentUser\My |
    Where { $_.Thumbprint -eq "A1B2C3..." }
    
Set-AuthenticodeSignature -FilePath .\important.ps1 `
    -Certificate $renewedCert `
    -TimestampServer "http://timestamp.digicert.com"

场景2:跨机器运行失败?

# 导出证书并导入到目标机器
Export-Certificate -Cert $cert -FilePath .\mycert.cer
# 在另一台机器执行:
Import-Certificate -FilePath .\mycert.cer `
    -CertStoreLocation Cert:\LocalMachine\Root

场景3:签名被拒绝? 检查三要素:

  1. 证书是否在受信任的根证书颁发机构存储中
  2. 时间戳服务是否可用
  3. 脚本编码是否为UTF-8 with BOM

五、安全与效率的平衡术

优点

  • 防止恶意脚本执行
  • 确保脚本完整性
  • 追溯脚本来源

缺点

  • 增加开发流程复杂度
  • 证书管理需要额外成本
  • 可能影响调试效率

最佳实践建议

  1. 开发环境用RemoteSigned策略
  2. 生产环境强制AllSigned
  3. 使用CI/CD自动签名
  4. 定期轮换签名证书
# 开发环境推荐配置
Set-ExecutionPolicy RemoteSigned -Scope CurrentUser

六、现代开发中的创新用法

结合Azure DevOps实现自动化:

# 在CI流水线中添加签名步骤
task: PowerShell@2
inputs:
  targetType: filePath
  filePath: 'scripts/SignBuild.ps1'
  arguments: >
    -KeyVaultName myVault
    -CertName BuildCert
    -ScriptPath $(Build.ArtifactStagingDirectory)/*.ps1

配合硬件安全模块(HSM):

# 使用HSM保护的证书签名
$hsmCert = Get-ChildItem -Path "Cert:\CurrentUser\My" |
    Where { $_.PrivateKey.CspKeyContainerInfo.HardwareDevice }

Set-AuthenticodeSignature -FilePath .\sensitive.ps1 `
    -Certificate $hsmCert

七、终极安全防御体系

构建多层级验证:

  1. 脚本签名验证作者
  2. 哈希校验确保内容完整
  3. 代码审计扫描危险命令
  4. 运行环境隔离保护
# 综合验证函数示例
function Invoke-SuperSafeScript {
    param(
        [Parameter(Mandatory)]
        [ValidateScript({
            Test-Path $_ -PathType Leaf
        })]
        [string]$Path
    )
    
    # 第一层:签名检查
    $sig = Get-AuthenticodeSignature $Path
    if($sig.Status -ne "Valid"){
        throw "签名验证失败"
    }
    
    # 第二层:哈希校验
    $storedHash = "A1B2C3..."
    $fileHash = (Get-FileHash $Path).Hash
    if($fileHash -ne $storedHash){
        throw "文件已被修改"
    }
    
    # 第三层:代码扫描
    $dangerousCommands = @("Remove-Item","Format-Volume")
    $content = Get-Content $Path
    if($dangerousCommands -match $content){
        Write-Warning "检测到危险命令"
    }
    
    # 最终执行
    & $Path
}

记住:没有绝对的安全,但签名就像给脚本装上防盗门,能让攻击者转向更容易得手的目标。