Encrypting a string using certificates and PowerShell

I recently had the need to encrypt some strings using a public and private key and then store it for later use. The public key was stored as part of a certificate issued by the internal CA, the private key was to be held offline for later use. If you were using this process to exchange data with another party, you would require the other parties public certificate (and hence their public key).

The encryption process was pretty simple; however the decryption process was another story.

Encrypting a string using a key stored in a public key requires only one prerequisite component, and that is the certificate containing the key you are going to use. This certificate in my case was stored within the Windows certificate store for the local machine, in the Trusted People folder. My encryption function will need two items of input, the string to encrypt, and the certificate we will be encrypting against as System.Security.Cryptography.X509Certificates.X509Certificate2.

You may have just wondered, or swore, “How the hell do I get a System.Security.Cryptography.X509Certificates.X509Certificate2???!!!??”. Well if you remember the power of PowerShell then you will remember that it allows you to interact with the certificate store in the same way you would the registry or the file system.

To see all the Trusted Root Certification Authorities using PowerShell:

Dir cert:\localmachine\Root

Look at the output, does it look familiar? This is the equivalent to opening the MMC, adding the Certificates snap-in for the local machine and browsing to Trusted Root Certification Authorities.

What about the certificates for my AD account?

Dir cert:\currentuser\my

This is the equivalent to opening the MMC, adding the Certificates snap-in for the current user and browsing to Personal.

 

If you perform a get-member on what is returned, you will notice a the objects being returned are the right type for what my function will require.

For what I was doing, I knew that the certificate with the thumbprint of 5F507E471772839A953406A00537F609955AFCD7 which was stored the Trusted People folder for the local machine. I could get the certificate using

Gi cert:\localmachine\trustedpeople\5F507E471772839A953406A00537F609955AFCD7

 

The other thing to note, is that my encryption function actually returns a base64 encoded envelope which not only contains the message/data which was encrypted but also information regarding the certificate used to encrypt the data (and which information will be used later to decrypt the data).

The encryption function looks like this:

Function encrypt-envelope ($unprotectedcontent, $cert)

{

            [System.Reflection.Assembly]::LoadWithPartialName("System.Security") | Out-Null

            $utf8content = [Text.Encoding]::UTF8.GetBytes($unprotectedcontent)

            $content = New-Object Security.Cryptography.Pkcs.ContentInfo `

                    -argumentList (,$utf8content)

            $env = New-Object Security.Cryptography.Pkcs.EnvelopedCms $content

            $recpient = (New-Object System.Security.Cryptography.Pkcs.CmsRecipient($cert))

            $env.Encrypt($recpient)

            $base64string = [Convert]::ToBase64String($env.Encode())

            return $base64string

}

Decryption is a little more complicated, but it isn’t overly complicated when you sit down and actually think about it. To work out the reverse method, I simply worked with PowerShell and the MSDN library to reverse the process laid out in the encryption method.

For this to work, you need to have the private key for the corresponding certificate installed in either the Local Computer or User certificate stores.

function decrypt-envelope ($base64string)

{

            [System.Reflection.Assembly]::LoadWithPartialName("System.Security") | Out-Null

            $content = [Convert]::FromBase64String($base64string)

 

            $env = New-Object Security.Cryptography.Pkcs.EnvelopedCms

            $env.Decode($content)

            $env.Decrypt()

 

            $utf8content = [text.encoding]::UTF8.getstring($env.ContentInfo.Content)

            return $utf8content

}

Note this will return the string correctly formatted as it originally entered.

I will be posting some other scripts using this code in the coming days.