As of version 1.9.1 DidiSoft OpenPGP Library for .NET supports OpenPGP smartcards. In this article, we are going to illustrate how to use an OpenPGP smartcard (here Yubikey) and perform the OpenPGP operations that require a private key – sign, clear text sign, detached sign and decrypt.
In order to use the example code from this chapter, you will need an OpenPGP smartcard, like Yubikey, Nitrokey, or another brand compatible with the OpenPGP smartcard specification version 2 or above.
The smartcard functionality resides in its own assembly DidiSoft.Pgp.Smartcard.dll available for .NET Framework only.
Table of contents
- Initializing the Smartcard
- Smart card information
- Importing keys
- Generate keys
- Signing
- Clear text sign
- Detached sign
- Decrypting
- Exception handling
Initializing the Smartcard
The physical smartcard is represented by the class DidiSoft.Pgp.Smartcard.SmartcardKeyStore. An instance of this class must be created and used along with DidiSoft.Pgp.PGPLib to perform PGP cryptography operations.
Get a list of available smartcards
A smartcard is referred by its name. To get a list of the names of the available smartcards we use the static GetCards() method:
C#
1 | string[] smartcards = DidiSoft.Pgp.Smartcard.SmartcardKeyStore.GetCards(); |
VB.NET
1 | Dim smartcards As String() = DidiSoft.Pgp.Smartcard.SmartcardKeyStore.GetCards() |
Create an instance of SmartcardKeyStore
There are several ways to create an instance of the SmartcardKeyStore class:
1 2 3 4 5 6 7 8 9 10 | using DidiSoft.Pgp.Smartcard; ... // open the first available smartcard SmartcardKeyStore ks = SmartcardKeyStore.OpenDefaultSmartcard("123456"); // equivalent to the above SmartcardKeyStore ks = SmartcardKeyStore.OpenSmartcard(SmartcardKeyStore.GetCards()[0], "123456"); // open a smartcard by name SmartcardKeyStore ks = new SmartcardKeyStore("Yubikey", "123456"); // equivalent to the above SmartcardKeyStore ks = SmartcardKeyStore.OpenSmartcard("Yubikey", "123456"); |
Smartcard information
Using the DidiSoft.Pgp.Smartcard.SmartcardKeyStore class we can get some identifying information from the smart card. After successfully opening a smartcard this information is loaded in properties as illustrated below:
C# sample
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | using System.IO; using DidiSoft.Pgp; using DidiSoft.Pgp.Smartcard; public class SmartCardGenerateKeys { public void Demo() { using (SmartcardKeyStore ks = SmartcardKeyStore.OpenDefaultSmartcard("123456")) { Console.WriteLine("Application ID: " + ks.ApplicationId); Console.WriteLine("Application Version: " + ks.ApplicationVersion); Console.WriteLine("Serial Number: " + ks.SerialNumber); Console.WriteLine("Card Manifacturer: " + ks.CardManufacturer); Console.WriteLine("Card Name: " + ks.CardName); Console.WriteLine("PIN remaining retries: " + ks.Pin1RetriesCount); Console.WriteLine("Admin PIN remaining retries: " + ks.Pin3RetriesCount); Console.WriteLine("Reset Counter remaining retries: " + ks.ResetCounterRetriesCount); } } } |
VB.NET sample
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | Imports System.IO Imports DidiSoft.Pgp Imports DidiSoft.Pgp.Smartcard Public Class SmartCardGenerateKeys Public Sub Demo() Using ks As SmartcardKeyStore = SmartcardKeyStore.OpenDefaultSmartcard("123456") Console.WriteLine("Application ID: " + ks.ApplicationId) Console.WriteLine("Application Version: " + ks.ApplicationVersion) Console.WriteLine("Serial Number: " + ks.SerialNumber) Console.WriteLine("Card Manifacturer: " + ks.CardManufacturer) Console.WriteLine("Card Name: " + ks.CardName) Console.WriteLine("PIN remaining retries: " + ks.Pin1RetriesCount) Console.WriteLine("Admin PIN remaining retries: " + ks.Pin3RetriesCount) Console.WriteLine("Reset Counter remaining retries: " + ks.ResetCounterRetriesCount) End Using End Sub End Class |
Importing keys into the Smartcard
The code below illustrates how to import an existing private key into an OpenPGP smartcard (like Yubikey). The key import operation requires also the additional Admin PIN password :
C# example
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | using System.IO; using DidiSoft.Pgp; using DidiSoft.Pgp.Smartcard; public class ImportIntoSmartCard { public void Demo() { using (Stream fIn = File.OpenRead(@"c:\Projects\PGPKeys\test2_secret.pgp")) using (SmartcardKeyStore ks = SmartcardKeyStore.OpenDefaultSmartcard("123456")) { string adminPIN = "12345678"; ks.ImportPrivateKey(fIn, "test2", 0, SmartcardKeyType.AuthenticationKey, adminPIN); } } } |
VB.NET exmple
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | Imports System.IO Imports DidiSoft.Pgp Imports DidiSoft.Pgp.Smartcard Public Class ImportIntoSmartCard Public Sub Demo() Using fIn As Stream = File.OpenRead("c:\Projects\PGPKeys\test2_secret.pgp") Using ks As SmartcardKeyStore = SmartcardKeyStore.OpenDefaultSmartcard("123456") Dim adminPIN As String = "12345678" ks.ImportPrivateKey(fIn, "test2", 0, SmartcardKeyType.AuthenticationKey, adminPIN) End Using End Using End Sub End Class |
Generate keys on the Smartcard
Generating keys on the OpenPGP smartcard is performed by the embedded CPU of the smart card. Currently only RSA based keys with length 4096 bits are supported. The type of key (Signing, Decrypting, Authentication) must be specified explicitly.
Again like in the key import operation the Admin PIN password also needs to be specified:
C# example
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | using System.IO; using DidiSoft.Pgp; using DidiSoft.Pgp.Smartcard; public class SmartCardGenerateKeys { public void Demo() { using (SmartcardKeyStore ks = SmartcardKeyStore.OpenDefaultSmartcard("123456")) { string adminPIN = "12345678"; KeyPairInformation key = ks.GenerateRsaKeyPair(SmartcardKeyType.AuthenticationKey, adminPIN); Console.WriteLine(key.KeyIdLongHex); } } } |
VB.NET exmple
1 2 3 4 5 6 7 8 9 10 11 12 13 | Imports System.IO Imports DidiSoft.Pgp Imports DidiSoft.Pgp.Smartcard Public Class SmartCardGenerateKeys Public Sub Demo() Using ks As SmartcardKeyStore = SmartcardKeyStore.OpenDefaultSmartcard("123456") Dim adminPIN As String = "12345678" Dim key As KeyPairInformation = ks.GenerateRsaKeyPair(SmartcardKeyType.AuthenticationKey, adminPIN) Console.WriteLine(key.KeyIdLongHex) End Using End Sub End Class |
Note: Keys created on the smart card cannot be exported from there!
Signing
After we have initialized an instance of SmartcardKeyStore we can perform OpenPGP sign operation similar to the way we do it with keys contained in a standard KeyStore. The example code below will use the master signing key from the smartcard.
If there is no such key an exception of type DidiSoft.Pgp.Exceptions.WrongPrivateKeyException will be thrown :
C# example
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | using DidiSoft.Pgp; using DidiSoft.Pgp.Smartcard; class SignSmartcardExample { public static void Main() { // initialize the smartcard SmartcardKeyStore ks = new SmartcardKeyStore("Yubikey", "123456"); // create an instance of the library PGPLib pgp = new PGPLib(); bool asciiOutput = true; // sign a file pgp.SignFile("INPUT.txt", ks, "INPUT.pgp", asciiOutput); // sign a string message string signedAsciiArmored = pgp.SignString("Hello World", ks); } } |
VB.NET example
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | Imports DidiSoft.Pgp Imports DidiSoft.Pgp.Smartcard Class SignSmartcardExample Public Shared Sub Demo() ' initialize the smartcard Dim ks As New SmartcardKeyStore("Yubikey", "123456") ' create an instance of the library Dim pgp As New PGPLib() ' sign file Dim asciiOutput As Boolean = True pgp.SignFile("INPUT.txt", ks, "OUTPUT.pgp", asciiOutput) ' sign string message Dim signedAsciiArmored As String = pgp.SignString("Hello World", ks) End Sub End Class |
Clear text sign
Clear text signing is similar to the example above. Again the master signing key from the smartcard will be used for the operation and the actual signature creation will be performed on the smartcard itself:
C# example
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | using DidiSoft.Pgp; using DidiSoft.Pgp.Smartcard; class ClearSignSmartcardExample { public static void Main() { // initialize the smartcard SmartcardKeyStore ks = new SmartcardKeyStore("Yubikey", "123456"); // create an instance of the library PGPLib pgp = new PGPLib(); // clear sign a file pgp.ClearSignFile("INPUT.txt", ks, HashAlgorithm.SHA1, "OUTPUT.pgp"); // cler sign a string message string clearSigned = pgp.ClearSignString("Hello World", ks, HashAlgorithm.SHA1); } } |
VB.NET example
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | Imports DidiSoft.Pgp Imports DidiSoft.Pgp.Smartcard Class ClearSignSmartcardExample Public Shared Sub Demo() ' initialize the smartcard Dim ks As New SmartcardKeyStore("Yubikey", "123456") ' create an instance of the library Dim pgp As New PGPLib() ' clear sign file pgp.ClearSignFile("INPUT.txt", ks, HashAlgorithm.SHA1, "OUTPUT.pgp") ' clear sign string message Dim clearSigned As String = pgp.ClearSignString("Hello World", ks, HashAlgorithm.SHA1) End Sub End Class |
Detached sign
Detached signatures are created the same way as with keys contained in an ordinary KeyStore. Again the master signing key is used for the signature operation
C# example
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | using DidiSoft.Pgp; using DidiSoft.Pgp.Smartcard; class DtachedSignSmartcardExample { public static void Main() { // initialize the smartcard SmartcardKeyStore ks = new SmartcardKeyStore("Yubikey", "123456"); // create an instance of the library PGPLib pgp = new PGPLib(); bool asciiOutput = true; // create a detached signature for a file pgp.DetachedSignFile("INPUT.txt", ks, "INPUT.sig", asciiOutput); // create a detached signature for a string message string detachedSignatureAsciiArmored = pgp.DetachedSignString("Hello World", ks); } } |
VB.NET example
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | Imports DidiSoft.Pgp Imports DidiSoft.Pgp.Smartcard Class SignSmartcardExample Public Shared Sub Demo() ' initialize the smartcard Dim ks As New SmartcardKeyStore("Yubikey", "123456") ' create an instance of the library Dim pgp As New PGPLib() ' create a detached signature for a file Dim asciiOutput As Boolean = True pgp.DetachedSignFile("INPUT.txt", ks, "OUTPUT.pgp", asciiOutput) ' create a detached signature for string message Dim detachedSsignatureAsciiArmored As String = pgp.DetachedSignString("Hello World", ks) End Sub End Class |
Decrypting
Decrypting with an OpenPGP smartcard involves the decryption sub-key located on the card. The actual decrypt operation over the symmetric session key is performed transparently on the smartcard.
In case there is no such key DidiSoft.Pgp.Exceptions.WrongPrivateKeyException will be thrown.
C# example
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | using System; using DidiSoft.Pgp; using DidiSoft.Pgp.Smartcard; public class KeyStoreDecryptFile { public static void Demo() { // initialize theKeyStore SmartcardKeyStore keyStore = new SmartcardKeyStore("Yubikey", "123456"); // initialize the library PGPLib pgp = new PGPLib(); pgp.DecryptFile(@"c:\INPUT.pgp", keyStore, @"c:\OUTPUT.txt"); } } |
VB.NET example
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | Imports System Imports DidiSoft.Pgp Imports DidiSoft.Pgp.Smartcard Public Class KeyStoreDecryptFile Public Shared Sub Demo() ' initialize theKeyStore Dim keyStore As New SmartcardKeyStore("Yubikey", "123456") ' initialize the library Dim pgp As New PGPLib() pgp.DecryptFile("c:\INPUT.pgp", keyStore, "c:\OUTPUT.txt") End Sub End Class |
Exception handling
When working with an external source of data like a smartcard exceptional conditions may occur. In order to be able to handle such cases gracefully, you can catch these exceptions:
DidiSoft.Pgp.Smartcard.SmartcardException – an IOException subclass thrown in cases when the smartcard is not available or the communication with the smartcard is malfunctioning.
The following subclasses of DidiSoft.Pgp.PGPException may also be thrown
DidiSoft.Pgp.Exceptions.WrongPasswordException – when constructing the SmartcardKeyStore class the provided unlock password (PIN1) is wrong.
DidiSoft.Pgp.Exceptions.WrongPrivateKeyException – when trying to perform signing operation and no signing key is found on the smartcard, or when trying to decrypt and no decryption key is loaded on the smartcard.
Summary
This tutorial chapter was a starting point for using OpenPGP smartcards with DidiSoft OpenPGP Library for .NET. For the listed examples you need a smartcard compatible with the OpenPGP smartcard specification.