DKIM signing

This question is probably for Dave Wyatt :slight_smile:
I built a PS module that has function that acts as SMTP server - sending only. It works fine. It has the benefit of being able to return and subsequently act based on SMTP Reply Codes, as opposed to sending an email via Send-MailMessage which a) depends on an SMTP server, and b) does not return success/failure status/details with respect to the actual communication of the recipient SMTP server.

At any rate, I’m trying to implement Domain Keys Identified Mail (DKIM). I simply have to add a number of tags to the email message header. I’m supposed to performs digital signing on the message, and add the signature information in the email message header. This is my question to you, any insight on how to sign a message, or where to look?

The message can be something like:

From: Sam Boutros  
MIME-Version: 1.0 
X-Priority: 1 
Priority: urgent 
Importance: high 
Date: 01/30/2015 08:18:01 
Subject: IOPS test 8 
Content-Type: text/html; charset=us-ascii 
Here's the IOPS test result:

and I have a private key like:


Would have to research the standard first. I think it’s this one:

BTW, your private key isn’t exactly private anymore. You might want to generate a new key pair at some point. :wink:

Yep, this is not a real key, it’s made up… :slight_smile:

Here’s how the DKIM header could look like if that helps:

DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; q=dns/txt;; s=blabla; h=mime-version:date:message-id:subject:from:to:content-type;

also fake but representative data…

I’m not sure how soon I would have time to review that standard. However, I can offer a few observations from what I’ve seen so far:

And an example of using BouncyCastle’s APIs to generate an RSA signature:

That aside, all that should be left is figuring out exactly what to hash, which algorithms to use, and how to create your DKIM-Signature header.

I haven’t read through all of the standard yet, but based on some educated guesswork of your example header: looks like the header contains the algorithm, so you can use whatever you like. In your example header, I suspect that the “bh=” portion is a SHA256 hash of the message body, and “b=” is the RSA signature of that hash. The c,q,d, and possible s portions of the header probably refer to the DNS query that is needed in order to download the public key that can be used to verify the signature.

Thank you for taking the time to look into this. I truly appreciate it.

I downloaded the BouncyCastle DLL, and loaded the assembly in a PS script


I can see classes using cmdlets like:

$DLL = 'C:\Sandbox\BouncyCastle.Crypto.dll'
$Classes = [appdomain]::currentdomain.GetAssemblies() | where { $_.Location -eq $DLL } 
$Classes.GetTypes() | sort basetype
$Classes.GetTypes().Name -match 'object' | sort
$Classes.GetTypes().Name -match 'BigInteger'
$Classes.GetTypes() | where { $_.Name -match 'Signer' } # SignerUtilities

I suppose my next step is to create and use objects with New-Object but I’m having hard time identifying the object types I need to use based on the C# examples above. I have not used C# before.

I played around with it a bit. Here’s some basic code to load up an OpenSSH private RSA key file and use it to sign something. (In this case, I’m signing the byte array 1…32, but you’d probably be using it to sign the hash of your message body or something.)

For giggles, I also included some code to verify the signature, though you may not necessarily care about that part in your project.

$pemFileName = 'C:\Path\To\Private\Key\file.pem'
$dataToSign = [byte[]](1..32)

$fileStream = [System.IO.File]::OpenText($pemFileName)
$pemReader = New-Object Org.BouncyCastle.OpenSsl.PemReader($fileStream)
$keyPair = $pemReader.ReadObject()

$signer = [Org.BouncyCastle.Security.SignerUtilities]::GetSigner('RSA')
$signer.Init($true, $keyPair.Private)
$signer.BlockUpdate($dataToSign, 0, $dataToSign.Count)
$signature = $signer.GenerateSignature()

$verifier = [Org.BouncyCastle.Security.SignerUtilities]::GetSigner('RSA')
$verifier.Init($false, $keyPair.Public)
$verifier.BlockUpdate($dataToSign, 0, $dataToSign.Count)

As for computing the hash of a string, I’d just stick with the .NET framework classes for that, as they seem to involve a lot fewer steps than the BouncyCastle classes. For example:

$string = 'I am a string.'
$bytes = [System.Text.Encoding]::UTF8.GetBytes($string)
$sha = New-Object System.Security.Cryptography.SHA256CryptoServiceProvider
$hash = $sha.ComputeHash($bytes)

For the heck of it, I started researching how to make this work without Bouncy Castle. Turns out that the CryptoAPI already has the ability to import DER-encoded key files via the CryptDecodeObject function, and you can tell an RsaCryptoServiceProvider object to use that key info.

However, this is extremely limited. It doesn’t handle password-protected private key files, and writing the code to read those headers and decrypt the file first would be more work, so I’d still use Bouncy Castle in practice. I was just curious if I could get it working. :slight_smile:

function Get-RsaFromPemFile
    param (
        [string] $Path,

        [switch] $Private

    Add-Type -TypeDefinition @'
        using System;
        using System.Runtime.InteropServices;

        public class CryptoWrapper
            [DllImport("Crypt32.dll", SetLastError = true)]
            [return: MarshalAs(UnmanagedType.Bool)]
            public static extern bool CryptDecodeObject(int encodingType,
                                                        int structType,
                                                        byte[] encoded,
                                                        int encodedCount,
                                                        int flags,
                                                        byte[] blob,
                                                        ref int blobSize);
    $rsa_private_key_type = 43

    $x509_asn_encoding_constant = 1
    $pkcs7_asn_encoding_constant = 65536

    $encodingType = $x509_asn_encoding_constant -bor $pkcs7_asn_encoding_constant

    $count = 0

    $pem = [string[]]@(Get-Content $Path)
    $base64 = $pem -match '^[a-z0-9\+/=]+$' -join ''"
    $bytes = [convert]::FromBase64String($base64)

    $success = [CryptoWrapper]::CryptDecodeObject($encodingType, $rsa_private_key_type, $bytes, $bytes.Count, 0, $null, [ref] $count)

    if ($success)
        $blob = New-Object byte[]($count)
        $success = [CryptoWrapper]::CryptDecodeObject($encodingType, $rsa_private_key_type, $bytes, $bytes.Count, 0, $blob, [ref] $count)

        if ($success)
            $rsaProvider = New-Object System.Security.Cryptography.RSACryptoServiceProvider

            return $rsaProvider

    $errCode = [int][System.Runtime.InteropServices.Marshal]::GetLastWin32Error()
    $win32ex = New-Object System.ComponentModel.Win32Exception($errCode)

    throw "Error decoding PEM file: $errCode, $($win32ex.Message)"

$rsaProvider = Get-RsaFromPemFile -Path C:\Users\dlwya_000\desktop\test.pem -Private

$toSign = [byte[]](1..32)
$sha256provider = New-Object System.Security.Cryptography.SHA256CryptoServiceProvider
$hash = $sha256provider.ComputeHash($toSign)

$sha256Oid = [System.Security.Cryptography.CryptoConfig]::MapNameToOID('SHA256')

$signature = $rsaProvider.SignHash($hash, $sha256Oid)

$rsaProvider.VerifyHash($hash, $sha256Oid, $signature)

This seems to work:

$From         = '' 
$SenderName   = 'FirstName LastName via'
$DKIMDomain   = 'SendingCompanyDomain'
$DKIMSelector = 'ClientDomain'
$To           = ''
$Subject      = 'My message subject line'
$MyFQDN       = ''
$Body         = @"
Here's the IOPS test result:
"@ # Build SMTP body $Command = "From: $SenderName `r`n" $Command += "MIME-Version: 1.0 `r`n" $Command += "To: $To `r`n" $Command += "X-Priority: 1 `r`n" $Command += "Priority: urgent `r`n" $Command += "Importance: high `r`n" $Command += "Date: $(Get-Date) `r`n" $Command += "Subject: $Subject `r`n" $Command += "Content-Type: text/html; charset=us-ascii `r`n" $Command += "Message-ID: `r`n" $Command += "$Body `r`n" # Compute the hash of $Command (String) $Bytes = [System.Text.Encoding]::UTF8.GetBytes($Command) $Sha = New-Object System.Security.Cryptography.SHA256CryptoServiceProvider $Hash = $Sha.ComputeHash($Bytes) # Load the BouncyCastle assembly, read the private key [Reflection.Assembly]::LoadFile('C:\Sandbox\BouncyCastle.Crypto.dll') | Out-Null $pemFileName = 'C:\Sandbox\Privatekeyfile.DKIM' $FileStream = [System.IO.File]::OpenText($pemFileName) $pemReader = New-Object Org.BouncyCastle.OpenSsl.PemReader($FileStream) $KeyPair = $pemReader.ReadObject() $FileStream.Close() # Sign the message ($Command) $Signer = [Org.BouncyCastle.Security.SignerUtilities]::GetSigner('RSA') $Signer.Init($true, $KeyPair.Private) $Signer.BlockUpdate($Hash, 0, $Hash.Count) $Signature = $Signer.GenerateSignature() # Build the DKIM header $DKIMHeader = 'DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; q=dns/txt; ' $DKIMHeader += "d=$DKIMDomain; s=$DKIMSelector; " $DKIMHeader += 'h=mime-version:date:message-id:subject:from:to:content-type; ' $DKIMHeader += "bh=$([System.Convert]::ToBase64String($Hash)); " $DKIMHeader += "b=$([System.Convert]::ToBase64String($Signature)) `r`n" # Finally, assemble the message adding the DKIM header $Command = $DKIMHeader + $Command $Command