Keys and Formats

From Crypto++ Wiki
Jump to: navigation, search

Keys and key formats are a popular topic on the Crypto++ mailing list. The topics range from what format is the key in, to how does one save and load a key. Topics on this page will include frequently re-occurring answers offered by folks like Geoff Beier.

Though this page discusses RSA and DSA keys in particular, the information applies equally to all Crypto++ asymmetric keys. This is due to the library's use of inheritance (for objects which derive from PublicKey and PrivateKey) and composition (for objects which derive from PublicKeyAlgorithm and PrivateKeyAlgorithm). Samples can be found with other systems - for example Elliptic Curve Integrated Encryption Scheme covers operations on ECIES keys.

In general, we should use the key's Load and Save functions to achieve maximum interoperability with other libraries such as OpenSSL, Java, and Microsoft's .Net. Load and Save operate on PKCS #8's PrivateKeyInfo and X.509's SubjectPublicKeyInfo. Note that X.509, IETF's Privacy Enhanced Mail (PEM), and PKCS #1 (RSA Encryption Standard) are identical in their definitions of public RSA keys.

If the keys will stay within the program's boundaries, we can use PKCS #8's PrivateKey (part of PrivateKeyInfo) and X.509's PublicKey (part of SubjectPublicKeyInfo). PKCS #8's PrivateKey and X.509's PublicKey save on overhead of bytes and processing time at the cost of interoperability.

ASN.1 must be used when writing keys using X.509 and PKCS #8. In particular the keys must be written using DER encoding and subsequently read using BER encoding. BER is a bit sloppier than DER - the standard's intent was to 'write strict' and 'read loose' to promote interoperability. See X.690, Specification of Basic Encoding Rules (BER), Canonical Encoding Rules (CER) and Distinguished Encoding Rules (DER).

Finally, two Code Project articles covering Crypto++, keys, and interoperability are available. See Cryptographic Interoperability: Keys and Cryptographic Interoperability: Digital Signatures.

Crypto++ Key Formats

Internally, Crypto++ uses a key representation that follows the real world object of interest. However, a raw byte dump of a Crypto++ key object is generally useless outside of the library. For example, Crypto++ does not represent a private key in PKCS #8 format with an ASN.1 encoding, so a dump is not useful for interoperability. A Crypto++ key does offer methods which allow the library to work with many popular formats, including PKCS #8 and X.509.

Private Key Format

Logical depiction of PrivateKeyInfo
The external private key format used by Crypto++ is PKCS #8: Private-Key Information Syntax Standard. The PKCS standard dictates an ASN.1 encoding of the key. ASN.1 is Abstract Syntax Notation and is specified in ITU's X.690, Specification of Basic Encoding Rules (BER), Canonical Encoding Rules (CER) and Distinguished Encoding Rules (DER).

Conceptually, there are two parts to the format. The 'first' part is packaging information such as an OID, version, and attributes. The 'second' part is the actual serialized key bits. In PKCS #8, the 'first' part is called PrivateKeyInfo, while the second part is called a PrivateKey.

In practice, we should think of the 'second ' part as XxxPrivateKey, where the 'Xxx' will be set later once the OID is known. For example, if the OID is that of rsaEncryption, XxxPrivateKey becomes RsaPrivateKey. If the OID is dsa, then XxxPrivateKey becomes DsaPrivateKey.

The depiction of the PrivateKey shows sample data 00 B7 67 B4 .... The sample data is encoded using an ASN.1 Octet String. Note that the PrivateKeyInfo encompasses the PrivateKey (they are not laid out sequentially).

Public Key Format

Logical depiction of PublicKeyInfo
The external public key format used by Crypto++ is X.509: Public-key and attribute certificate frameworks. As with PKCS #8, keys are encoded using ASN.1.

Conceptually, there are two parts to the format. The 'first' part is packaging with information such as an OID. The 'second' part is the actual serialized key bits (it sounds a lot like a PKCS #8 all over again). In X.509, the 'first' part is called PublicKeyInfo, while the second part is called a PublicKey. As with XxxPrivateKey, we should expect to nail down a key type once the OID is known so that XxxPublicKey becomes RsaPublicKey, DsaPublicKey (etc.).

The PublicKey diverges from PKCS #8 in the ASN.1 data type used to encode the PublicKey. The depiction of the PublicKey shows sample data of 10101010111.... The sample data is encoded using an ASN.1 Binary String. As with PKCS #8, the PublicKeyInfo encompasses the PublicKey (they are not laid out sequentially).

Crypto++ PKCS8PrivateKey

RSA::PrivateKey and DSA::PrivateKey are the Crypto++ objects which encapsulate much of the RSA and DSA standards keys (ie, PKCS, X.509, IEEE, and IETF). Each inherit from a PrivateKey and more importantly a PKCS8PrivateKey. The documentation for PKCS8PrivateKey class is located in the PKCS8PrivateKey Class Reference and shows the class offers Load and Save functions.

Crypto++ X509PublicKey

Similarly, RSA::PublicKey and DSA::PublicKey each inherit from a PublicKey and X509PublicKey. The documentation for the X509PublicKey class is located in the X509PublicKey Class Reference and shows the class offers Load and Save functions.

Generating, Validating, Saving, and Loading Keys

We will need a program to generate both an RSA key pair and DSA key pairs. For that, we will use cryptopp-key-gen. cryptopp-key-gen generates and saves the keys for demonstration purposes. The RSA keys can be used for encryption (see RSA Encryption Schemes) or signatures (see RSA Signature Schemes); and the DSA key are used with the Digital Signature Algorithm.

Note: RSA::PrivateKey and DSA::PrivateKey are the Crypto++ equivalent of PKCS #8 PrivateKeyInfo type. Similarly, RSA::PublicKey and DSA::PublicKey are the X.509 PublicKeyInfo type. If you want to save or load a key in PKCS #8 XxxPrivateKey format or X.509 XxxPublicKey format, see BER and DER Encoding below.

////////////////////////////////////////////////////////////////////////////////////

RSA::PrivateKey rsaPrivate;
rsaPrivate.GenerateRandomWithKeySize(rnd, 3072);

RSA::PublicKey rsaPublic(rsaPrivate);

SavePrivateKey("rsa-private.key", rsaPrivate);
SavePublicKey("rsa-public.key", rsaPublic);

////////////////////////////////////////////////////////////////////////////////////

DSA::PrivateKey dsaPrivate;
dsaPrivate.GenerateRandomWithKeySize(rnd, 1024);

DSA::PublicKey dsaPublic;
dsaPrivate.MakePublicKey(dsaPublic);

SavePrivateKey("dsa-private.key", dsaPrivate);
SavePublicKey("dsa-public.key", dsaPublic);

Generating Keys

Key generation will for RSA and DSA are very similar. RSA::PrivateKey and DSA::PrivateKey each inherit from Crypto++'s PrivateKey (documented at PrivateKey Class Reference), which offers GenerateRandom and GenerateRandomWithKeySize. Both Crypto++ methods take a RandomNumberGenerator (documented at RandomNumberGenerator Class Reference), and the later takes a generator and bit size.

When choosing a bit size, current best practices (from NIST) dictates we use a security level of 128 (80 and 112 bits of security should not be used in new systems). This means we should use an RSA modulus size of 3072 bits. This also means that a DSS version 1 or version 2 DSA key pairs are too weak to satisfy contemporary security requirements (a 1024 bit DSA key offers about 80 ideal bits of security).

In the code below, we create an RSA key of 3072 bits, and a DSA key of 1024 bits (per DSS Version 2). AutoSeededRandomPool is a PGP Random Pool-like generator. If you need more security in a generator, use an X917RNG (from ANSI 9.17 Appendix C and based on DES) or AutoSeededX917RNG based on the AES block cipher.

AutoSeededRandomPool rnd;

RSA::PrivateKey rsaPrivate;
rsaPrivate.GenerateRandomWithKeySize(rnd, 3072);

DSA::PrivateKey dsaPrivate;
dsaPrivate.GenerateRandomWithKeySize(rnd, 1024);

We create public keys from the private keys as follows. The RSA::PublicKey allows us to use the private key in the constructor. The DSA::PublicKey requires we use MakePublicKey.

RSA::PublicKey rsaPublic(rsaPrivate);

DSA::PublicKey dsaPublic;
dsaPrivate.MakePublicKey(dsaPublic);

Saving Keys

Crypto++ is full of self documenting source codeTM. If we search the sources for ".Save" (notice the period since its a method), we find at least 25 files which use the function. Of particular interest is test.cpp, which is part of cryptest.exe, and which we know will generate and save keys. Unfortunately, GenerateRSAKey writes a hex encoded key, which does not promote poking and prodding by tools such as dumpasn1.

Moving on to validate2.cpp, we find a lot of loading and saving into byte queues. A ByteQueue is a good choice for a sink since it uses a secure allocator - SecByteBlock - which ensures the key material will be zeroized when the ByteQueue's destructor runs. A sample from validate2.cpp is shown below.

ByteQueue queue;
priv.AccessKey().SavePrecomputation(queue);

Where the library uses AccessKey and then calls SavePrecomputation, we already have the key (so there's no need to call AccessKey), and we are only interested in saving the key itself (rather than the precomputation and other trap-door goodies). In keeping with the library, our function to save a public key is as follows (a private key is similar).

void SavePublicKey(const string& filename, const PublicKey& key)
{
    ByteQueue queue;
    key.Save(queue);

    Save(filename, queue);
}

SavePublicKey and SavePrivateKey take a Crypto++ PublicKey and PrivateKey and simply write the key to file. In the spirit of object orientedness, SavePublicKey and SavePrivateKey call Save. cryptopp-key-gen's use of Save is nearly identical to the library's use of Save (nearly identical because object oriented interface programming has a lot of function calls that don't actually do much of anything).

If we departed a bit from object oriented and interface programming (by yanking cryptopp-key-gen Save), we could probably do away with the calls to CopyTo and MessageEnd at the cost of duplicating functionality (heaven forbid!).

void Save(const string& filename, const BufferedTransformation& bt)
{
    FileSink file(filename.c_str());

    bt.CopyTo(file);
    file.MessageEnd();
}

Loading Keys

Though we would like to leave key loading as an exercise to the reader, its probably best if an article which discusses how to load a key were to demonstrate how to do it.

Just as both PKCS8PrivateKey and X509PublicKey offered a Save, each offers Load. To use Load, we create three thin wrappers (two of which are shown) that are the antithesis of the functions we used for Save. That is, data will flow from a FileSource, into a BufferedTransformation, and then loaded by the key.

void LoadPublicKey(const string& filename, PublicKey& key);
void Load(const string& filename, BufferedTransformation& bt)

cryptopp-key-gen's low level Load fills the caller's BufferedTransformation with the file's data:

void Load(const string& filename, BufferedTransformation& bt)
{
    FileSource file(filename.c_str(), true /*pumpAll*/);

    file.TransferTo(bt);
    bt.MessageEnd();
}

And LoadPublicKey calls load on the Crypto++ key:

void LoadPublicKey(const string& filename, PublicKey& key)
{
    ByteQueue queue;
    Load(filename, queue);

    key.Load(queue);    
}

Finally, we can verify that the key "round trips" with the following.

RSA::PrivateKey key1, key2;
key1.GenerateRandomWithKeySize(rnd, 3072);

SavePrivateKey("rsa-roundtrip.key", key1);
LoadPrivateKey("rsa-roundtrip.key", key2);

if(key1.GetModulus() != key2.GetModulus() ||
   key1.GetPublicExponent() != key2.GetPublicExponent() ||
   key1.GetPrivateExponent() != key2.GetPrivateExponent())
{
    throw runtime_error("key data did not round trip");
}

Validating Keys

A key should be validated once loaded, regardless of the library that generated it or the intended purpose since you don't know who generated the key or the tool used (the key could have a non-trivial flaw). Never apply a secret to an unvalidated key since the key may have a flaw that allows the secret's recovery. Never consume recovered data based on an unvalidated key (this includes authenticity of digital signatures). These warnings are concerned with 'bad' use cases (for example, dishonest participants); and not the intended 'good' use cases (where folks play fairly with others).

CryptoMaterial, from which PublicKey and PrivateKey inherit, offers Validate. Validate takes a RandomNumberGenerator and a level, and returns a boolean. The comments accompanying Validate in CryptoMaterial state (see the CryptoMaterial Class Reference):

Level denotes the level of thoroughness: 0 - using this object won't cause a crash or exception (rng is ignored); 1 - this object will probably function (encrypt, sign, etc.) correctly (but may not check for weak keys and such); 2 - make sure this object will function correctly, and do reasonable security checks; 3 - do checks that may take a long time.

In practice, you should require at least level two validation, and prefer level three validation. Level three is preferred because some applications cannot tolerate the extended processing. Level three validation may be required in some cases; for example, if the data is highly sensitive or the operational decision (following the decryption or verification) is critical. The code to validate a key once generated or loaded is as follows.

if(!rsaPrivate.Validate(rnd, 3))
    throw runtime_error("Rsa private key validation failed");

if(!dsaPrivate.Validate(rnd, 3))
    throw runtime_error("Dsa private key validation failed");

GnuPG ElGamal Keys

GnuPG generated ElGamal keys will fail validation at level two and higher. This is because GnuPG ElGamal keys use Lim-Lee primes, and not strong or safe primes. GnuPG's documentation on ElGamal and its keys is available at Prime-Number-Generator Subsystem Architecture. The paper that discusses the problem that motivated GnuPG and its use of Lim-Lee primes is A Key Recovery Attack on Discrete Log-based Schemes Using a Prime Order Subgroup by Chae Hoon Lim and Pil Joong Lee.

To validate the GnuPG ElGamal key, you should obtain the unique factorization of p (from Section 4 of Lim and Lee's paper: p = q p1 p2 p3 ... pn). Then ensure each pi is prime and |pi| > l. l is approximately the size of q, and q is chosen due to Schnorr's subgroup attacks. To obtain the unique factorization, you can get it from the peer or factor it. According to Werner Kock of GnuPG, the unique factorization was available in secring.gpg up to (and including) version 1.4.1. After version 1.4.1, you will have to factor the key. (Note: According to GnuPG's Release Notes, version 1.4.1 was superseded in July, 2005).

If you find you need to use GnuPG and its ElGamal schemes, it is probably easiest to generate the keys using Crypto++ (since the key will validate in Crypto++ and other libraries), and then import the key into GnuPG for use.

BER and DER Encoding

DER encoded public and private RSA keys
Recall from Private Key Format and Public Key Format that PKCS #8 and X.509 have an 'outer' PrivateKeyInfo or SubjectPublicKeyInfo. The outer structure includes information such as OID and version. Within the ASN.1 structure is the actual key material upon which an algorithm operates. For example, a RsaPublicKey would have a public exponent 'e' and modulus 'n', while a DsaPrivateKey key would have 'p', 'q', 'g', and 'x'.

Crypto++ offers functions to save and load the 'inner' key material via DEREncodePrivateKey, DEREncodePublicKey, BERDecodePrivateKey, and BERDecodePublicKey. When using the encoding function, keep in mind that you might lose interoperability with other libraries.

Writing a key uses DER encoding (most strict encoding rules), while reading a key uses BER decoding (sloppy decoding rules). The strict/sloppy in reading and writing is required by the ITU and promotes interoperability, and its in spirit with Postel's Law. Its the reason keys is saved using DER encoding rules, and loaded using BER encoding rules.

The code below is available in cryptopp-key-encode.zip.

DER Encoding

The code below demonstrates saving the key material of a RSA public key. Note that we must specifically use RSA::PrivateKey and RSA::PublicKey.

int main(int argc, char** argv)
{
    AutoSeededRandomPool rnd;

    try
    {
        RSA::PrivateKey rsaPrivate;
        rsaPrivate.GenerateRandomWithKeySize(rnd, 3072);

        RSA::PublicKey rsaPublic(rsaPrivate);

        EncodePrivateKey("rsa-private.key", rsaPrivate);
        EncodePublicKey("rsa-public.key", rsaPublic);

        cout << "Successfully generated and saved RSA keys" << endl;
    }

    catch(CryptoPP::Exception& e)
    {
        cerr << e.what() << endl;
        return -1;
    }

    return 0;
}

void EncodePrivateKey(const string& filename, const RSA::PrivateKey& key)
{
    ByteQueue queue;
    key.DEREncodePrivateKey(queue);

    Encode(filename, queue);
}

void EncodePublicKey(const string& filename, const RSA::PublicKey& key)
{
    ByteQueue queue;
    key.DEREncodePublicKey(queue);

    Encode(filename, queue);
}

void Encode(const string& filename, const BufferedTransformation& bt)
{
    FileSink file(filename.c_str());

    bt.CopyTo(file);
    file.MessageEnd();
}

BER Decoding

BER Decoding is somewhat more cumbersome to use. The generic interface of DEREncodePrivateKey and DEREncodePublicKey are:

BERDecodePrivateKey (BufferedTransformation &bt, bool parametersPresent, size_t size)
BERDecodePublicKey (BufferedTransformation &bt, bool parametersPresent, size_t size)

Examining the source code in rsa.cpp, neither parametersPresent nor size are used (parametersPresent is only needed for DL_PrivateKey_EC<EC>, which is a base class for an elliptic curve key), so we pass false. Though unused, we pass the number of bytes in the queue for size.

int main(int argc, char** argv)
{
    AutoSeededRandomPool rnd;

    try
    {
        RSA::PrivateKey k1;
        DecodePrivateKey("rsa-private.key", k1);

        RSA::PublicKey k2;
        DecodePublicKey("rsa-public.key", k2);

        cout << "Successfully loaded RSA keys" << endl;

        ////////////////////////////////////////////////////////////////////////////////////

        if(!k1.Validate(rnd, 3))
            throw runtime_error("Rsa private key validation failed");

        if(!k2.Validate(rnd, 3))
            throw runtime_error("Rsa public key validation failed");

        cout << "Successfully validated RSA keys" << endl;

        ////////////////////////////////////////////////////////////////////////////////////

        if(k1.GetModulus() != k2.GetModulus() ||
           k1.GetPublicExponent() != k2.GetPublicExponent())
        {
            throw runtime_error("key data did not round trip");
        }

        cout << "Successfully round-tripped RSA keys" << endl;
    }

    catch(CryptoPP::Exception& e)
    {
        cerr << e.what() << endl;
        return -1;
    }

    return 0;
}

void DecodePrivateKey(const string& filename, RSA::PrivateKey& key)
{
    ByteQueue queue;

    Decode(filename, queue);
    key.BERDecodePrivateKey(queue, false /*paramsPresent*/, queue.MaxRetrievable());
}

void DecodePublicKey(const string& filename, RSA::PublicKey& key)
{
    ByteQueue queue;

    Decode(filename, queue);
    key.BERDecodePublicKey(queue, false /*paramsPresent*/, queue.MaxRetrievable());
}

void Decode(const string& filename, BufferedTransformation& bt)
{
    FileSource file(filename.c_str(), true /*pumpAll*/);

    file.TransferTo(bt);
    bt.MessageEnd();
}

Hex and Base64 Encoding

A single RSA key in binary and base64 encodings
A key that is written in binary format is sometimes referred to as raw or uncooked (this applies to data in general, and not keys in particular). Sometimes it is advantageous (or required) to add some additional processing - for example, hex encoding or base64 encoding. A raw key that has been subjected to additional processing is sometimes referred to as cooked or baked.

In the accompanying image, anyone who has spent time examining dumps will recognize the binary string 0x30 0x82 ... probably indicates the file is raw ASN.1. Similarly, a person will recognize a file starting with the ASCII string MII... is probably a Base64 encoded ASN.1 file.

Below, Hexadecimal and Base64 encoding are shown. The decoding is left as an exercise to the reader.

Hex Encoding and Decoding

If we wanted to hex encode the key during a save, we would add the following signatures.

void SaveHexPrivateKey(const string& filename, const PrivateKey& key);
void SaveHexPublicKey(const string& filename, const PublicKey& key);

void SaveHex(const string& filename, const BufferedTransformation& bt);

Finally, the hex encoded implementations would be as follows.

void SaveHexPrivateKey(const string& filename, const PrivateKey& key)
{
    ByteQueue queue;
    key.Save(queue);

    SaveHex(filename, queue);
}

void SaveHexPublicKey(const string& filename, const PublicKey& key)
{
    ByteQueue queue;
    key.Save(queue);

    SaveHex(filename, queue);
}

void SaveHex(const string& filename, const BufferedTransformation& bt)
{
    HexEncoder encoder;

    bt.CopyTo(encoder);
    encoder.MessageEnd();

    Save(filename, encoder);
}

Base64 Encoding and Decoding

If we wanted to add Base64 processing to the key during a save, we would add the following signatures.

void SaveBase64PrivateKey(const string& filename, const PrivateKey& key);
void SaveBase64PublicKey(const string& filename, const PublicKey& key);

void SaveBase64(const string& filename, const BufferedTransformation& bt);

Finally, the base 64'd implementations would be as follows.

void SaveBase64PrivateKey(const string& filename, const PrivateKey& key)
{
    ByteQueue queue;
    key.Save(queue);

    SaveBase64(filename, queue);
}

void SaveBase64PublicKey(const string& filename, const PublicKey& key)
{
    ByteQueue queue;
    key.Save(queue);

    SaveBase64(filename, queue);
}

void SaveBase64(const string& filename, const BufferedTransformation& bt)
{
    Base64Encoder encoder;

    bt.CopyTo(encoder);
    encoder.MessageEnd();

    Save(filename, encoder);
}

PEM Encoded Keys

Crypto++ does not convert to/from PEM encoding. You have two choices if you need to read and write PEM encoded keys. First, you can add the PEM Pack to the library and recompile it. Second, you can use the following to convert from a PEM encoded key to plain BER/DER encoded key, and then use BERDecodePrivateKey to load the encoded key into a PubliceKey or PrivateKey structure.

string RSA_PRIV_KEY =
    "-----BEGIN RSA PRIVATE KEY-----\n"
    "MIIBOgIBAAJBAK8Q+ToR4tWGshaKYRHKJ3ZmMUF6jjwCS/u1A8v1tFbQiVpBlxYB\n"
    "paNcT2ENEXBGdmWqr8VwSl0NBIKyq4p0rhsCAQMCQHS1+3wL7I5ZzA8G62Exb6RE\n"
    "INZRtCgBh/0jV91OeDnfQUc07SE6vs31J8m7qw/rxeB3E9h6oGi9IVRebVO+9zsC\n"
    "IQDWb//KAzrSOo0P0yktnY57UF9Q3Y26rulWI6LqpsxZDwIhAND/cmlg7rUz34Pf\n"
    "SmM61lJEmMEjKp8RB/xgghzmCeI1AiEAjvVVMVd8jCcItTdwyRO0UjWU4JOz0cnw\n"
    "5BfB8cSIO18CIQCLVPbw60nOIpUClNxCJzmMLbsrbMcUtgVS6wFomVvsIwIhAK+A\n"
    "YqT6WwsMW2On5l9di+RPzhDT1QdGyTI5eFNS+GxY\n"
    "-----END RSA PRIVATE KEY-----";

static string HEADER = "-----BEGIN RSA PRIVATE KEY-----";
static string FOOTER = "-----END RSA PRIVATE KEY-----";
    
size_t pos1, pos2;
pos1 = RSA_PRIV_KEY.find(HEADER);
if(pos1 == string::npos)
    throw runtime_error("PEM header not found");
    
pos2 = RSA_PRIV_KEY.find(FOOTER, pos1+1);
if(pos2 == string::npos)
    throw runtime_error("PEM footer not found");
    
// Start position and length
pos1 = pos1 + HEADER.length();
pos2 = pos2 - pos1;
string keystr = RSA_PRIV_KEY.substr(pos1, pos2);

// Base64 decode, place in a ByteQueue    
ByteQueue queue;
Base64Decoder decoder;

decoder.Attach(new Redirector(queue));
decoder.Put((const byte*)keystr.data(), keystr.length());
decoder.MessageEnd();

// Write to file for inspection
FileSink fs("decoded-key.der");
queue.CopyTo(fs);
fs.MessageEnd();

try
{
    CryptoPP::RSA::PrivateKey rsaPrivate;
    rsaPrivate.BERDecodePrivateKey(queue, false /*paramsPresent*/, queue.MaxRetrievable());

    // BERDecodePrivateKey is a void function. Here's the only check
    // we have regarding the DER bytes consumed.
    ASSERT(queue.IsEmpty());
    
    AutoSeededRandomPool prng;
    bool valid = rsaPrivate.Validate(prng, 3);
    if(!valid)
        cerr << "RSA private key is not valid" << endl;
    
    cout << "N:" << rsaPrivate.GetModulus() << endl;
    cout << "E:" << rsaPrivate.GetPublicExponent() << endl;
    cout << "D:" << rsaPrivate.GetPrivateExponent() << endl;
    
}
catch (const Exception& ex)
{
    cerr << ex.what() << endl;
    exit (1);
}

If the key is password protected, then use one of the OpenSSL commands listed below to remove the password and convert to PKCS #8. For example:

openssl pkcs8 -nocrypt -in rsa-key.pem -inform PEM -topk8 -outform DER -out rsa-key.der

High Level Objects

It might be prudent to use Crypto++'s higher level objects, such as RSAES_PKCS1v15_Decryptor, RSAES_OAEP_SHA_Decryptor, and RSASSA_PKCS1v15_SHA_Signer to generate key pairs, rather than RSA::PrivateKey. This is because the higher level objects will have knowledge of (and corresponding logic for) domain specific issues, such as use of safe primes versus random primes. When in doubt, use the high level objects.

Decryptors and Signers

Generally speaking, Crypto++ high level objects such as RSAES_OAEP_SHA_Decryptor and RSASSA_PKCS1v15_SHA_Signer "HAVE A" PrivateKey. More correctly, decryptors inherit from PK_Decryptor and signers inherit from PK_Signer, both of which trace their lineage back to PrivateKeyAlgorithm. See PK_Decryptor Class Reference, PK_Signer Class Reference, and PrivateKeyAlgorithm Class Reference.

Encryptors and Verifiers

Similar to decryptors and signers, Crypto++ high level objects such as RSAES_OAEP_SHA_Encryptor and RSASSA_PKCS1v15_SHA_Verifier "HAVE A" PublicKey. Encryptors inherit from PK_Encryptor and verifiers inherit from PK_Verifier, both of which trace their lineage back to PublicKeyAlgorithm. See PK_Encryptor Class Reference, PK_Verifier Class Reference, and PublicKeyAlgorithm Class Reference.

RSAES_OAEP_SHA_{Encryptor|Decryptor}

The following demonstrates how to create a public and private key pair using RSAES_OAEP_SHA_Decryptor and RSAES_OAEP_SHA_Encryptor. Since the high level objects use composition ("HAS A"), we use AccessKey to fetch the key before calling methods on the key.

RSAES_OAEP_SHA_Decryptor rsaPrivate;
rsaPrivate.AccessKey().GenerateRandomWithKeySize(rnd, 3072);
RSAES_OAEP_SHA_Encryptor rsaPublic(rsaPrivate);

SavePrivateKey("rsa-private.key", rsaPrivate.AccessKey());
SavePublicKey("rsa-public.key", rsaPublic.AccessKey());

RSASSA_PKCS1v15_SHA_{Signer|Verifier}

The following shows how to create a public and private key pair using RSASSA_PKCS1v15_SHA_Signer and RSASSA_PKCS1v15_SHA_Verifier. The objects also use composition ("HAS A"), so AccessKey fetches the key before calling methods on the key.

RSASSA_PKCS1v15_SHA_Signer rsaPrivate;
rsaPrivate.AccessKey().GenerateRandomWithKeySize(rnd, 3072);
RSASSA_PKCS1v15_SHA_Verifier rsaPublic(rsaPrivate);

SavePrivateKey("rsa-private.key", rsaPrivate.AccessKey());
SavePublicKey("rsa-public.key", rsaPublic.AccessKey());

Dumping PKCS #8 and X.509 Keys

Now that we have an understanding of the key formats, its time to poke and prod the keys a bit to further our understanding. To dump the keys, Peter Guttman's dumpasn1 works well from the command line. For the visual world, Objective Systems has a free ASN.1 Viewer.

RSA PublicKey

Recall that cryptopp-key-gen saved rsa-public.key as a X.509 PublicKeyInfo type. In programming terms, here's what the PublicKeyInfo looks like (the definitions are taken directly from X.509).

PublicKeyInfo ::= SEQUENCE {
  algorithm AlgorithmIdentifier,
  PublicKey BIT STRING
}

AlgorithmIdentifier ::= SEQUENCE {
  algorithm ALGORITHM.id,
  parameters ALGORITHM.type OPTIONAL
}

X.509 specifies the format includes an inner PublicKey, but does not specify what the inner key is. Since the OID is for and RSA public key, the type is actually a RSAPublicKey. In this case, we can get the definition from X.509 or get the definition from PKCS #1 (the definitions are equivalent):

RSAPublicKey ::= SEQUENCE {
  modulus INTEGER,
  publicExponent INTEGER
}

Often times, the public exponent can act as a library indicator if the default is used: Crypto++ uses 17, GnuTLS, Java, and OpenSSL use 65537, and Microsoft's .Net uses 3. Notice the public exponent is selected with a low Hamming weight to speed up computation.

ASN.1 dump of rsa-public.key

Above is the screen capture of a rsa-public.key dump. PublicKeyInfo starts with a sequence at octet 0 and is 221 octets in length. A nested sequence starts at offset 3, which implies the first sequence is 3 octets. The second sequence houses an OID: 1.2.840.113549.1.1.1, which is a RSA key and an ASN.1 NULL (the ALGORITHM.type).

At offset 18 (with a length of 203 octets), the RSAPublicKey begins. From above we have the public modulus or n which is 00 F8 54 F4 ... D9 D2 15 CC ... DB 7C 5F 2D (dumpasn takes the -a options which shows all the data, and not just the first 128 octets of an ASN.1 type). Following n is the public exponent.

OID lookups can usually be performed at the OID Repository if the tool cannot resolve an OID. Sometimes a web search is required to find a bleeding edge RFC or other standard's amendment.

RSA PrivateKey

Dump of RSA PrivateKeyInfo using dumpasn1
Dumping an ASN.1 encoded private key is similar to dumping the public key. The format is a PrivateKeyInfo (rather than PublicKeyInfo). Its structure is as follows and includes a version and optional attributes.
PrivateKeyInfo ::= SEQUENCE {
  version Version,
  privateKeyAlgorithm PrivateKeyAlgorithmIdentifier,
  privateKey PrivateKey,
  attributes [0] IMPLICIT Attributes OPTIONAL
}

Things don't get interesting until we look at the PrivateKey encapsulated byt the PrivateKeyInfo. As with the PublicKey, since the PrivateKey has a RSA OID in the PrivateKeyInfo, the type is RSAPrivateKey.

Below is the structure which includes the familiar suspects: e, d, p, and q. The exponents are used for precomputation so the holder of the key does not need to use the e, d, n and the Chinese Remainder Theorem to recalculate useful internal values.

RSAPrivateKey ::= SEQUENCE {
  version Version,
  modulus INTEGER,
  publicExponent INTEGER,
  privateExponent INTEGER,
  prime1 INTEGER,
  prime2 INTEGER,
  exponent1 INTEGER,
  exponent2 INTEGER,
  coefficient INTEGER
}

DSA PublicKey

DSA public key

The steps to dump a DSA public key are identical to that of RSA. DSA's OID is 1.2.840.10040.4.1. DSA will include DSS domain parameters in the optional parameters.

PublicKeyInfo ::= SEQUENCE {
  algorithm AlgorithmIdentifier,
  PublicKey BIT STRING
}

AlgorithmIdentifier ::= SEQUENCE {
  algorithm ALGORITHM.id,
  parameters Dss-Parms
}

Dss-Parms ::= SEQUENCE {
  p INTEGER,
  q INTEGER,
  g INTEGER
}

The DSA public key is simply the public exponent Y encoded as a bit string:

DSAPublicKey ::= BITSTRING {
  publicExponent INTEGER
}

DSA PrivateKey

DSA private key

A DSA private key also uses the 1.2.840.10040.4.1 OID. Where a public key used a bit string (to encode its public exponent), the private key uses an octet string to encode the private exponent X:

PrivateKeyInfo ::= SEQUENCE {
  version Version,
  algorithm AlgorithmIdentifier,
  PrivateKey OCTETSTRING
}

AlgorithmIdentifier ::= SEQUENCE {
  algorithm ALGORITHM.id,
  parameters Dss-Parms
}

Dss-Parms ::= SEQUENCE {
  p INTEGER,
  q INTEGER,
  g INTEGER
}

DSAPrivateKey ::= OCTETSTRING {
  privateExponent INTEGER
}

OpenSSL Commands

The following offers OpenSSL commands for those who need to inter-operate with the OpenSSL library.

By default, Crypto++ uses DER encoding, while OpenSSL uses Privacy Enhanced Mail (PEM) encoding. PEM encoding is a Base64 encoding with a header and footer. One usually moves between DER and PEM encodings with the -inform and -outform switches.

$ openssl genrsa -out rsa-openssl.pem 256
Generating RSA private key, 256 bit long modulus
...
$ cat rsa-openssl.pem 
-----BEGIN RSA PRIVATE KEY-----
MIGrAgEAAiEA1+FPfSd+EaXYYU0LlPZ0K+k3uWFUb+s/8NxV9PEl1psCAwEAAQIg
RjBfY9W/S4WcgKZIKbqnsjYuQO5nEAGAjx/XY9za48ECEQD2EpdAXBwXDBC9NQGD
yUAJAhEA4JblIvDA6WOWr4KO79oCgwIQUfoFi3pkHUV2uiHTfFzqSQIRAJOcWamn
VwOOgwGD6/JU9YUCEQDUVTl+0TNEiA3SbsvbRya7
-----END RSA PRIVATE KEY-----

genpkey(1) can also be used.

RSA Public Key (SubjectPublicKeyInfo)

The following commands create a RSA public key in PKCS #1/X.509 format which Crypto++ can consume. Recall the format is SubjectPublicKeyInfo with an OID (1.2.840.113549.1.1.1), and the RSAPublicKey (the inner key material).

$ openssl genrsa -out rsa-openssl.pem 3072
$ openssl rsa -in rsa-openssl.pem -pubout -outform DER -out rsa-openssl.der

RSA Private Key (PrivateKeyInfo)

The following commands create an unencrypted RSA private key in PKCS #8 format which Crypto++ can consume. Recall the format is PrivateKeyInfo with an OID (1.2.840.113549.1.1.1), version, and the RSAPrivateKey (the inner key material).

$ openssl genrsa -out rsa-openssl.pem 3072
$ openssl pkcs8 -nocrypt -in rsa-openssl.pem -inform PEM -topk8 -outform DER -out rsa-openssl.der

DSA Public Key (SubjectPublicKeyInfo)

The following commands create a DSA public key in PKCS #1/X.509 format which Crypto++ can consume. Recall the format is SubjectPublicKeyInfo with an OID (1.2.840.10040.4.1), DSS parameters, and the DSAPublicKey (the inner key material).

$ openssl dsaparam -out dsa-param-openssl.pem 1024
$ openssl gendsa -out dsa-openssl.pem dsa-param-openssl.pem
$ openssl dsa -in dsa-openssl.pem -pubout -outform DER -out dsa-openssl.der

DSA Private Key (PrivateKeyInfo)

The following commands create an unencrypted DSA private key in PKCS #8 format which Crypto++ can consume. Recall the format is PrivateKeyInfo with an OID (1.2.840.10040.4.1), version, DSS parameters, and the DSAPrivateKey (the inner key material).

$ openssl dsaparam -out dsa-param-openssl.pem 1024
$ openssl gendsa -out dsa-openssl.pem dsa-param-openssl.pem
$ openssl pkcs8 -nocrypt -in dsa-openssl.pem -inform PEM -topk8 -outform DER -out dsa-openssl.der

ECC Public Key (SubjectPublicKeyInfo)

The following commands create a ECC public key in PKCS #1/X.509 format which Crypto++ can consume. The curve is SEC's prime 256.

$ openssl ecparam -param_enc explicit -name secp256k1 -genkey -outform PEM -out ec-openssl.pem
$ openssl ec -param_enc explicit -inform PEM -in ec-openssl.pem -pubout -outform DER -out ec-openssl.der

ECC Private Key (PrivateKeyInfo)

The following command creates an unencrypted ECC private key in PKCS #8 format which Crypto++ can consume. The curve is SEC's prime 256.

$ openssl ecparam -name secp256k1 -genkey -param_enc explicit -outform DER -out ec-openssl.der

GnuTLS Commands

The following GnuTLS commands should help those who use Gnu's secure transport library, GnuTLS.

$ gnutls-cli --version
gnutls-cli (GnuTLS) 2.8.5
Copyright (C) 2009 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.
...
Written by Nikos Mavrogiannopoulos.

RSA Public Key (SubjectPublicKeyInfo)

According to Nikos Mavrogiannopoulos, GnuTLS 2.11 and above can use certtool's --certificate-pubkey switch. See How to Create/Derive DER Encoded Public Keys (PKCS #1/X.509 SubjectPublicKeyInfo)?.

RSA Private Key (PrivateKeyInfo)

$ certtool --generate-privkey --pkcs8 --outder --bits 3072 --outfile rsa-gnutls.der

DSA Public Key (SubjectPublicKeyInfo)

According to Nikos Mavrogiannopoulos, GnuTLS 2.11 and above can use certtool's --certificate-pubkey switch. See How to Create/Derive DER Encoded Public Keys (PKCS #1/X.509 SubjectPublicKeyInfo)?.

DSA Private Key (PrivateKeyInfo)

$ certtool --dsa --generate-privkey --pkcs8 --outder --bits 1024 --outfile dsa-gnutls.der

Downloads

cryptopp-key-gen.zip - Generates, saves, loads, and validates keys for RSA and DSA in PKCS #8 and X.509 format.

cryptopp-key-encode.zip - Generates, encodes, and decodes RSA keys using DER encoding and BER decoding.