Encrypting and Decrypting Files with Smartcards and PowerShell

So you want to send a file securely to someone using their smart card certificate, but without using S/MIME?

PowerShell can come to the rescue. With PowerShell, we can invoke the .NET Cryptography APIs and the local certificate store, which lets us utilise any certificates via CAPI.

Before you do anything, get the recipient to send you their encryption certificate (without the Private Key of course!) and import it into your Personal Certificate Store.

Encrypting the File

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
## Variables
$hash = "A1B2C3D4..." # you can find the hash of the cert you want to use by running "dir Cert:\CurrentUser\My"
$fileToEncrypt = "C:\Temp\FileToEncrypt.txt"
$outputFile = "C:\Temp\EncryptedFile.enc"

## Load the assembly, otherwise you'll get errors
[System.Reflection.Assembly]::LoadWithPartialName("System.Security") | Out-Null;

$cleartextBytes = [System.IO.File]::ReadAllBytes($fileToEncrypt);
$encryptionContentInfo = New-Object System.Security.Cryptography.Pkcs.ContentInfo @(,$cleartextBytes);
$envelopedCms = New-Object Security.Cryptography.Pkcs.EnvelopedCms $encryptionContentInfo;
$encryptionCert = Get-Item ("Cert:\CurrentUser\My\" + $hash);
$cmsRecipient = New-Object Security.Cryptography.Pkcs.CmsRecipient $encryptionCert;
$envelopedCms.Encrypt($cmsRecipient);
$encodedBytes = $envelopedCms.Encode();
$base64Bytes = [Convert]::ToBase64String($encodedBytes);
$base64Bytes | Out-File $outputFile;

Decrypting the File

Now, the recipient recieves your EncryptedFile.enc (or whatever you’ve called it), and they can run:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
## Variables
$hash = "A1B2C3D4..." # you can find the hash of the cert you want to use by running "dir Cert:\CurrentUser\My"
$fileToDecrypt = "C:\Temp\EncryptedFile.enc"
$outputFile = "C:\Temp\DecryptedFile.txt"

## Load the assembly, otherwise you'll get errors
[System.Reflection.Assembly]::LoadWithPartialName("System.Security") | Out-Null;

$ciphertext = Get-Content $fileToDecrypt;
$decodedCiphertext = [System.Convert]::FromBase64String($ciphertext);
$envelopedCms = New-Object Security.Cryptography.Pkcs.EnvelopedCms;
$envelopedCms.Decode($decodedCiphertext);
$encryptionCert = Get-Item ("Cert:\CurrentUser\My\" + $hash);
$collection = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2Collection $encryptionCert;
$envelopedCms.Decrypt($collection);
$decryptedData = $envelopedCms.ContentInfo.Content;
[System.IO.File]::WriteAllBytes($outputFile,$decryptedData);

If their private key is stored on a smart card, HSM, or even just password-protected, then CAPI will prompt you to provide the passphrase to use the key and decrypt the file.