DKIM signing

This topic contains 9 replies, has 2 voices, and was last updated by  Sam Boutros 2 years, 6 months ago.

  • Author
  • #22269

    Sam Boutros

    This question is probably for Dave Wyatt 🙂
    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 [b]performs digital signing on the message[/b], 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:

    -----END RSA PRIVATE KEY-----

  • #22270

    Dave Wyatt

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

  • #22271

    Dave Wyatt

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

    • #22273

      Sam Boutros

      Yep, this is not a real key, it's made up.. 🙂

  • #22274

    Sam Boutros

    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..

  • #22276

    Dave Wyatt

    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:

    – The .NET Framework crypto libraries are not going to be much help to you at the moment, because they all work with Certificates rather than simple key pairs. You'll have a much easier time if you use a respected third-party crypto library; perhaps BouncyCastle. Here's an example of importing those key files with BouncyCastle:

    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.

  • #22286

    Sam Boutros

    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.

  • #22287

    Dave Wyatt

    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)
  • #22288

    Dave Wyatt

    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. 🙂

    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)
  • #22290

    Sam Boutros

    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

You must be logged in to reply to this topic.