Getting Started with OpenPGP
Requirements:
IPWorks OpenPGP
Introduction
IPWorks OpenPGP is a comprehensive suite of components that implements the OpenPGP standard for encryption and decryption, as well as key generation and management.
This guide will focus specifically on the IPWorks OpenPGP components, which can be used to perform a combination of signing and encrypting messages as well as decrypting and verifying. If you're interested in using OpenPGP encryption in your solution, you're in the right place.
Before continuing, it is recommended to download IPWorks OpenPGP in order to follow along with this tutorial.
Contents
- Key Management
- Signing
- Verifying
- Encrypting
- Decrypting
- Sign and Encrypt
- Decrypt and Verify
- Streaming
- Code Examples
- Upgrading From Previous Versions
Generating or Importing OpenPGP Keys
The KeyMgr component can be used to perform a variety of key-related actions. You can create, delete, import, export, and manage keys. Both individual keys and keyrings can be created and used.
Keyring Management
Keyrings vs Keys
A single PGP key contains a public portion and a private portion. Single keys often come in an ASCII-armored (.asc) format. Multiple keys can be stored in a keyring. This makes it easier to store not only your own key but the keys of any of your contacts. Keyrings as a concept originate from GPG and our tools follow their standards.
Keyring Format
GPG has two formats to store multiple keys. Versions 2.0 and older use keyrings. Public keys are stored in pubring.gpg. Secret keys are stored in secring.gpg. Versions 2.1 and newer use a keybox. Public keys are stored in a .kbx file. Private keys are stored in private-keys-v1.d. By default KeyMgr will use the older style. To use the newer, .kbx format set the KeyringFormat configuration setting to 2.
keymgr1.Config("KeyringFormat=2");
Loading & Saving Keyrings
After creating a new key or set of keys, or modifying an existing keyring, call SaveKeyring. This writes any keys held in memory to disk in the current KeyringFormat.
keymgr1.SaveKeyring("C:\\keyring");
To list keys from an existing keyring or otherwise modify it, load it into memory from disk by using the LoadKeyring method.
keymgr1.LoadKeyring("C:\\keyring");
Listing Keys
The keys (public/private key pairs) in the selected keyring can be listed using the ListKeys method. The results of this method are provided through the KeyList event.
keymgr1.ListKeys();
// KeyList event handler
void keymgr1_OnKeyList(object sender, KeymgrKeyListEventArgs e)
{
lstKeys.Items.Add(e.UserId + " [" + e.KeyId + "]");
}
Setting the Key.UserId property to a valid UserId will effectively load that key, setting all of the other Key properties to the corresponding values for that key. This can be used to view detailed information or perform operations on a specific key.
Generating Keys
The CreateKey method can be used to create a new OpenPGP key pair by passing in the UserId and Passphrase for the new key.
keymgr1.CreateKey("newKeyUserId", "passphrase");
Note that the UserId format is as follows:
FirstName LastName (Comment) <Email>
Not all values are required when selecting or generating a key, but at least FirstName or Email are required.
Importing Keys
The ImportKey method can be used to import a key (with the specified UserId) from a key file into the current keyring. If the UserId parameter is set to "*" or "", all keys in the specified key file will be imported.
keymgr1.ImportKey("C:\\temp\\keyfile.key", "")
Exporting Keys
To export a specific key, it will first need to be selected within the keyring. As mentioned above, this can be done by setting the Key.UserId property. After this is done, the ExportPublicKey and ExportSecretKey methods can be used to export the public and secret key values to a specified file, respectively.
keymgr1.LoadKeyring("C:\\keyring");
keymgr1.Key.UserId = "UserId";
keymgr1.ExportPublicKey("C:\\temp\\publicKey.asc", true);
keymgr1.ExportSecretKey("C:\\temp\\secretKey.asc", true);
Signing
The OpenPGP component's Sign method can be used to sign a given message using the secret key specified in the Keys collection.
openpgp1.Keys.Add(new Key("C:\\keyring", "UserId"));
openpgp1.Keys[0].Passphrase = "passphrase";
openpgp1.InputFile = "C:\\temp\\inputData.txt";
openpgp1.OutputFile = "C:\\temp\\outputData.pgp";
openpgp1.Sign();
Verifying
The VerifySignature method can be used to do just that: verify the signature of a message. The message will be verified using the keys specified in SignerKeys. Before verification begins the component will fire the SignatureInfo event with information about the signature including the key used to sign the message. Within this event you may use the information available to load the correct key into SignerKeys.
openpgp1.SignerKeys.Add(new Key("C:\\keyring", "SignerUserId"));
openpgp1.InputFile = "C:\\temp\\signedData.pgp";
openpgp1.OutputFile = "C:\\temp\\outputData.txt";
openpgp1.VerifySignature();
By default, if the signature is not valid the component will throw an exception. The VerificationStatus event can also be checked to determine the result of the operation.
Encrypting
Encryption is performed using the recipient's public key. The OpenPGP component can be used to encrypt data by first specifying the recipient's key via RecipientKeys and calling the Encrypt method.
openpgp1.RecipientKeys.Add(new Key("C:\\keyring", "RecipientUserId"));
openpgp1.InputFile = "C:\\temp\\inputData.txt";
openpgp1.OutputFile = "C:\\temp\\outputData.pgp";
openpgp1.Encrypt();
When encrypting, you can specify which encryption and compression algorithms to use via the EncryptingAlgorithm and CompressionMethod properties, respectively. You also have the option to encode the output message using ASCII armor by setting the ASCIIArmor property to true.
Decrypting
Decryption using the component is as simple as setting the secret key via Keys and calling the Decrypt method. Before decryption begins, the component's RecipientInfo event will fire with information about the encrypted message, including the key used to encrypt the message. Within this event, you may use this information to load the correct key into Keys.
openpgp1.Keys.Add(new Key("C:\\keyring", "UserId"));
openpgp1.Keys[0].Passphrase = "passphrase";
openpgp1.InputFile = "C:\\temp\\inputData.pgp";
openpgp1.OutputFile = "C:\\temp\\outputData.txt";
openpgp1.Decrypt();
Sign and Encrypt
Signing and encrypting can be combined into one step using the SignAndEncrypt method. When called, the component will sign and subsequently encrypt the specified data. When using this, it will be necessary to specify both the recipient's key and your secret key.
openpgp1.Keys.Add(new Key("C:\\keyring", "UserId"));
openpgp1.Keys[0].Passphrase = "passphrase";
openpgp1.RecipientKeys.Add(new Key("C:\\keyring", "RecipientUserId"));
openpgp1.InputFile = "C:\\temp\\inputData.txt";
openpgp1.OutputFile = "C:\\temp\\outputData.pgp";
openpgp1.SignAndEncrypt();
Decrypt and Verify
The DecryptAndVerify method can be used to decrypt and verify a message in one step. To do this, set the signer keys via SignerKeys as well as your secret key via Keys.
openpgp1.SignerKeys.Add(new Key("C:\\keyring", "SignerUserId"));
openpgp1.Keys.Add(new Key("C:\\keyring", "UserId"));
openpgp1.Keys[0].Passphrase = "passphrase";
openpgp1.InputFile = "C:\\temp\\inputData.pgp";
openpgp1.OutputFile = "C:\\temp\\outputData.txt";
openpgp1.DecryptAndVerifySignature();
Streaming
Java and .NET
Streaming to and from files is supported in the Java and .NET editions of the toolkit via the SetInputStream and SetOutputStream methods. Simply specify the corresponding streams to use for input and/or output before calling the method to complete the operation using these streams.
pgp.SetInputStream(inputStream);
pgp.SetOutputStream(outputStream);
pgp.Encrypt();
Note that by default streams specified via these methods will be closed after processing, however, there are configuration settings to instruct the component to keep these streams open. When the CloseInputStreamAfterProcess or CloseOutputStreamAfterProcess configs are set to false, the respective streams will be kept open after processing. For example:
pgp.Config("CloseInputStreamAfterProcess=false");
pgp.Config("CloseOutputStreamAfterProcess=false");
Other Languages (C++, Delphi, ActiveX, etc.)
The component can be configured to output the encrypted/decrypted data to the progress event. This can be enabled via the WriteToProgressEvent configuration setting. When used, the component will repeatedly fire the Progress event to provide output data. Inside the event check OutputMessage when the Operation parameter of the event is 2 (Write). The IsEOF parameter should be checked inside the event to determine when all output data has been provided. This allows output data to be chunked and obtained piece by piece. For example:
pgp.RecipientKeys.Add(new Key("C:\\keyring", "RecipientUserId"));
pgp.Keys.Add(new Key("C:\\keyring", "UserId") { Passphrase = "test" });
pgp.InputFile = inputFile;
FileStream fs = new FileStream(encryptedFile, FileMode.OpenOrCreate);
pgp.OnProgress += new Openpgp.OnProgressHandler(delegate(object sender, OpenpgpProgressEventArgs e) {
if (e.Operation == 2) {
fs.Write(pgp.OutputMessageB, 0, pgp.OutputMessageB.Length);
if (e.IsEOF) {
fs.Flush();
fs.Close();
}
}
});
pgp.Config("WriteToProgressEvent=true");
pgp.SignAndEncrypt();
Code Examples
Encrypting Strings
Aside from the streaming section, all of the above examples exhibit the use of OpenPGP on files using the InputFile and OutputFile properties. The InputMessage and OutputMessage properties can also be used to read and write directly using strings. When doing so, it is useful to set the ASCIIArmor property to true to ASCII armor encode the output message.
// Signing
openpgp1.Keys.Add(new Key("C:\\keyring", "UserId"));
openpgp1.Keys[0].Passphrase = "passphrase";
openpgp1.InputMessage = "This is a test message.";
openpgp1.ASCIIArmor = true;
openpgp1.Sign();
signedData = openpgp1.OutputMessage;
// Verifying
openpgp1.SignerKeys.Add(new Key("C:\\keyring", "SignerUserId"));
openpgp1.InputMessage = signedData;
openpgp1.VerifySignature();
data = openpgp1.OutputMessage;
// Encrypting
openpgp1.RecipientKeys.Add(new Key("C:\\keyring", "RecipientUserId"));
openpgp1.InputMessage = "This is a test message.";
openpgp1.ASCIIArmor = true;
openpgp1.Encrypt();
encryptedData = openpgp1.OutputMessage;
// Decrypting
openpgp1.Keys.Add(new Key("C:\\keyring", "UserId"));
openpgp1.Keys[0].Passphrase = "passphrase";
openpgp1.InputMessage = encryptedData;
openpgp1.Decrypt();
data = openpgp1.OutputMessage;
// Sign and Encrypt
openpgp1.Keys.Add(new Key("C:\\keyring", "UserId"));
openpgp1.Keys[0].Passphrase = "passphrase";
openpgp1.RecipientKeys.Add(new Key("C:\\keyring", "RecipientUserId"));
openpgp1.InputMessage = "This is a test message.";
openpgp1.ASCIIArmor = true;
openpgp1.SignAndEncrypt();
encryptedData = openpgp1.OutputMessage;
// Decrypt and Verify
openpgp1.SignerKeys.Add(new Key("C:\\keyring", "SignerUserId"));
openpgp1.Keys.Add(new Key("C:\\keyring", "UserId"));
openpgp1.Keys[0].Passphrase = "passphrase";
openpgp1.InputMessage = encryptedData;
openpgp1.DecryptAndVerifySignature();
data = openpgp1.OutputMessage;
The InputMessageB and OutputMessageB properties can be used to read and write binary data from memory.
Upgrading From Previous Versions
The following sections provide details about upgrading from version 2022 or earlier to the current version. Note that this information is only applicable to OpenPGP version 4 (default). For information on OpenPGP version 6 support please see the OpenPGP Version 6 Support article.
Key Properties
The new Version field of the Key type can be used to query the version of the currently selected key. Possible values are 4 and 6. Please see below for a simple example:
keymgr1.CreateKey("test", "test");
Console.WriteLine("Key Version: " + keymgr1.Key.Version); // Output: "Key Version: 4"
In the latest version, it is now possible to select a specific key using the key's Fingerprint or KeyId. It is typically recommended to use the key's fingerprint as the identifier instead of the key Id, as it is possible (though still unlikely) for keys to have the same key Id. For example, to select a specific key in the KeyMgr component:
keymgr1.CreateKey("user1", "test1");
string fingerprint1 = keymgr1.Key.Fingerprint;
keymgr1.CreateKey("user2", "test2");
string fingerprint2 = keymgr1.Key.Fingerprint;
// Select the first key
keymgr1.Key.Fingerprint = fingerprint1;
Console.WriteLine(keymgr1.Key.UserId); // user1
// Select the second key
keymgr1.Key.Fingerprint = fingerprint2;
Console.WriteLine(keymgr1.Key.UserId); // user2
Additionally, the default value of the KeyIdLength config has been increased from 4 to 8. This change is applicable in all events containing a KeyId parameter, as well as the KeyId field of the Key type. For OpenPGP v4 keys, the KeyId corresponds to the last 8 bytes of the key's Fingerprint.
Public Key Algorithms and Curves
In the latest version, the PublicKeyAlgorithm config now includes support for the following additional values:
- Ed25519
- Ed448
The new default value of the PublicKeyAlgorithm config is automatic. For OpenPGP v4 keys, this corresponds to a public key algorithm of EdDSA.
Additionally, the Ed448 curve has been introduced in the latest version and may be set before key creation using the Curve config. For a list of valid and default curves for each public key algorithm, please refer to the component's documentation. Please see below for a simple example.
keymgr1.OnKeyList += (o, e) => {
Console.WriteLine("Public Key Algorithm: " + e.PublicKeyAlgorithm); // Expected output: "Public Key Algorithm: EdDSA"
Console.WriteLine("Curve: " + e.Curve); // Expected output: "Curve: Ed25519"
};
keymgr1.CreateKey("test", "test");
keymgr1.ListKeys();
Subkey Algorithms and Curves
When creating a key with CreateKey, or creating a subkey directly with CreateSubkey, the SubKeyAlgorithm configuration setting can be set to specify the subkey's public key algorithm. In the latest version, the following subkey algorithms, along with the relevant subkey operations, have been introduced:
Subkey Algorithm | Supported Subkey Operation |
---|---|
Ed25519 | Sign |
Ed448 | Sign |
X25519 | Encrypt |
X448 | Encrypt |
By default, the component will choose an appropriate subkey algorithm depending on the PublicKeyAlgorithm. Additionally, the following subkey curves have been introduced in the latest version and may be set before key creation using the SubKeyCurve config.
- Ed448
- Curve448
For a list of valid curves for each subkey algorithm, please refer to the component's documentation.
AEAD and Argon2
In the latest version, the following AEAD encryption algorithms are now supported:
- AES256-OCB
- AES192-OCB
- AES128-OCB
- AES256-GCM
- AES192-GCM
- AES128-GCM
For the OpenPGP component, and other components capable of message encryption and symmetric encryption, these may be specified using the EncryptingAlgorithm property. For the KeyMgr component, these algorithms may be specified using the KeyEncryptionAlgorithm config.
For use during key derivation / symmetric encryption, we have also introduced support for the Argon2 algorithm, which may be enabled using the UseArgon2 configuration. Note that if UseArgon2 is enabled, an AEAD encryption algorithm must be specified.
Updated Configuration Settings
Along with the mentioned changes, there have been many updates to existing configuration settings, including new algorithms and default values not mentioned above.
PublicKeyLength: This config specifies the length of the public key when creating a key using the CreateKey method. Note that this config is only applicable if the PublicKeyAlgorithm is set to RSA or DSA. The default value of this config has been updated from 2048 to 3072.
SubKeyLength: This config specifies the length of the subkey when either creating a key or a subkey. This config was previously known as DSAPublicSubKeyLength, which may still be utilized. The default value of this config was previously 0, and will remain the same in the latest version. When specified as 0, the subkey length will be determined based on the PublicKeyLength. Therefore, the updates to PublicKeyLength apply to this config as well.
KeyEncryptionAlgorithm: This config specifies the encryption algorithm used when creating a key. Previously, the default value of this config was CAST5. In the latest version, the default value of this config is AES128.
PreferredHashAlgorithms: This config specifies the preferred hash algorithms for the key being created. The default value of this config has been updated from 0208090A0B0103 to 08090A0B020103. This indicates the preferred hash algorithms of the key will be: SHA256, SHA384, SHA512, SHA224, SHA1, MD5, RIPEMD160.
PreferredSymmetricAlgorithms: This config specifies the preferred symmetric encryption algorithms for the key being created. The default value of this config has been updated from 0302090807040A01 to 090807040A010203. This indicates the preferred symmetric algorithms of the key will be: AES256, AES192, AES128, BLOWFISH, TWOFISH, IDEA, 3DES, CAST5.
PublicKeySignatureHashAlgorithm: This config specifies the public key signature hash algorithm used when creating a key. The default value of this config remains the same, but the following algorithms have been introduced:
- SHA3-256
- SHA3-512
This config is applicable in the KeyMgr component, but may appear throughout various events in other components. Please refer to the documentation for additional details.
KeyBoxProtectionMode: This new config specifies the keybox protection mode used when saving a keyring. Note that this config is only applicable when the KeyringFormat config is set to 2 (GPG 2.1 and newer). Previously, this config did not exist, and the default (and only) protection mode was openpgp-s2k3-ocb-aes. With the introduction of this config, the default protection mode has been updated to openpgp-s2k3-sha1-aes-cbc.
KeyIdLength: This config specifies the length of the key Id. This config is applicable when a key's Id is reported by the component in events throughout many components. Previously, the default value of this config was 4. In the latest version, this config is 8 by default. For OpenPGP v4 keys, the key Id is the low-order 64 bits of the key's fingerprint.
VerifyClearTextSignatureWithCache: This new config determines whether the cleartext message is cached in memory when verifying a cleartext signature. By default, this config is enabled and is required to successfully verify OpenPGP v6 signatures. If it is known beforehand that the component will only verify OpenPGP v4 signatures, this config may be disabled.
We appreciate your feedback. If you have any questions, comments, or suggestions about this article please contact our support team at support@nsoftware.com.