OpenPGP KeyStore for C# and VB.NET


The KeyStore class encapsulates storage that contains public and private OpenPGP keys. Referencing the keys in the key store is done either by their Key ID, Key Hex ID or by their User ID.

The DidiSoft.Pgp.KeyStore object is an implementation of the PKCS 12 standard

Common operations

1. Create and Open

2. Save
3. Auto save and backup
4. List keys
5. Referencing a key

6. Check for a key
7. Reusing an existing .pkr and .skr files from PGP®, GnuPG, EBS®
8. Importing and Exporting
9. Change password

1. Create and open a KeyStore

1.1 KeyStore in a File

The KeyStore data is usually stored in a file. The KeyStore constructor shown below is used when we want to create or open an existing KeyStore file:

// C#
KeyStore ks = new KeyStore(@"c:\my.keystore", "my password");
' VB.NET
Dim ks As New KeyStore("c:\my.keystore", "my password")

An equivalent static construction is available:

// C#
KeyStore ks = KeyStore.OpenFile(@"c:\my.keystore", "my password");
' VB.NET
Dim ks As KeyStore = KeyStore.OpenFile("c:\my.keystore", "my password")

The key store location can be also a valid shared network path with read\write access, e.g. \\SharedServer\\SharedFolder\\my.keystore

1.2 In-memory KeyStore

An in-memory KeyStore stores temporary its content in memory and disappears if not serialized explicitly on program exit.

// C#
KeyStore ks = KeyStore.OpenInMemory();
' VB.NET
Dim ks As KeyStore = KeyStore.OpenInMemory()

The property IsInMemory can be used to check is this instance backed by a file or is a temporary in-memory key storage.

1.3 Custom storage

A custom storage for the KeyStore class provides a custom serialization mechanism. Examples of such storage can be a relational or NoSQL database, a remote web service, etc. Using the custom constructor we can rely on the Auto-save mechanism and thus don’t have to call Save explicitly on each key modification operation.

In order to create a KeyStore backed by a custom storage, a constructor that expects a parameter of type DidiSoft.Pgp.Storage.IKeyStorage must be used:

public interface IKeyStorage
{
  // loads the KeyStore from the provided Stream
  Stream GetInputStream();
  // saves the KeyStore into the specified Stream
  void Store(Stream dataStream, int dataLength);
}

Here is how to create an implementation of the IKeyStorage interface that uses a database table in MS SQL Server® :

CREATE TABLE KeyStore(KeyStoreData varbinary(MAX) NULL);

The KeyStore contents are stored as one large BLOB (binary object) in either encrypted or non-encrypted format, depending on the KeyStore constructor used.

using System;
using System.IO;
using System.Data.SqlClient;
 
public class DBKeyStorage : DidiSoft.Pgp.Storage.IKeyStorage
{
  private string connectionString;
  public DBKeyStorage(string connectionString)
  {
   this.connectionString = connectionString;
  }
 
  public Stream GetInputStream()
  {
   SqlConnection myConnection = new SqlConnection(this.connectionString);            
   myConnection.Open();
 
   using (SqlCommand cmdIns = new SqlCommand(sqlIns, myConnection))
   using (SqlDataReader dr = cmdIns.ExecuteReader())
   {
     byte[] data = new byte[] { };
     if (dr.Read())
     {
       data = (byte[])dr["KeyStoreData"];
     }
     return new MemoryStream(data);
   }
  }
 
  public void Store(Stream dataStream, int dataLength)
  {
   SqlConnection myConnection = new SqlConnection(this.connectionString);            
   myConnection.Open();
 
   string sqlIns = "select top 1 KeyStoreData from KeyStore";
   SqlCommand cmdSelect = new SqlCommand(sqlIns, myConnection);
   SqlDataReader dr = cmdSelect.ExecuteReader();
   bool rowExists = dr.Read();
   dr.Close();
   cmdSelect.Dispose();
 
   if (rowExists)
   {
	sqlIns = "UPDATE KeyStore SET KeyStoreData = @data";
	SqlCommand cmdIns = new SqlCommand(sqlIns, myConnection);
	cmdIns.Parameters.AddWithValue("@data", dataStream);
	cmdIns.ExecuteNonQuery();
	cmdIns.Dispose();
   }
   else
   {
	sqlIns = "INSERT INTO KeyStore(KeyStoreData) VALUES (@data)";
	SqlCommand cmdIns = new SqlCommand(sqlIns, myConnection);
	cmdIns.Parameters.AddWithValue("@data", dataStream);
	cmdIns.ExecuteNonQuery();
	cmdIns.Dispose();
   }
   myConnection.Close();
  }
}

Example usage of the above KeyStore custom storage is :

DBKeyStorage keyStorage = new DBKeyStorage(txtConnectionString.Text);
// cosntructor without a password expects the data to be unencrypted
KeyStore ksUnencrypted = new KeyStore(keyStorage);
// cosntructor with a password expects the data to be encrypted
KeyStore ksEncrypted = new KeyStore(keyStorage, "changeit");

2. Saving a KeyStore

The Save() method of the KeyStore class writes back the KeyStore object to its file on the disk. This operation has no effect over an in-memory KeyStore.

3. Auto save and backup

By default, the AutoSave property is on, and after each operation, the modified state of the KeyStore is saved to the disk automatically. The BackupOnSave property is true by default too and on each save, a backup file is stored with the previous state before the save operation (only one backup though).

4. List the contained keys

A list of all keys (unsorted) is obtained with the KeyStore.GetKeys() method. Each key is represented as an instance of DidiSoft.Pgp.KeyPairInformation.

If you wish the list to be sorted you can do so with by ordering based on properties of the KeyPairInformation class like:

KeyPairInformation[] keysByCreationTime = ks.GetKeys().OrderBy(item => item.CreationTime).ToArray();

The example below lists and prints the keys in a way similar to GnuPG/gpg:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
using System;
using System.Text;
using DidiSoft.Pgp;
 
public class KeyStoreListKeys
{
 public static void Demo()
 {
   // initialize the key store
   KeyStore ks = KeyStore.OpenFile(@"c:\my_key.store", "keystore password");
 
   KeyPairInformation[] keys = ks.GetKeys(); 
   StringBuilder sb = new StringBuilder();
   sb.Append("Type".PadRight(10));
   sb.Append("Key Id".PadRight(30));
   sb.Append("Created".PadRight(20));
   sb.Append("User Id");
   Console.WriteLine(sb.ToString());
 
   foreach (KeyPairInformation key in keys) {
	sb.Remove(0, sb.Length);
 
	String keyType = null;
	if (key.HasPrivateKey) {
		keyType = "pub/sec";	
	} else {
		keyType = "pub";	
	}
	sb.Append(keyType.PadRight(10));
 
	sb.Append(Convert.ToString(key.KeyId).PadRight(30));
	sb.Append(key.CreationTime.ToShortDateString().PadRight(20));
 
	foreach (String id in key.UserIds) {
		sb.Append(id);
	}
 
	Console.WriteLine(sb.ToString());
   }
 }
}

VB.NET example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
Imports System
Imports System.Text
Imports DidiSoft.Pgp
 
Public Class KeyStoreListKeys
 Public Shared Sub Demo()
   ' initialize the key store
   Dim ks As KeyStore = KeyStore.OpenFile("c:\my_key.store", "keystore password")
 
   Dim keys As KeyPairInformation() = ks.GetKeys() 
   For Each key As KeyPairInformation In keys
 
	Dim sb As New StringBuilder()
	sb.Append("Type".PadRight(10))
	sb.Append("Key Id".PadRight(30))
	sb.Append("Created".PadRight(20))
	sb.Append("User Id")
	Console.WriteLine(sb.ToString())
 
	sb.Remove(0, sb.Length)
	Dim keyType As String = Nothing
	If key.HasPrivateKey Then
		keyType = "pub/sec"
	Else
		keyType = "pub"
	End If
	sb.Append(keyType.PadRight(10))
 
	sb.Append(Convert.ToString(key.KeyId).PadRight(30))
	sb.Append(key.CreationTime.ToShortDateString().PadRight(20))
 
	For Each id As String In key.UserIds
		sb.Append(id)
	Next
 
	Console.WriteLine(sb.ToString())
   Next
 End Sub
End Class

5. Referencing a key

Here you can see how to reference a key located in a KeyStore instance through its Key ID, part of or the whole User ID and by the short or long hexadecimal representation of the Key ID.

5.1 Referencing a key by User ID

A full User ID of an OpenPGP key is usually in the format “Name of the user <email>”. In older versions of the library, a key had to be referenced by the full User ID.

As User ID can be specified by only a part of it, for example, “John Doe <john@doe.com>” can be referenced only with “john@doe.com”. Strict User ID referencing can be turned on by setting the property KeyStore.PartialMatchUserIds to false. The case sensitivity of the User ID referencing is controlled with the property KeyStore.CaseSensitiveMatchUserIds.

A key can also have multiple User ID’s associated with it and we can use any of them.

In this example, we show how to export a key to a file by specifying it by its User ID.

C# example

   KeyStore ks = KeyStore.OpenFile(@"c:\my_key.store", "keystore password");
 
   String partnerUserId = "partner@company.com";
   bool asciiArmor = true;
   ks.ExportPublicKey(@"c:\public_key.asc", partnerUserId, asciiArmor);

VB.NET example

   Dim ks As KeyStore = KeyStore.OpenFile("c:\my_key.store", "keystore password")
 
   Dim partnerUserId As String = "partner@company.com"
   Dim asciiArmor As Boolean = True
   ks.ExportPublicKey("c:\public_key.asc", partnerUserId, asciiArmor)

5.2 Referencing a key by hexadecimal Key ID

The hexadecimal representation of a Key ID is a string 16 characters long (2 characters for each byte). In order to ease users, command line PGP and GnuPG used a short hex Key ID of 8 characters representing the lower 4 bytes of the real Key ID (which is a System.Int64 value). You can use both short and long hexadecimal Key ID’s with the OpenPGP Library for .NET.

This example demonstrates how to reference an OpenPGP public key by its hexadecimal Key ID.

C# example

1
2
3
4
5
6
7
   KeyStore ks = KeyStore.OpenFile(@"c:\my_key.store", "keystore password");
 
   String partnerKeyId = "197B3E74";  // we can use any the short and the long form
   String partnerLongKeyId = "A01234B7197B3E74";
 
   bool asciiArmor = true;
   ks.ExportPublicKey(@"c:\public_key.asc", partnerKeyId, asciiArmor);

VB.NET example

1
2
3
4
5
6
   Dim ks As KeyStore = KeyStore.OpenFile("c:\my_key.store", "keystore password")
   ' we can use both the short and the long form
   Dim partnerKeyId As String = "197B3E74" 
   Dim partnerLongKeyId As String = "A01234B7197B3E74"
   Dim asciiArmor As Boolean = True
   ks.ExportPublicKey("c:\public_key.asc", partnerKeyId, asciiArmor)

5.3 Referencing a key by Key ID

The real OpenPGP Key ID is a 64-bit integer value. The Key Hex ID is constructed by the lower 32 bits of the real Key ID. The library accepts the real Key ID as a number of type Long (System.Int64).

Below we demonstrate how to obtain the real Key ID that corresponds to a given User ID.

C# example

   KeyStore ks = KeyStore.OpenFile(@"c:\my_key.store", "keystore password");
 
   String partnerUserId = "partner@company.com";
   long keyId = ks.GetKeyIdForUserId(partnerUserId);

VB.NET example

   Dim ks As KeyStore = KeyStore.OpenFile("c:\my_key.store", "keystore password")
 
   Dim partnerUserId As String = "partner@company.com"
   Dim keyId As Long = ks.GetKeyIdForUserId(partnerUserId)

6. Check for a key

Here you can see how to check is a given key contained in a KeyStore instance. A check can be made by part of the key User ID, the hexadecimal Key ID or the raw Key ID:

C# example

   KeyStore ks = KeyStore.OpenFile(@"c:\my_key.store", "keystore password");
 
   String partnerUserId = "partner@company.com";
   bool containsKey = ks.ContainsKey(partnerUserId);

VB.NET example

   Dim ks As KeyStore = KeyStore.OpenFile("c:\my_key.store", "keystore password")
 
   Dim partnerUserId As String = "partner@company.com"
   Dim containsKey As Boolean = ks.ContainsKey(partnerUserId)

7. Reusing an existing .pkr and .skr files from PGP®, GnuPG, EBS®

We can easily reuse keys from an existing PGP(r) installation. In the example below, we will create an in-memory located KeyStore instance and load the keys used by PGP(r). The same code can be used with the GnuPG keyring files too.

C# example

   KeyStore ks = KeyStore.OpenInMemory();
 
   ks.ImportKeyRing(@"C:\Users\JohnDoe\Documents\PGP\pubring.pkr");
   ks.ImportKeyRing(@"C:\Users\JohnDoe\Documents\PGP\secring.skr");

VB.NET example

   Dim ks As KeyStore = KeyStore.OpenInMemory()
 
   ks.ImportKeyRing("C:\Users\JohnDoe\Documents\PGP\pubring.pkr")
   ks.ImportKeyRing("C:\Users\JohnDoe\Documents\PGP\secring.skr")

8. Importing and Exporting keys

Please refer to the complete chapters that explain Importing and Exporting keys.

9. Change password

In order to change the password of a KeyStore we have to open it and afterward change its Password property.
(available as of version 1.7.8.8)

// C#
KeyStore ks = KeyStore.OpenFile(@"c:\my.keystore", "my password");
ks.Password = "new password";
ks.Save();
' VB.NET
Dim ks As KeyStore = KeyStore.OpenFile("c:\my.keystore", "my password")
ks.Password = "new password"
ks.Save()

Summary

This chapter discussed basic operations with the KeyStore object.

Topics not discussed here which can be of further interest are importing keysexporting keys, generating keys, deleting keys.