如何在 PowerShell 中使用 S/MIME 对消息进行签名和加密

Posted

技术标签:

【中文标题】如何在 PowerShell 中使用 S/MIME 对消息进行签名和加密【英文标题】:How to sign and encrypt a message using S/MIME in PowerShell 【发布时间】:2017-12-31 12:00:42 【问题描述】:

我正在尝试创建一个 PowerShell 脚本,它将:

构建消息 使用我的私人 S/MIME 证书签署消息 使用收件人的 S/MIME 公共证书加密邮件 发送已签名和加密的电子邮件

我在下面包含了完整的脚本,但更改了电子邮件地址、证书名称等。

已使用 Internet Explorer 将私有证书导入计算机。然后在目录C:\Users\xxx\AppData\Roaming\Microsoft\SystemCertificates\My\Certificates\中引用它

问题是当我使用脚本发送电子邮件时,它被加密但没有签名。

但是,如果我不加密消息,而是在构建内存流时包含$SignedMessageBytes(请参阅脚本步骤 4 中的第一行),则电子邮件在发送时会正确签名。这表明消息在加密发生之前已被正确签名。

由于某种原因,脚本在加密消息时不会包含签名。

我必须怎么做才能在邮件被加密时包含签名?

$SMTPServer = "localhost"
$Recipient = "recipient@emailaddress.com"
$From = "sender@emailaddress.com"
$RecipientCertificatePath = "C:\recipient@emailaddress.com.cer"
$SignerCertificatePath = "C:\Users\xxx\AppData\Roaming\Microsoft\SystemCertificates\My\Certificates\xxxx"

Add-Type -assemblyName "System.Security"
$MailClient = New-Object System.Net.Mail.SmtpClient $SMTPServer
$Message = New-Object System.Net.Mail.MailMessage
$Message.To.Add($Recipient)
$Message.From = $From
$Body = $null
$File= get-item -Path "C:\CONTRL__9911837000009_4045399000008_20170704_ELE00207.TXT"
$Message.Subject = $File.Name

# STEP 1: Capture Message Body
$MIMEMessage = New-Object system.Text.StringBuilder
$MIMEMessage.AppendLine("MIME-Version: 1.0") | Out-Null
$MIMEMessage.AppendLine("Content-Type: multipart/mixed; boundary=unique-boundary-1") | Out-Null
$MIMEMessage.AppendLine() | Out-Null
$MIMEMessage.AppendLine("This is a multi-part message in MIME format.") | Out-Null
$MIMEMessage.AppendLine("--unique-boundary-1") | Out-Null
$MIMEMessage.AppendLine("Content-Type: text/plain") | Out-Null
$MIMEMessage.AppendLine("Content-Transfer-Encoding: 7Bit") | Out-Null
$MIMEMessage.AppendLine() | Out-Null
$MIMEMessage.AppendLine($Body) | Out-Null
$MIMEMessage.AppendLine() | Out-Null
$MIMEMessage.AppendLine("--unique-boundary-1") | Out-Null
$MIMEMessage.AppendLine("Content-Type: application/octet-stream; name="+ $file.Name) | Out-Null
$MIMEMessage.AppendLine("Content-Transfer-Encoding: base64") | Out-Null
$MIMEMessage.AppendLine("Content-Disposition: attachment; filename="+ $file.Name) | Out-Null
$MIMEMessage.AppendLine() | Out-Null

[Byte[]] $binaryData = [System.IO.File]::ReadAllBytes($File)
[string] $base64Value = [System.Convert]::ToBase64String($binaryData, 0, $binaryData.Length)
[int] $position = 0
while($position -lt $base64Value.Length)

    [int] $chunkSize = 100
    if (($base64Value.Length - ($position + $chunkSize)) -lt 0)
    
        $chunkSize = $base64Value.Length - $position
    
    $MIMEMessage.AppendLine($base64Value.Substring($position, $chunkSize)) | Out-Null
    $MIMEMessage.AppendLine() | Out-Null
    $position += $chunkSize;


$MIMEMessage.AppendLine("--unique-boundary-1--") | Out-Null
[Byte[]] $MessageBytes = [System.Text.Encoding]::ASCII.GetBytes($MIMEMessage.ToString())


# STEP 2: Sign
$ci = New-Object System.Security.Cryptography.Pkcs.ContentInfo(,$MessageBytes)
$signedCms = New-Object System.Security.Cryptography.Pkcs.SignedCms($ci)

$SignerCertificate = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($SignerCertificatePath)
$Signer = New-Object System.Security.Cryptography.Pkcs.CmsSigner( $SignerCertificate )
$timeAttribute = New-Object -TypeName System.Security.Cryptography.Pkcs.Pkcs9SigningTime
$null = $signer.SignedAttributes.Add($timeAttribute)
$sha2_oid = New-Object System.Security.Cryptography.Oid("2.16.840.1.101.3.4.2.1")
$Signer.DigestAlgorithm = $sha2_oid

Write-Host "-----------------------------------------------------"
Write-Host "Cert friendly name: " $Signer.Certificate.FriendlyName
Write-Host "Cert subject      : " $Signer.Certificate.Subject
Write-Host "Cert thumbprint   : " $Signer.Certificate.Thumbprint
Write-Host "Digest algorithm  : " $Signer.DigestAlgorithm.FriendlyName
Write-Host "Sign Time         : " $Signer.SignedAttributes.Values.SigningTime

$signedCms.ComputeSignature($Signer)
$SignedMessageBytes = $signedCms.Encode()


# STEP 3: Encrypt
$ContentInfo = New-Object System.Security.Cryptography.Pkcs.ContentInfo (,$SignedMessageBytes)
$CMSRecipient = New-Object System.Security.Cryptography.Pkcs.CmsRecipient $RecipientCertificatePath
$algo_id = New-Object System.Security.Cryptography.Pkcs.AlgorithmIdentifier("2.16.840.1.101.3.4.1.42")
$EnvelopedCMS = New-Object System.Security.Cryptography.Pkcs.EnvelopedCms( $ContentInfo , $algo_id )

$EnvelopedCMS.Encrypt($CMSRecipient)

Write-Host "Key length       : " $EnvelopedCMS.ContentEncryptionAlgorithm.KeyLength
Write-Host "OID friendly name: " $EnvelopedCMS.ContentEncryptionAlgorithm.Oid.FriendlyName
Write-Host "OID value        : " $EnvelopedCMS.ContentEncryptionAlgorithm.Oid.Value
Write-Host "Parameters       : " $EnvelopedCMS.ContentEncryptionAlgorithm.Parameters

[Byte[]] $EncryptedBytes = $EnvelopedCMS.Encode()


# STEP 4: Create and send mail
$MemoryStream = New-Object System.IO.MemoryStream @(,$EncryptedBytes)
$AlternateView = New-Object System.Net.Mail.AlternateView($MemoryStream, "application/x-pkcs7-mime; smime-type=enveloped-data;name=smime.p7m")
$Message.AlternateViews.Add($AlternateView)
$MailClient.Send($Message)

【问题讨论】:

【参考方案1】:

感谢您的基础工作。

我通过添加一个额外的 mime 层使其工作:

# STEP 3: Encrypt
$OID = New-Object System.Security.Cryptography.Oid 2.16.840.1.101.3.4.1.42
$AId = New-Object System.Security.Cryptography.Pkcs.AlgorithmIdentifier ($OID, 256)

$SignatureBytes = $SignedCMS.Encode()
$MIMEMessage2 = New-Object system.Text.StringBuilder 
$MIMEMessage2.AppendLine('Content-Type: application/pkcs7-mime; smime-type=enveloped-data;name=smime.p7m') | Out-Null 
$MIMEMessage2.AppendLine('Content-Transfer-Encoding: base64') | Out-Null 
$MIMEMessage2.AppendLine() | Out-Null 
$MIMEMessage2.AppendLine([Convert]::ToBase64String($SignedMessageBytes)) | Out-Null

Byte[]] $BodyBytes = [System.Text.Encoding]::UTF8.GetBytes($MIMEMessage2.ToString())

ContentInfo = New-Object System.Security.Cryptography.Pkcs.ContentInfo (,$BodyBytes)

$CMSRecipient = New-Object System.Security.Cryptography.Pkcs.CmsRecipient $ChosenCertificate 
$EnvelopedCMS = New-Object System.Security.Cryptography.Pkcs.EnvelopedCms( $ContentInfo, $AId)
$EnvelopedCMS.Encrypt($CMSRecipient) 
[Byte[]] $EncryptedBytes = $EnvelopedCMS.Encode() 

我不确定上面的代码是否可以直接使用,因为我的变量名可能与你的不同。

以上代码仅用 Outlook2016 测试。

【讨论】:

@abil 太棒了。感谢那。您是否有机会进行转换,使其先加密然后签名(而不是先签名然后加密)。我已经尝试过,但最终我使用了多层加密和签名,但它不起作用。

以上是关于如何在 PowerShell 中使用 S/MIME 对消息进行签名和加密的主要内容,如果未能解决你的问题,请参考以下文章

Java - 使用 JavaMail 发送和接收 S/MIME 消息

如何创建 x-pkcs7-signature s/mime 消息?

从 MIME 创建 S/MIME?

使用 S/MIME 在 Ruby 中对电子邮件进行数字签名

尝试使用 Openssl 解密 S/MIME 文件

使用 S/MIME (PHP) 发送带有附件的电子邮件