OpenPGP Smartcard

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

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.