Public Key Cryptography - Adaptive Code via C#. Agile coding with design patterns and SOLID principles (2014)

Adaptive Code via C#. Agile coding with design patterns and SOLID principles (2014)

Chapter 7. Public Key Cryptography

WARNING

Many of the recipes in this chapter are too low-level for general-purpose use. We recommend that you first try to find what you need in Chapter 9 before resorting to building solutions yourself. If you do use this chapter, please be careful, read all of our warnings, and do consider the higher-level constructs we suggest.

Public key cryptography offers a number of important advantages over traditional, or symmetric, cryptography:

Key agreement

Traditional cryptography is done with a single shared key. There are obvious limitations to that kind of cryptography, though. The biggest one is the key agreement problem: how do two parties that wish to communicate do so securely? One option is to use a more secure out-of-band medium for transport, such as telephone or postal mail. Such a solution is rarely practical, however, considering that we might want to do business securely with an online merchant we've never previously encountered. Public key cryptography can help solve the key agreement problem, although doing so is not as easy as one might hope. We touch upon this issue throughout this chapter and expand upon it in Chapter 8.

Digital signatures

Another useful service that public key cryptography can provide is digital signatures, which allow for message integrity checks without a shared secret. In a symmetric environment with message authentication codes (MACs) for message authentication, a user can determine that someone with the MAC key sent a particular message, but it isn't possible to provide third parties any assurance as to who signed a message (this ability is called non-repudiation ). That is, if Alice and Bob exchange messages using a MAC, and somehow Charlie has been given a copy of the message and the MAC key, Charlie will be able to determine only that someone who had the MAC key at some point before him generated the message. Using only symmetric cryptography, he cannot distinguish between messages created by Alice and messages created by Bob in a secure manner.

Establishing identity

A third use of public key cryptography is in authentication schemes for purposes of identity establishment (e.g., login). We'll largely skip this topic for now, coming back to it in Chapter 8.

In practice, public key cryptography is a complex field with a lot of infrastructure built around it. Using it effectively requires a trusted third party, which is usually a public key infrastructure (PKI).

TIP

This entire chapter is effective only in the context of some kind of working PKI, even if it is an ad hoc PKI. Refer to Chapter 10 for PKI basics.

In this chapter, we'll describe the fundamentals of key exchange and digital signatures at a low level. Unfortunately, this area is quite vast, and we've had to limit our discussion to the topics we believe are most relevant to the average developer. We expect that supplemental recipes for more esoteric topics will gradually become available on this book's web site, based on reader contributions.

There are certain interesting topics that we simply don't have room for in this chapter. For example, elliptic curve cryptography is a type of public key encryption that can offer security similar to that of the traditional algorithms presented in this chapter, with notable speed gains. While elliptic curve cryptography doesn't speed things up so much that you would want to use it in places where traditional public key cryptography isn't useful, it does allow you to better scale the number of simultaneous connections you can handle. While elliptic curve cryptography is a fascinating and useful area, however, it's not nearly as important as the rest of the material in this chapter, particularly considering that standards and implementations for this kind of public key cryptography have emerged only in the last few years, and that the technology isn't yet deployed on a wide scale (plus, there are intellectual property issues when using the standard).

We've also limited our examples to OpenSSL whenever it supports the topic under discussion. While we do cover Microsoft's CryptoAPI in several other chapters side by side with OpenSSL, we won't be discussing it in this chapter. CryptoAPI's support for public key cryptography is sufficiently crippled that providing solutions that use it would be incomplete to the point of providing you with little or no utility. In particular, CryptoAPI provides no means to exchange keys in any kind of recognized portable format (such as DER or PEM; see Recipe 7.16 and Recipe 7.17) and no means by which keys other than randomly generated ones can generate digital signatures. These limitations effectively rule out a large portion of public key cryptography's common uses, which make up the majority of code-related recipes in this chapter.

The code presented in this chapter should otherwise translate easily to most other functionally complete libraries. Again, in situations where this is not the case, we expect that reader contributions will eventually mend this problem.

WARNING

We expect that for most purposes, the general-purpose networking recipes provided in Chapter 9 are likely to be more applicable to the average developer. Unless you really know what you're doing, there is significant risk of needing a prosthetic foot when using this chapter.

7.1. Determining When to Use Public Key Cryptography

Problem

You want to know when to use public key cryptography as opposed to symmetric cryptography.

Solution

Use public key cryptography only for key exchange or digital signatures. Otherwise, there are a lot of disadvantages and things that can go wrong (particularly when using it for general-purpose encryption). Because public key operations are computationally expensive, limit digital signatures to authentication at connection time and when you need non-repudiation.

TIP

Whenever you use public key encryption, be sure to remember also to perform proper authentication and message integrity checking.

Discussion

Public key cryptography allows parties to communicate securely without having to establish a key through a secure channel in advance of communication, as long as a trusted third party is involved. Therein lies the first rub. Generally, if you use public key cryptography, you need to determine explicitly with whom you're communicating, and you need to check with a trusted third party in a secure manner. To do that, you will need to have identification data that is bound to your trusted third party, which you'll probably need to authenticate over some secure channel.

Figure 7-1 (A) illustrates why public key cryptography on its own does not provide secure communication. Suppose the server has a {public key, private key} pair, and the client wishes to communicate with the server. If the client hasn't already securely obtained the public key of the server, it will need to request those credentials, generally over an insecure channel (e.g., over the Internet). What is to stop an attacker from replacing the server's credentials with its own credentials?

Then, when the client tries to establish a secure connection, it could actually be talking to an attacker, who may choose to either masquerade as the server or just sit in the middle, communicating with the server on the client's behalf, as shown in Figure 7-1 (B). Such an attack is known as aman-in-the-middle attack.

A man-in-the-middle attack

Figure 7-1. A man-in-the-middle attack

Getting a server's key over an insecure channel is okay as long as there is some way of determining whether the key the client gets back is actually the right one. The most common way of establishing trust is by using a PKI, a concept we explain in Recipe 10.1.

Another issue when it comes to public key cryptography is speed. Even the fastest public key cryptography that's believed to be secure is orders of magnitude slower than traditional symmetric encryption. For example, a Pentium class machine may encrypt data using RC4 with 128-bit keys at about 11 cycles per byte (the key size isn't actually a factor in RC4's speed). The same machine can process data at only about 2,500 cycles per byte when using an optimized version of vanilla RSA and 2,048-bit keys (the decrypt speed is the limiting factor—encryption is usually about 20 times faster). True, versions of RSA based on elliptic curves can perform better, but they still don't perform well for general-purpose use.

Because public key encryption is so expensive, it is only really useful for processing small pieces of data. As a result, there are two ways in which public key cryptography is widely used: key exchange (done by encrypting a symmetric encryption key) and digital signatures (done by encrypting a hash of the data to sign; see Recipe 7.12, Recipe 7.13 and Recipe 7.15).

When using digital signatures for authentication, a valid signature on a piece of data proves that the signer has the correct secret key that corresponds to the public key we have (of course, we then need to ensure that the public key really does belong to the entity we want to authenticate). The signature also validates that the message arrived without modification. However, it's not a good idea to use digital signatures for all of our message integrity needs because it is incredibly slow. You essentially need public key cryptography to provide message integrity for a key exchange, and while you're doing that, you might as well use it to authenticate (the authentication is often free). However, once you have a symmetric key to use, you should use MACs to provide message integrity because they're far more efficient.

The only time it makes sense to use a digital signature outside the context of initial connection establishment is when there is a need for non-repudiation. That is, if you wish to be able to demonstrate that a particular user "signed" a piece of data to a third party, you must use public key-based algorithms. Symmetric key integrity checks are not sufficient for implementing non-repudiation, because anyone who has the shared secret can create valid message integrity values. There's no way to bind the output of the integrity check algorithm to a particular entity in the system. Public key cryptography allows you to demonstrate that someone who has the private key associated with a particular public key "signed" the data, and that the data hasn't changed since it was signed.

See Also

Recipe 7.12, Recipe 7.13, Recipe 7.15, Recipe 10.1

7.2. Selecting a Public Key Algorithm

Problem

You want to determine which public key algorithms you should support in your application.

Solution

RSA is a good all-around solution. There is also nothing wrong with using Diffie-Hellman for key exchange and DSA for digital signatures.

Elliptic curve cryptography can provide the same levels of security with much smaller key sizes and with faster algorithms, but this type of cryptography is not yet in widespread use.

Discussion

TIP

Be sure to see the general recommendations for using public key cryptography in Recipe 7.1.

Security-wise, there's no real reason to choose any one of the common algorithms over the others. There are also no intellectual property restrictions on any of these algorithms (though there may be on some elliptic curve variants). RSA definitely sees the most widespread use.

RSA private key operations can be made much faster than operations in other algorithms, which is a major reason it's preferred in many circumstances. Public key operations across RSA and the two other major algorithms (Diffie-Hellman and DSA) tend to be about the same speed.

When signing messages, RSA tends to be about the same speed or perhaps a bit slower than DSA, but it is about 10 times faster for verification, if implemented properly. RSA is generally much preferable for key establishment, because some protocols can minimize server load better if they're based on RSA.

Elliptic curve cryptography is appealing in terms of efficiency, but there is a practical downside in that the standard in this space (IEEE P1363) requires licensing patents from Certicom. We believe you can probably implement nonstandard yet still secure elliptic curve cryptosystems that completely avoid any patent restrictions, but we would never pursue such a thing without first obtaining legal counsel.

See Also

Recipe 7.1

7.3. Selecting Public Key Sizes

Problem

You've decided to use public key cryptography, and you need to know what size numbers you should use in your system. For example, if you want to use RSA, should you use 512-bit RSA or 4,096-bit RSA?

Solution

There's some debate on this issue. When using RSA, we recommend a 2,048-bit instantiation for general-purpose use. Certainly don't use fewer than 1,024 bits, and use that few only if you're not worried about long-term security from attackers with big budgets. For Diffie-Hellman and DSA, 1,024 bits should be sufficient. Elliptic curve systems can use far fewer bits.

Discussion

The commonly discussed " bit size" of an algorithm should be an indication of the algorithm's strength, but it measures different things for different algorithms. For example, with RSA, the bit size really refers to the bit length of a public value that is a part of the public key. It just so happens that the combined bit length of the two secret primes tends to be about the same size. With Diffie-Hellman, the bit length refers to a public value, as it does with DSA.[1] In elliptic curve cryptosystems, bit length does roughly map to key size, but there's a lot you need to understand to give an accurate depiction of exactly what is being measured (and it's not worth understanding for the sake of this discussion—"key size" will do!).

Obviously, we can't always compare numbers directly, even across public key algorithms, never mind trying to make a direct comparison to symmetric algorithms. A 256-bit AES key probably offers more security than you'll ever need, whereas the strength of a 256-bit key in a public key cryptosystem can be incredibly weak (as with vanilla RSA) or quite strong (as is believed to be the case for standard elliptic variants of RSA). Nonetheless, relative strengths in the public key world tend to be about equal for all elliptic algorithms and for all nonelliptic algorithms. That is, if you were to talk about "1,024-bit RSA" and "1,024-bit Diffie-Hellman," you'd be talking about two things that are believed to be about as strong as each other.

In addition, in the block cipher world, there's an assumption that the highly favored ciphers do their job well enough that the best practical attack won't be much better than brute force. Such an assumption seems quite reasonable because recent ciphers such as AES were developed to resist all known attacks. It's been quite a long time since cryptographers have found a new methodology for attacking block ciphers that turns into a practical attack when applied to a well-regarded algorithm with 128-bit key sizes or greater. While there are certainly no proofs, cryptographers tend to be very comfortable with the security of 128-bit AES for the long term, even if quantum computing becomes a reality.

In the public key world, the future impact of number theory and other interesting approaches such as quantum computing is a much bigger unknown. Cryptographers have a much harder time predicting how far out in time a particular key size is going to be secure. For example, in 1990, Ron Rivest, the "R" in RSA, believed that a 677-bit modulus would provide average security, and 2,017 bits would provide high security, at least through the year 2020. Ten years later, 512 bits was clearly weak, and 1,024 was the minimum size anyone was recommending (though few people have recommended anything higher until more recently, when 2,048 bits is looking like the conservative bet).

Cryptographers try to relate the bit strength of public key primitives to the key strength of symmetric key cryptosystems. That way, you can figure out what sort of protection you'd like in a symmetric world and pick public key sizes to match. Usually, the numbers you will see are guesses, but they should be as educated as possible if they come from a reputable source. Table 7-1 lists our recommendations. Note that not everyone agrees what numbers should be in each of these boxes (for example, the biggest proponents of elliptic curve cryptography will suggest larger numbers in the nonelliptic curve public key boxes). Nonetheless, these recommendations shouldn't get you into trouble, as long as you check current literature in four or five years to make sure that there haven't been any drastic changes.

Table 7-1. Recommended key strengths for public key cryptography

Desired security level

Symmetric length

"Regular" public key lengths

Elliptic curve sizes

Acceptable (probably secure 5 years out, perhaps 10)

80 bits

2048 bits (1024 bits in some cases; see below)

160 bits

Good (may even last forever)

128 bits

2048 bits

224 bits

Paranoid

192 bits

4096 bits

384 bits

Very paranoid

256 bits

8192 bits

512 bits

TIP

Remember that "acceptable" is usually good enough; cryptography is rarely the weakest link in a system!

Until recently, 1,024 bits was the public key size people were recommending. Then, in 2003, Adi Shamir (the "S" in RSA) and Eran Tromer demonstrated that a $10 million machine could be used to break RSA keys in under a year. That means 1,024-bit keys are very much on the liberal end of the spectrum. They certainly do not provide adequate secrecy if you're worried about well-funded attackers such as governments.


[1] With DSA, there is another parameter that's important to the security of the algorithm, which few people ever mention, let alone understand (though the second parameter tends not to be a worry in practice). See any good cryptography book, such as Applied Cryptography, or the Handbook of Applied Cryptography, for more information.

7.4. Manipulating Big Numbers

Problem

You need to do integer-based arithmetic on numbers that are too large to represent in 32 (or 64) bits. For example, you may need to implement a public key algorithm that isn't supported by the library you're using.

Solution

Use a preexisting library for arbitrary-precision integer math, such as the BIGNUM library that comes with OpenSSL (discussed here) or the GNU Multi-Precision (gmp) library.

Discussion

Most of the world tends to use a small set of public key primitives, and the popular libraries reflect that fact. There are a lot of interesting things you can do with public key cryptography that are in the academic literature but not in real libraries, such as a wide variety of different digital signature techniques.

If you need such a primitive and there aren't good free libraries that implement it, you may need to strike off on your own, which will generally require doing math with very large numbers.

In general, arbitrary-precision libraries work by keeping an array of words that represents the value of a number, then implementing operations on that representation in software. Math on very large numbers tends to be slow, and software implementation of such math tends to be even slower. While there are tricks that occasionally come in handy (such as using a fast Fourier transform for multiplication instead of longhand multiplication when the numbers are large enough to merit it), such libraries still tend to be slow, even though the most speed-critical parts are often implemented in hand-optimized assembly. For this reason, it's a good idea to stick with a preexisting library for arbitrary-precision arithmetic if you have general-purpose needs.

In this recipe, we'll cover the OpenSSL BIGNUM library, which supports arbitrary precision math, albeit with a very quirky interface.

Initialization and cleanup

The BIGNUM library generally lives in libcrypto , which comes with OpenSSL. Its API is defined in openssl/bn.h. This library exports the BIGNUM type. BIGNUM objects always need to be initialized before use, even if they're statically declared. For example, here's how to initialize a statically allocated BIGNUM object:

BIGNUM bn;

void BN_init(&bn);

If you're dynamically allocating a BIGNUM object, OpenSSL provides a function that allocates and initializes in one fell swoop:

BIGNUM *bn = BN_new( );

You should not use malloc( ) to allocate a BIGNUM object because you are likely to confuse the library (it may believe that your object is unallocated).

If you would like to deallocate a BIGNUM object that was allocated using BN_new( ), pass it to BN_free( ) .

In addition, for security purposes, you may wish to zero out the memory used by a BIGNUM object before you deallocate it. If so, pass it to BN_clear( ) , which explicitly overwrites all memory in use by a BIGNUM context. You can also zero and free in one operation by passing the object toBIGNUM_clear_free( ) .

void BN_free(BIGNUM *bn);

void BN_clear(BIGNUM *bn);

void BN_clear_free(BIGNUM *bn);

Some operations may require you to allocate BN_CTX objects. These objects are scratch space for temporary values. You should always create BN_CTX objects dynamically by calling BN_CTX_new( ) , which will return a dynamically allocated and initialized BN_CTX object. When you're done with aBN_CTX object, destroy it by passing it to BN_CTX_free( ) .

BN_CTX *BN_CTX_new(void);

int BN_CTX_free(BN_CTX *c);

Assigning to BIGNUM objects

Naturally, we'll want to assign numerical values to BIGNUM objects. The easiest way to do this is to copy another number. OpenSSL provides a way to allocate a new BIGNUM object and copy a second BIGNUM object all at once:

BIGNUM *BN_dup(BIGNUM *bn_to_copy);

In addition, if you already have an allocated context, you can just call BN_copy( ) , which has the following signature:

BIGNUM *BN_copy(BIGNUM *destination_bn, BIGNUM *src_bn);

This function returns destination_bn on success.

You can assign the value 0 to a BIGNUM object with the following function:

int BN_zero(BIGNUM *bn);

You can also use BN_clear( ) , which will write over the old value first.

There's a similar function for assigning the value 1:

int BN_one(BIGNUM *bn);

You can also assign any nonnegative value that fits in an unsigned long using the function BN_set_word( ) :

int BN_set_word(BIGNUM *bn, unsigned long value);

The previous three functions return 1 on success.

If you need to assign a positive number that is too large to represent as an unsigned long, you can represent it in binary as a sequence of bytes and have OpenSSL convert the binary buffer to a BIGNUM object. Note that the bytes must be in order from most significant to least significant. That is, you can't just point OpenSSL at memory containing a 64-bit long long (_ _int64 on Windows) on a little-endian machine, because the bytes will be backwards. Once your buffer is in the right format, you can use the function BN_bin2bn( ) , which has the following signature:

BIGNUM *BN_bin2bn(unsigned char *buf, int len, BIGNUM *c);

This function has the following arguments:

buf

Buffer containing the binary representation to be converted.

len

Length of the buffer in bits. It does not need to be a multiple of eight. Extra bits in the buffer will be ignored.

c

BIGNUM object to be loaded with the value from the binary representation. This may be specified as NULL, in which case a new BIGNUM object will be dynamically allocated. The new BIGNUM object will be returned if one is allocated; otherwise, the specified BIGNUM object will be returned.

None of the previously mentioned techniques allows us to represent a negative number. The simplest technique is to get the corresponding positive integer, then use the following macro that takes a pointer to a BIGNUM object and negates it (i.e., multiplies by -1):

#define BN_negate(x) ((x)->neg = (!((x)->neg)) & 1)

Getting BIGNUM objects with random values

Before you can get BIGNUM objects with random values, you need to have seeded the OpenSSL random number generator. (With newer versions of OpenSSL, the generator will be seeded for you on most platforms; see Recipe 11.9).

One common thing to want to do is generate a random prime number. The API for this is somewhat complex:

BIGNUM *BN_generate_prime(BIGNUM *ret, int num, int safe, BIGNUM *add, BIGNUM *rem,

void (*callback)(int, int, void *), void *cb_arg);

This function has the following arguments:

ret

An allocated BIGNUM object, which will also be returned on success. If it is specified as NULL, a new BIGNUM object will be dynamically allocated and returned instead. The prime number that is generated will be stored in this object.

num

Number of bits that should be in the generated prime number.

safe

Boolean value that indicates whether a safe prime should be generated. A safe prime is a prime number for which the prime minus 1 divided by 2 is also a prime number. For Diffie-Hellman key exchange, a safe prime is required; otherwise, it usually isn't necessary.

add

If this argument is specified as non-NULL, the remainder must be the value of the rem argument when the generated prime number is divided by this number. The use of this argument is important for Diffie-Hellman key exchange.

rem

If the add argument is specified as non-NULL, this value should be the remainder when the generated prime number is divided by the value of the add argument. If this argument is specified as NULL, a value of 1 is used.

callback

Pointer to a callback function to be called during prime generation to report progress. It may be specified as NULL, in which case no progress information is reported.

cb_arg

If a callback function to monitor progress is specified, this argument is passed directly to the callback function.

Note that, depending on your hardware, it can take several seconds to generate a prime number, even if you have sufficient entropy available. The callback functionality allows you to monitor the progress of prime generation. Unfortunately, there's no way to determine how much time finding a prime will actually take, so it's not feasible to use this callback to implement a progress meter. We do not discuss the callback mechanism any further in this book. However, callbacks are discussed in the book Network Security with OpenSSL by John Viega, Matt Messier, and Pravir Chandra (O'Reilly & Associates) as well as in the online OpenSSL documentation.

It's much simpler to get a BIGNUM object with a random value:

int BN_rand_range(BIGNUM *result, BIGNUM *range);

This function requires you to pass in a pointer to an initialized BIGNUM object that receives the random value. The possible values for the random number are zero through one less than the specified range.

Additionally, you can ask for a random number with a specific number of bits:

int BN_rand(BIGNUM *result, int bits, int top, int bottom);

This function has the following arguments:

result

The generated random number will be stored in this BIGNUM object.

bits

Number of bits that the generated random number should contain.

top

If the value of this argument is 0, the most significant bit in the generated random number will be set. If it is -1, the most significant bit can be anything. If it is 1, the 2 most significant bits will be set. This is useful when you want to make sure that the product of 2 numbers of a particular bit length will always have exactly twice as many bits.

bottom

If the value of this argument is 1, the resulting random number will be odd. Otherwise, it may be either odd or even.

Outputting BIGNUM objects

If you wish to represent your BIGNUM object as a binary number, you can use BN_bn2bin( ) , which will store the binary representation of the BIGNUM object in the buffer pointed to by the outbuf argument:

int BN_bn2bin(BIGNUM *bn, unsigned char *outbuf);

Unfortunately, you first need to know in advance how big the output buffer needs to be. You can learn this by calling BN_num_bytes( ) , which has the following signature:

int BN_num_bytes(BIGNUM *bn);

WARNING

BN_bn2bin( ) will not output the sign of a number. You can manually query the sign of the number by using the following macro:

#define BN_is_negative(x) ((x)->neg)

The following is a wrapper that converts a BIGNUM object to binary, allocating its result via malloc( ) and properly setting the most significant bit to 1 if the result is negative. Note that you have to pass in a pointer to an unsigned integer. That integer gets filled with the size of the returned buffer in bytes.

#include <stdlib.h>

#include <openssl/bn.h>

#define BN_is_negative(x) ((x)->neg)

unsigned char *BN_to_binary(BIGNUM *b, unsigned int *outsz) {

unsigned char *ret;

*outsz = BN_num_bytes(b);

if (BN_is_negative(b)) {

(*outsz)++;

if (!(ret = (unsigned char *)malloc(*outsz))) return 0;

BN_bn2bin(b, ret + 1);

ret[0] = 0x80;

} else {

if (!(ret = (unsigned char *)malloc(*outsz))) return 0;

BN_bn2bin(b, ret);

}

return ret;

}

WARNING

Remember that the binary format used by a BIGNUM object is big-endian, so if you wish to take the binary output and put it in an integer on a little-endian architecture (such as an Intel x86 machine), you must byte-swap each word.

If you wish to print BIGNUM objects, you can print to a FILE pointer using BN_print_fp( ). It will only print in hexadecimal format, but it does get negative numbers right:

int BN_print_fp(FILE *f, BIGNUM *bn);

Note that you have to supply your own newline if required.

You can also convert a BIGNUM object into a hexadecimal or a base-10 string using one of the following two functions:

char *BN_bn2hex(BIGNUM *bn);

char *BN_bn2dec(BIGNUM *bn);

You can then do what you like with the string, but note that when it comes time to deallocate the string, you must call OPENSSL_free( ).

Common tests on BIGNUM objects

The function BN_cmp( ) compares two BIGNUM objects, returning 0 if they're equal, 1 if the first one is larger, or -1 if the second one is larger:

int BN_cmp(BIGNUM *a, BIGNUM *b);

The function BN_ucmp( ) is the same as BN_cmp( ), except that it compares the absolute values of the two numbers:

int BN_ucmp(BIGNUM *a, BIGNUM *b);

The following functions are actually macros that test the value of a single BIGNUM object, and return 1 or 0 depending on whether the respective condition is true or false:

BN_is_zero(BIGNUM *bn);

BN_is_one(BIGNUM *bn);

BN_is_odd(BIGNUM *bn);

In addition, you might wish to test a number to see if it is prime. The API for that one is a bit complex:

int BN_is_prime(BIGNUM *bn, int numchecks, void (*callback)(int, int, void *),

BN_CTX *ctx, void *cb_arg);

int BN_is_prime_fasttest(BIGNUM *bn, int numchecks,

void (*callback)(int, int, void *), BN_CTX *ctx,

void *cb_arg);

These functions do not guarantee that the number is prime. OpenSSL uses the Rabin-Miller primality test, which is an iterative, probabilistic algorithm, where the probability that the algorithm is right increases dramatically with every iteration. The checks argument specifies how many iterations to use. We strongly recommend using the built-in constant BN_prime_checks, which makes probability of the result being wrong negligible. When using that value, the odds of the result being wrong are 1 in 280.

This function requires you to pass in a pointer to an initialized BN_CTX object, which it uses as scratch space.

Prime number testing isn't that cheap. BN_is_prime_fasttest( ) explicitly tries factoring by a bunch of small primes, which speeds things up when the value you're checking might not be prime (which is the case when you're generating a random prime).

Because testing the primality of a number can be quite expensive, OpenSSL provides a way to monitor status by using the callback and cb_arg arguments. In addition, because the primality-testing algorithm consists of performing a fixed number of iterations, this callback can be useful for implementing a status meter of some sort.

If you define the callback, it is called after each iteration. The first argument is always 1, the second is always the iteration number (starting with 0), and the third is the value of cb_arg (this can be used to identify the calling thread if multiple threads are sharing the same callback).

Math operations on BIGNUM objects

Yes, we saved the best for last. Table 7-2 lists the math operations supported by OpenSSL's BIGNUM library.

Table 7-2. Math operations supported by OpenSSL's BIGNUM library

Function

Description

Limitations

Comments

int BN_add(BIGNUM *r, BIGNUM

*a, BIGNUM *b);

r = a+b

int BN_sub(BIGNUM *r, BIGNUM

*a, BIGNUM *b);

r = a-b

r≠a and r≠b

Values may be the same, but the objects may not be.

int BN_mul(BIGNUM *r, BIGNUM

*a, BIGNUM *b, BN_CTX *ctx);

r = a×b

Use BN_lshift or BN_lshift1 instead to multiply by a known power of 2 (it's faster).

int BN_lshift1(BIGNUM *r,

BIGNUM *a);

r = a×2

Fastest way to multiply by 2.

int BN_lshift(BIGNUM *r,

BIGNUM *a, int n);

r = a×2n

Fastest way to multiply by a power of 2 where n>1.

int BN_rshift1(BIGNUM *r,

BIGNUM *a);

r = a÷2

Fastest way to divide by 2.

int BN_rshift(BIGNUM *r,

BIGNUM *a, int n);

r=a÷2n

Fastest way to divide by a power of 2 where n>1.

int BN_sqr(BIGNUM *r, BIGNUM

*a, BN_CTX *ctx);

r = a×a

Faster than BN_mul.

int BN_exp(BIGNUM

*r, BIGNUM *a, BIGNUM *p, BN_

CTX *ctx);

r = ap

r≠a, r≠p

Values may be the same, but the objects may not be.

int BN_div(BIGNUM *d, BIGNUM

*r, BIGNUM *a, BIGNUM *b, BN_

CTX *ctx);

d = a÷b

r = a mod b

d≠a, d≠b, r≠a, r≠b

Values may be the same, but the objects may not be; either d or r may be NULL.

int BN_mod(BIGNUM *r, BIGNUM

*a, BIGNUM *b, BN_CTX *ctx);

r = a mod b

r≠a, r≠b

Values may be the same, but the objects may not be.

int BN_nnmod(BIGNUM *r,

BIGNUM *a, BIGNUM *b, BN_CTX

*ctx);

r = |a mod b|

r≠a, r≠b

Values may be the same, but the objects may not be.

int BN_mod_add(BIGNUM *r,

BIGNUM *a, BIGNUM *b, BIGNUM

*m, BN_CTX *ctx);

r = |a+b mod m|

r≠a, r≠b, r≠m

Values may be the same, but the objects may not be.

int BN_mod_sub(BIGNUM *r,

BIGNUM *a, BIGNUM *b, BIGNUM

*m, BN_CTX *ctx);

r = |a-b mod m|

r≠a, r≠b, r≠m

Values may be the same, but the objects may not be.

int BN_mod_mul(BIGNUM *r,

BIGNUM *a, BIGNUM *b, BIGNUM

*m, BN_CTX *ctx);

r = |a×b mod m|

r≠a, r≠b, r≠m

Values may be the same, but the objects may not be.

int BN_mod_sqr(BIGNUM *r,

BIGNUM *a, BIGNUM *b, BIGNUM

*m, BN_CTX *ctx);

r = |a×a mod m|

r≠a, r≠m

Values may be the same, but the objects may not be.

Faster than BN_mod_mul.

int BN_mod_exp(BIGNUM *r,

BIGNUM *a, BIGNUM *p, BIGNUM

*m, BN_CTX *ctx);

r = |ap mod m|

r≠a, r≠p, r≠m

Values may be the same, but the objects may not be.

BIGNUM *BN_mod_

inverse(BIGNUM *r, BIGNUM

*a, BIGNUM *m, BN_CTX *ctx);

Returns NULL on error, such as when no modular inverse exists.

int BN_gcd(BIGNUM *r, BIGNUM

*a, BIGNUM *b, BN_CTX *ctx);

r = GCD(a,b)

Greatest common divisor.

int BN_add_word(BIGNUM *a,

BN_ULONG w);

a = a+w

int BN_sub_word(BIGNUM *a,

BN_ULONG w);

a = a-w

int BN_mul_word(BIGNUM *a,

BN_ULONG *a);

a = a×w

BN_ULONG BN_div_word(BIGNUM

*a, BN_ULONG w);

a = a÷w

Returns the remainder.

BN_ULONG BN_mod_word(BIGNUM

*a, BN_ULONG w);

return a mod w

All of the above functions that return an int return 1 on success or 0 on failure. BN_div_word( ) and BN_mod_word( ) return their result. Note that the type BN_ULONG is simply a typedef for unsigned long.

See Also

Recipe 11.9

7.5. Generating a Prime Number (Testing for Primality)

Problem

You need to generate a random prime number or test to see if a number is prime.

Solution

Use the routines provided by your arbitrary-precision math library, or generate a random odd number and use the Rabin-Miller primality test to see whether the number generated is actually prime.

Discussion

Good arbitrary-precision math libraries have functions that can automatically generate primes and determine to a near certainty whether a number is prime. In addition, these libraries should have functionality that produces "safe" primes (that is, a prime whose value minus 1 divided by 2 is also prime). You should also be able to ask for a prime that gives a particular remainder when you divide that prime by a particular number. The last two pieces of functionality are useful for generating parameters for Diffie-Hellman key exchange.

The OpenSSL functionality for generating and testing primes is discussed in Recipe 7.4.

The most common way primes are generated is by choosing a random odd number of the desired bit length from a secure pseudo-random source (we discuss pseudo-randomness in depth in Recipe 11.1). Generally, the output of the random number generator will have the first and last bits set. Setting the last bit ensures that the number is odd; no even numbers are primes. Setting the first bit ensures that the generated number really is of the desired bit length.

When generating RSA keys, people usually set the first two bits of all their potential primes. That way, if you multiply two primes of the same bit length together, they'll produce a result that's exactly twice the bit length. When people talk about the "bit length of an RSA key," they're generally talking about the size of such a product.

For determining whether a number is prime, most people use the Rabin-Miller test, which can determine primality with high probability. Every time you run the Rabin-Miller test and the test reports the number "may be prime," the actual probability of the number being prime increases dramatically. By the time you've run five iterations and have received "may be prime" every time, the odds of the random value's not being prime aren't worth worrying about.

If you are generating a prime number for use in Diffie-Hellman key exchange (i.e., a "safe" prime), you should test the extra conditions before you even check to see if the number itself is prime because doing so will speed up tests.

We provide the following code that implements Rabin-Miller on top of the OpenSSL BIGNUM library, which almost seems worthless, because if you're using OpenSSL, it already contains this test as an API function (again, see Recipe 7.4). However, the OpenSSL BIGNUM API is straightforward. It should be easy to take this code and translate it to work with whatever package you're using for arbitrary precision math.

TIP

Do note, though, that any library you use is likely already to have a function that performs this work for you.

In this code, we explicitly attempt division for the first 100 primes, although we recommend trying more primes than that. (OpenSSL itself tries 2,048, a widely recommended number.) We omit the additional primes for space reasons, but you can find a list of those primes on this book's web site. In addition, we use spc_rand( ) to get a random binary value. See Recipe 11.2 for a discussion of this function.

#include <stdlib.h>

#include <openssl/bn.h>

#define NUMBER_ITERS 5

#define NUMBER_PRIMES 100

static unsigned long primes[NUMBER_PRIMES] = {

2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53,

59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131,

137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223,

227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311,

313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409,

419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503,

509, 521, 523, 541

};

static int is_obviously_not_prime(BIGNUM *p);

static int passes_rabin_miller_once(BIGNUM *p);

static unsigned int calc_b_and_m(BIGNUM *p, BIGNUM *m);

int spc_is_probably_prime(BIGNUM *p) {

int i;

if (is_obviously_not_prime(p)) return 0;

for (i = 0; i < NUMBER_ITERS; i++)

if (!passes_rabin_miller_once(p))

return 0;

return 1;

}

BIGNUM *spc_generate_prime(int nbits) {

BIGNUM *p = BN_new( );

unsigned char binary_rep[nbits / 8];

/* This code assumes we'll only ever want to generate primes with the number of

* bits a multiple of eight!

*/

if (nbits % 8 || !p) abort( );

for (;;) {

spc_rand(binary_rep, nbits / 8);

/* Set the two most significant and the least significant bits to 1. */

binary_rep[0] |= 0xc0;

binary_rep[nbits / 8 - 1] |= 1;

/* Convert this number to its BIGNUM representation */

if (!BN_bin2bn(binary_rep, nbits / 8, p)) abort( );

/* If you're going to test for suitability as a Diffie-Hellman prime, do so

* before calling spc_is_probably_prime(p).

*/

if (spc_is_probably_prime(p)) return p;

}

}

/* Try simple division with all our small primes. This is, for each prime, if it

* evenly divides p, return 0. Note that this obviously doesn't work if we're

* checking a prime number that's in the list!

*/

static int is_obviously_not_prime(BIGNUM *p) {

int i;

for (i = 0; i < NUMBER_PRIMES; i++)

if (!BN_mod_word(p, primes[i])) return 1;

return 0;

}

static int passes_rabin_miller_once(BIGNUM *p) {

BIGNUM a, m, z, tmp;

BN_CTX *ctx;

unsigned int b, i;

/* Initialize a, m, z and tmp properly. */

BN_init(&a);

BN_init(&m);

BN_init(&z);

BN_init(&tmp);

ctx = BN_CTX_new( );

b = calc_b_and_m(p, &m);

/* a is a random number less than p: */

if (!BN_rand_range(&a, p)) abort( );

/* z = a^m mod p. */

if (!BN_mod_exp(&z, &a, &m, p, ctx)) abort( );

/* if z = 1 at the start, pass. */

if (BN_is_one(&z)) return 1;

for (i = 0; i < b; i++) {

if (BN_is_one(&z)) return 0;

/* if z = p-1, pass! */

BN_copy(&tmp, &z);

if (!BN_add_word(&tmp, 1)) abort( );

if (!BN_cmp(&tmp, p)) return 1;

/* z = z^2 mod p */

BN_mod_sqr(&tmp, &z, p, ctx);

BN_copy(&z, &tmp);

}

/* if z = p-1, pass! */

BN_copy(&tmp, &z);

if (!BN_add_word(&tmp, 1)) abort( );

if (!BN_cmp(&tmp, p)) return 1;

/* Fail! */

return 0;

}

/* b = How many times does 2 divide p - 1? This gets returned.

* m is (p-1)/(2^b).

*/

static unsigned int calc_b_and_m(BIGNUM *p, BIGNUM *x) {

unsigned int b;

if (!BN_copy(x, p)) abort( );

if (!BN_sub_word(x, 1)) abort( );

for (b = 0; !BN_is_odd(x); b++)

BN_div_word(x, 2);

return b;

}

See Also

Recipe 7.4, Recipe 11.1, Recipe 11.2

7.6. Generating an RSA Key Pair

Problem

You want to use RSA to encrypt data, and you need to generate a public key and its corresponding private key.

Solution

Use a cryptography library's built-in functionality to generate an RSA key pair. Here we'll describe the OpenSSL API. If you insist on implementing RSA yourself (generally a bad idea), see the following discussion.

Discussion

TIP

Be sure to see Recipe 7.1 and Recipe 7.2 for general-purpose guidance on using public key cryptography.

The OpenSSL library provides a function, RSA_generate_key( ) , that generates a {public key, private key} pair, which is stored in an RSA object. The signature for this function is:

RSA *RSA_generate_key(int bits, unsigned long exp, void (*cb)(int, int, void),

void *cb_arg);

This function has the following arguments:

bits

Size of the key to be generated, in bits. This must be a multiple of 16, and at a bare minimum it should be at least 1,024. 2,048 is a common value, and 4,096 is used occasionally. The more bits in the number, the more secure and the slower operations will be. We recommend 2,048 bits for general-purpose use.

exp

Fixed exponent to be used with the key pair. This value is typically 3, 17, or 65,537, and it can vary depending on the exact context in which you're using RSA. For example, public key certificates encode the public exponent within them, and it is almost universally one of these three values. These numbers are common because it's fast to multiply other numbers with these numbers, particularly in hardware. This number is stored in the RSA object, and it is used for both encryption and decryption operations.

cb

Callback function; when called, it allows for monitoring the progress of generating a prime. It is passed directly to the function's internal call to BN_generate_prime( ), as discussed in Recipe 7.4.

cb_arg

Application-specific argument that is passed directly to the callback function, if one is specified.

If you need to generate an "n-bit" key manually, you can do so as follows:

1. Choose two random primes p and q, both of length n/2, using the techniques discussed in Recipe 7.5. Ideally, both primes will have their two most significant bits set to ensure that the public key (derived from these primes) is exactly n bits long.

2. Compute n, the product of p and q. This is the public key.

3. Compute d, the inverse of the chosen exponent, modulo (p - 1) × (q - 1). This is generally done using the extended Euclidean algorithm, which is outside the scope of this book. See the Handbook of Applied Cryptography by Alfred J. Menezes, Paul C. Van Oorschot, and Scott A. Vanstone for a good discussion of the extended Euclidean algorithm.

4. Optionally, precompute some values that will significantly speed up private key operations (decryption and signing): d mod (p - 1), d mod (q - 1), and the inverse of q mod p (again using the extended Euclidean algorithm).

Here's an example, using the OpenSSL BIGNUM library, of computing all the values you need for a key, given two primes p and q:

#include <openssl/bn.h>

typedef struct {

BIGNUM *n;

unsigned long e; /* This number should generally be small. */

} RSA_PUBKEY;

typedef struct {

BIGNUM *n;

BIGNUM *d; /* The actual private key. */

/* These aren't necessary, but speed things up if used. If you do use them,

you don't need to keep n or d around. */

BIGNUM *p;

BIGNUM *q;

BIGNUM *dP, *dQ, *qInv;

} RSA_PRIVATE;

void spc_keypair_from_primes(BIGNUM *p, BIGNUM *q, unsigned long e,

RSA_PUBKEY *pubkey, RSA_PRIVATE *privkey)

{

BN_CTX *x = BN_CTX_new( );

BIGNUM p_minus_1, q_minus_1, one, tmp, bn_e;

pubkey->n = privkey->n = BN_new( );

privkey->d = BN_new( );

pubkey->e = e;

privkey->p = p;

privkey->q = q;

BN_mul(pubkey->n, p, q, x);

BN_init(&p_minus_1);

BN_init(&q_minus_1);

BN_init(&one);

BN_init(&tmp);

BN_init(&bn_e);

BN_set_word(&bn_e, e);

BN_one(&one);

BN_sub(&p_minus_1, p, &one);

BN_sub(&q_minus_1, q, &one);

BN_mul(&tmp, &p_minus_1, &q_minus_1, x);

BN_mod_inverse(privkey->d, &bn_e, &tmp, x);

/* Compute extra values. */

privkey->dP = BN_new( );

privkey->dQ = BN_new( );

privkey->qInv = BN_new( );

BN_mod(privkey->dP, privkey->d, &p_minus_1, x);

BN_mod(privkey->dQ, privkey->d, &q_minus_1, x);

BN_mod_inverse(privkey->qInv, q, p, x);

}

See Also

Recipe 7.1, Recipe 7.2, Recipe 7.5

7.7. Disentangling the Public and Private Keys in OpenSSL

Problem

You are using OpenSSL and have a filled RSA object. You wish to remove the private parts of the key, leaving only the public key, so that you can serialize the data structure and send it off to a party who should not have the private information.

Solution

Remove all elements of the structure except for n and e.

Discussion

OpenSSL lumps the private key and the public key into a single RSA structure. They do this because the information in the public key is useful to anyone with the private key. If an entity needs only the public key, you're supposed to clear out the rest of the data.

#include <openssl/rsa.h>

void remove_private_key(RSA *r) {

r->d = r->p = r->q = r->dmp1 = r->dmq1 = r->iqmp = 0;

}

Be sure to deallocate the BIGNUM objects if you're erasing the last reference to them.

Any party that has the private key should also hold on to the public key.

7.8. Converting Binary Strings to Integers for Use with RSA

Problem

You need to encode a string as a number for use with the RSA encryption algorithm.

Solution

Use the standard PKCS #1 method for converting a nonnegative integer to a string of a specified length. PKCS #1 is the RSA Security standard for encryption with the RSA encryption algorithm.[2]

Discussion

The PKCS #1 method for representing binary strings as integers is simple. You simply treat the binary representation of the string directly as the binary representation of the number, where the string is considered a list of bytes from most significant to least significant (big-endian notation).

For example, if you have the binary string "Test", you would have a number represented as a list of ASCII values. In decimal, these values are:

84, 101, 115, 116

This would map to the hexadecimal value:

0x54657374

If you simply treat the hexadecimal value as a number, you'll get the integer representation. In base 10, the previous number would be 1415934836.

If, for some reason, you need to calculate this value manually given the ASCII values of the integers, you would compute the following:

84×2563 + 101×2562 + 115×2561 + 116×2560

In the real world, your arbitrary-precision math library will probably have a way to turn binary strings into numbers that is compatible with the PKCS algorithm. For example, OpenSSL provides BN_bin2bn( ), which is discussed in Recipe 7.4.

If you need to perform this conversion yourself, make sure that your numerical representation uses either an array of char values or an array of unsigned int values. If you use the former, you can use the binary string directly as a number. If you use the latter, you will have to byte-swap each word on a little-endian machine before treating the string as a number. On a big-endian machine, you need not perform any swap.

See Also

§ PKCS #1 page: http://www.rsasecurity.com/rsalabs/pkcs/pkcs-1/

§ Recipe 7.4


[2] For the PKCS #1 specification, see http://www.rsasecurity.com/rsalabs/pkcs/pkcs-1/.

7.9. Converting Integers into Binary Strings for Use with RSA

Problem

You have a number as a result of an RSA operation that you'd like to turn into a binary string of a fixed length.

Solution

Use the inverse of the previous recipe, padding the start of the string with zero-bits, if necessary, to reach the desired output length. If the number is too big, return an error.

Discussion

In practice, you should be using a binary representation of very large integers that stores a value as an array of values of type unsigned int or type char. If you're using a little-endian machine and word-sized storage, each word will need to be byte- swapped before the value can be treated as a binary string.

Byte swapping can be done with the htonl( ) macro, which can be imported by including arpa/inet.h on Unix or winsock.h on Windows.

7.10. Performing Raw Encryption with an RSA Public Key

Problem

You want to encrypt a small message using an RSA public key so that only an entity with the corresponding private key can decrypt the message.

Solution

Your cryptographic library should have a straightforward API to the RSA encryption algorithm: you should be able to give it the public key, the data to encrypt, a buffer for the results, an indication of the data's length, and a specification as to what kind of padding to use (EME-OAEP padding is recommended).

When using OpenSSL, this can be done with the RSA_public_encrypt( ) function, defined in openssl/rsa.h.

If, for some reason, you need to implement RSA on your own (which we strongly recommend against), refer to the Public Key Cryptography Standard (PKCS) #1, Version 2.1 (the latest version).

Discussion

TIP

Be sure to read the generic considerations for public key cryptography in Recipe 7.1 and Recipe 7.2.

Conceptually, RSA encryption is very simple. A message is translated into an integer and encrypted with integer math. Given a message m written as an integer, if you want to encrypt to a public key, you take the modulus n and the exponent e from that public key. Then compute c = m e modn, where c is the ciphertext, written as an integer. Given the ciphertext, you must have the private key to recover m. The private key consists of a single integer d, which can undo the encipherment with the operation m = cd mod n.

This scheme is believed to be as "hard" as factoring a very large number. That's because n is the product of two secret primes, p and q. Given p and q, it is easy to compute d. Without those two primes, it's believed that the most practical way to decrypt messages is by factoring n to get p andq.

RSA is mathematically simple and elegant. Unfortunately, a straightforward implementation of RSA based directly on the math will usually fall prey to a number of attacks. RSA itself is secure, but only if it is deployed correctly, and that can be quite a challenge. Therefore, if you're going to use RSA (and not something high-level), we strongly recommend sticking to preexisting standards. In particular, you should use a preexisting API or, at the very worst, follow PKCS#1 recommendations for deployment.

WARNING

It's important to note that using RSA properly is predicated on your having received a known-to-be-valid public key over a secure channel (otherwise, man-in-the-middle attacks are possible; see Recipe 7.1 for a discussion of this problem). Generally, secure public key distribution is done with a PKI (see Recipe 10.1 for an introduction to PKI).

From the average API's point of view, RSA encryption is similar to standard symmetric encryption, except that there are practical limitations imposed on RSA mainly due to the fact that RSA is brutally slow compared to symmetric encryption. As a result, many libraries have two APIs for RSA encryption: one performs "raw" RSA encryption, and the other uses RSA to encrypt a temporary key, then uses that temporary key to encrypt the data you actually wanted to encrypt. Such an interface is sometimes called an enveloping interface .

As with symmetric encryption, you need to pass in relevant key material, the input buffer, and the output buffer. There will be a length associated with the input buffer, but you are probably expected to know the size of the output in advance. With OpenSSL, if you have a pointer to an RSAobject x, you can call RSA_size(x) to determine the output size of an RSA encryption, measured in bytes.

When performing raw RSA encryption, you should expect there to be a small maximum message length. Generally, the maximum message length is dependent on the type of padding that you're using.

WARNING

While RSA is believed to be secure if used properly, it is very easy not to use properly. Secure padding schemes are an incredibly important part of securely deploying RSA. Note that there's no good reason to invent your own padding format (you strongly risk messing something up, too). Instead, we recommend EME-OAEP padding (specified in PKCS #1 v2.0 or later).

There are primarily two types of padding: PKCS #1 v1.5 padding and EME-OAEP padding. The latter is specified in Version 2.0 and later of PKCS #1, and is recommended for all new applications. Use PKCS #1 v1.5 padding only for legacy systems. Do not mix padding types in a single application.

With EME-OAEP padding, the message is padded by a random value output from a cryptographic one-way hash function. There are two parameters for EME-OAEP padding: the hash function to use and an additional function used internally by the padding mechanism. The only internal function in widespread use is called MGF1 and is defined in PKCS #1 v2.0 and later. While any cryptographic one-way hash algorithm can be used with EME-OAEP padding, many implementations are hardwired to use SHA1. Generally, you should decide which hash algorithm to use based on the level of security you need overall in your application, assuming that hash functions give you half their output length in security. That is, if you're comfortable with 80 bits of security (which we believe you should be for the foreseeable future), SHA1 is sufficient. If you're feeling conservative, use SHA-256, SHA-384, or SHA-512 instead.

When using EME-OAEP padding, if k is the number of bytes in your public RSA modulus, and if h is the number of bytes output by the hash function you choose, the maximum message length you can encrypt is k - (2h + 2) bytes. For example, if you're using 2,048-bit RSA and SHA1, thenk = 2,048 / 8 and h = 20. Therefore, you can encrypt up to 214 bytes. With OpenSSL, specifying EME-OAEP padding forces the use of SHA1.

Do not use PKCS #1 v1.5 public key padding for any purpose other than encrypting session keys or hash values. This form of padding can encrypt messages up to 11 bytes smaller than the modulus size in bytes. For example, if you're using 2,048-bit RSA, you can encrypt 245-byte messages.

With OpenSSL, encryption with RSA can be done using the function RSA_public_encrypt( ) :

int RSA_public_encrypt(int l, unsigned char *pt, unsigned char *ct, RSA *r, int p);

This function has the following arguments:

l

Length of the plaintext to be encrypted.

pt

Buffer that contains the plaintext data to be encrypted.

ct

Buffer into which the resulting ciphertext data will be placed. The size of the buffer must be equal to the size in bytes of the public modulus. This value can be obtained by passing the RSA object to RSA_size( ).

r

RSA object containing the public key to be used to encrypt the plaintext data. The public modulus (n) and the public exponent (e) must be filled in, but everything else may be absent.

p

Type of padding to use.

The constants that may be used to specify the type of padding to use, as well as the prototype for RSA_public_encrypt( ) , are defined in the header file openssl/rsa.h. The defined constants are:

RSA_PKCS1_PADDING

Padding mode specified in version 1.5 of PKCS #1. This mode is in wide use, but it should only be used for compatibility. Use the EME-OAEP padding method instead.

RSA_PKCS1_OAEP_PADDING

EME-OAEP padding as specified in PKCS #1 Version 2.0 and later. It is what you should use for new applications.

RSA_SSLV23_PADDING

The SSL and TLS protocols specify a slight variant of PKCS #1 v1.5 padding. This shouldn't be used outside the context of the SSL or TLS protocols.

RSA_NO_PADDING

This mode disables padding. Do not use this mode unless you're using it to implement a known-secure padding mode.

When you're encrypting with RSA, the message you're actually trying to encrypt is represented as an integer. The binary string you pass in is converted to an integer for you, using the algorithm described in Recipe 7.8.

You can encrypt only one integer at a time with most low-level interfaces, and the OpenSSL interface is no exception. This is part of the reason there are limits to message size. In practice, you should never need a larger message size. Instead, RSA is usually used to encrypt a temporary key for a much faster encryption algorithm, or to encrypt some other small piece of data.

WARNING

If there are a small number of possible plaintext inputs to RSA encryption, the attacker can figure out which plaintext was used via a dictionary attack. Therefore, make sure that there are always a reasonable number of possible plaintexts and that all plaintexts are equally likely. Again, it is best to simply encrypt a 16-byte symmetric key.

If you forego padding (which is insecure; we discuss it just to explain how RSA works), the number you encrypt must be a value between 0 and n - 1, where n is the public modulus (the public key). Also, the value must be represented in the minimum number of bytes it takes to represent n. We recommend that you not do this unless you absolutely understand the security issues involved. For example, if you're using OpenSSL, the only reason you should ever consider implementing your own padding mechanism would be if you wanted to use EME-OAEP padding with a hash algorithm stronger than SHA1, such as SHA-256. See the PKCS #1 v2.1 document for a comprehensive implementation guide for EME-OAEP padding.

If you are using a predefined padding method, you don't have to worry about performing any padding yourself. However, you do need to worry about message length. If you try to encrypt a message that is too long, RSA_public_encrypt( ) will return 0. Again, you should be expecting to encrypt messages of no more than 32 bytes, so this should not be a problem.

See Also

§ PKCS #1 page: http://www.rsasecurity.com/rsalabs/pkcs/pkcs-1/

§ Recipe 7.1, Recipe 7.2, Recipe 7.8, Recipe 10.1

7.11. Performing Raw Decryption Using an RSA Private Key

Problem

You have a session key encrypted with an RSA public key (probably using a standard padding algorithm), and you need to decrypt the value with the corresponding RSA private key.

Solution

Your cryptographic library should have a straightforward API-to-RSA decryption algorithm: you should be able to give it the public key, the data to decrypt, a buffer for the results, and a specification as to what kind of padding was used for encryption (EME-OAEP padding is recommended; see Recipe 7.10). The size of the input message will always be equal to the bit length of RSA you're using. The API function should return the length of the result, and this length will usually be significantly smaller than the input.

If, for some reason, you need to implement RSA on your own (which we strongly recommend against), refer to the Public Key Cryptography Standard (PKCS) #1, Version 2.1 (the latest version).

Discussion

WARNING

While RSA is believed to be secure if used properly, it is very easy to use improperly. Be sure to read the Recipe on RSA encryption and the general-purpose considerations for public key encryption in Recipe 7.1 and Recipe 7.2 in addition to this one.

When using OpenSSL, decryption can be done with the RSA_private_decrypt( ) function, defined in openssl/rsa.h and shown below. It will return the length of the decrypted string, or -1 if an error occurs.

int RSA_private_decrypt(int l, unsigned char *ct, unsigned char *pt, RSA *r, int p);

This function has the following arguments:

l

Length in bytes of the ciphertext to be decrypted, which must be equal to the size in bytes of the public modulus. This value can be obtained by passing the RSA object to RSA_size( ).

ct

Buffer containing the ciphertext to be decrypted.

pt

Buffer into which the plaintext will be written. The size of this buffer must be at least RSA_size(r) bytes.

r

RSA object containing the private key to be used to decrypt the ciphertext.

p

Type of padding that was used when encrypting. The defined constants for padding types are enumerated in Recipe 7.10.

Some implementations of RSA decryption are susceptible to timing attacks. Basically, if RSA decryption operations do not happen in a fixed amount of time, such attacks may be a possibility. A technique called blinding can thwart timing attacks. The amount of time it takes to decrypt is randomized somewhat by operating on a random number in the process. To eliminate the possibility of such attacks, you should always turn blinding on before doing a decryption operation. To thwart blinding attacks in OpenSSL, you can use the RSA_blinding_on( ) function, which has the following signature:

int RSA_blinding_on(RSA *r, BN_CTX *x);

This function has the following arguments:

r

RSA object for which blinding should be enabled.

x

BN_CTX object that will be used by the blinding operations as scratch space (see Recipe 7.4 for a discussion of BN_CTX objects). It may be specified as NULL, in which case a new one will be allocated and used internally.

See Also

Recipe 7.1, Recipe 7.2, Recipe 7.4, Recipe 7.10

7.12. Signing Data Using an RSA Private Key

Problem

You want to use RSA to digitally sign data.

Solution

Use a well-known one-way hash function to compress the data, then use a digital signing technique specified in PKCS #1 v2.0 or later. Any good cryptographic library should have primitives for doing exactly this. OpenSSL provides both a low-level interface and a high-level interface, although the high-level interface doesn't end up removing any complexity.

Discussion

Digital signing with RSA is roughly equivalent to encrypting with a private key. Basically, the signer computes a message digest, then encrypts the value with his private key. The verifier also computes the digest and decrypts the signed value, comparing the two. Of course, the verifier has to have the valid public key for the entity whose signature is to be verified, which means that the public key needs to be validated by some trusted third party or transmitted over a secure medium such as a trusted courier.

Digital signing works because only the person with the correct private key will produce a "signature" that decrypts to the correct result. An attacker cannot use the public key to come up with a correct encrypted value that would authenticate properly. If that were possible, it would end up implying that the entire RSA algorithm could be broken.

PKCS #1 v2.0 specifies two different signing standards, both of which are assumed to operate on message digest values produced by standard algorithms. Basically, these standards dictate how to take a message digest value and produce a "signature." The preferred standard is RSASSA-PSS, which is analogous to RSAES-OAEP, the padding standard used for encryption. It has provable security properties and therefore is no less robust than the alternative, RSASSA-PKCS1v1.5.[3] There aren't any known problems with the RSASSA-PKCS1v1.5, however, and it is in widespread use. On the other hand, few people are currently using RSASSA-PSS. In fact, OpenSSL doesn't support RSASSA-PSS. If RSASSA-PSS is available in your cryptographic library, we recommend using it, unless you are concerned about interoperating with a legacy application. Otherwise, there is nothing wrong with RSASSA-PKCS1v1.5.

Both schemes should have a similar interface in a cryptographic library supporting RSA. That is, signing should take the following parameters:

§ The signer's private key.

§ The message to be signed. In a low-level API, instead of the actual message, you will be expected to provide a hash digest of the data you really want to be signing. High-level APIs will do the message digest operation for you.

§ An indication of which message digest algorithm was used in the signing. This may be assumed for you in a high-level API (in which case it will probably be SHA1).

RSASSA-PKCS1v1.5 encodes the message digest value into its result to avoid certain classes of attack. RSASSA-PSS does no such encoding, but it uses a hash function internally, and that function should generally be the same one used to create the digest to be signed.

You may or may not need to give an indication of the length of the input message digest. The value can be deduced easily if the API enforces that the input should be a message digest value. Similarly, the API may output the signature size, even though it is a well-known value (the same size as the public RSA modulus—for example, 2,048 bits in 2,048-bit RSA).

WARNING

OpenSSL supports RSASSA-PKCS1v1.5 only for digital signatures. It does support raw encrypting with the private key, which you can use to implement RSASSA-PSS. However, we don't generally recommend this, and you certainly should not use the raw interface (RSA_private_encrypt( )) for any other purpose whatsoever.

In OpenSSL, we recommend always using the low-level interface to RSA signing, using the function RSA_sign( ) to perform signatures when you've already calculated the appropriate hash. The signature, defined in openssl/rsa.h, is:

int RSA_sign(int md_type, unsigned char *dgst, unsigned int dlen,

unsigned char *sig, unsigned int *siglen, RSA *r);

This function has the following arguments:

md_type

OpenSSL-specific identifier for the hash function. Possible values are NID_sha1, NID_ripemd, or NID_md5. A fourth value, NID_md5_sha1, can be used to combine MD5 and SHA1 by hashing with both hash functions and concatenating the results. These four constants are defined in the header file openssl/objects.h.

dgst

Buffer containing the digest to be signed. The digest should have been generated by the algorithm specified by the md_type argument.

dlen

Length in bytes of the digest buffer. For MD5, the digest buffer should always be 16 bytes. For SHA1 and RIPEMD, it should always be 20 bytes. For the MD5 and SHA1 combination, it should always be 36 bytes.

sig

Buffer into which the generated signature will be placed.

siglen

The number of bytes written into the signature buffer will be placed in the integer pointed to by this argument. The number of bytes will always be the same size as the public modulus, which can be determined by calling RSA_size( ) with the RSA object that will be used to generate the signature.

r

RSA object to be used to generate the signature. The RSA object must contain the private key for signing.

The high-level interface to RSA signatures is certainly no less complex than computing the digest and calling RSA_sign( ) yourself. The only advantage of it is that you can minimize the amount of code you need to change if you would additionally like to support DSA signatures. If you're interested in this API, see the book Network Security with OpenSSL for more information.

Here's an example of signing an arbitrary message using OpenSSL's RSA_sign( ) function:

#include <openssl/sha.h>

#include <openssl/rsa.h>

#include <openssl/objects.h>

int spc_sign(unsigned char *msg, unsigned int mlen, unsigned char *out,

unsigned int *outlen, RSA *r) {

unsigned char hash[20];

if (!SHA1(msg, mlen, hash)) return 0;

return RSA_sign(NID_sha1, hash, 20, out, outlen, r);

}


[3] There is a known theoretical problem with RSASSA-PKCS1v1.5, but it is not practical, in that it's actually harder to attack the scheme than it is to attack the underlying message digest algorithm when using SHA1.

7.13. Verifying Signed Data Using an RSA Public Key

Problem

You have some data, an RSA digital signature of that data, and the public key that you believe corresponds to the signature. You want to determine whether the signature is valid. A successful check would demonstrate both that the data was not modified from the time it was signed (message integrity) and that the entity with the corresponding public key signed the data (authentication).

Solution

Use the verification algorithm that corresponds to the chosen signing algorithm from Recipe 7.12. Generally, this should be included with your cryptographic library.

Discussion

Recipe 7.12 explains the basic components of digital signatures with RSA. When verifying, you will generally need to provide the following inputs:

§ The signer's public key.

§ The signature to be verified.

§ The message digest corresponding to the message you want to authenticate. If it's a high-level API, you might be able to provide only the message.

§ An indication of the message digest algorithm used in the signing operation. Again, this may be assumed in a high-level API.

The API should simply return indication of success or failure.

Some implementations of RSA signature verification are susceptible to timing attacks. Basically, if RSA private key operations do not happen in a fixed amount of time, such attacks are possible. A technique called blinding can thwart timing attacks. The amount of time it takes to decrypt is randomized somewhat by operating on a random number in the process. To eliminate the possibility of such attacks, you should always turn blinding on before doing a signature validation operation.

With OpenSSL, blinding can be enabled with by calling RSA_blinding_on( ) , which has the following signature:

int RSA_blinding_on(RSA *r, BN_CTX *x);

This function has the following arguments:

r

RSA object for which blinding should be enabled.

x

BN_CTX object that will be used by the blinding operations as scratch space. (See Recipe 7.4 for a discussion of BN_CTX objects.) It may be specified as NULL, in which case a new one will be allocated and used internally.

The OpenSSL analog to RSA_sign( ) (discussed in Recipe 7.12) is RSA_verify( ), which has the following signature:

int RSA_verify(int md_type, unsigned char *dgst, unsigned int dlen,

unsigned char *sig, unsigned int siglen, RSA *r);

This function has the following arguments:

md_type

OpenSSL-specific identifier for the hash function. Possible values are NID_sha1, NID_ripemd, or NID_md5. A fourth value, NID_md5_sha1, can be used to combine MD5 and SHA1 by hashing with both hash functions and concatenating the results. These four constants are defined in the header file openssl/objects.h.

dgst

Buffer containing the digest of the data whose signature is to be verified. The digest should have been generated by the algorithm specified by the md_type argument.

dlen

Length in bytes of the digest buffer. For MD5, the digest buffer should always be 16 bytes. For SHA1 and RIPEMD, it should always be 20 bytes. For the MD5 and SHA1 combination, it should always be 36 bytes.

sig

Buffer containing the signature that is to be verified.

siglen

Number of bytes contained in the signature buffer. The number of bytes should always be the same size as the public modulus, which can be determined by calling RSA_size( ) with the RSA object that will be used to verify the signature.

r

RSA object to be used to verify the signature. The RSA object must contain the signer's public key for verification to be successful.

As we discussed in Recipe 7.12, OpenSSL RSA signatures only support PKCS #1 v1.5 and do not support RSASSA-PSS.

Here's code that implements verification on an arbitrary message, given a signature and the public RSA key of the signer:

#include <openssl/bn.h>

#include <openssl/sha.h>

#include <openssl/rsa.h>

#include <openssl/objects.h>

int spc_verify(unsigned char *msg, unsigned int mlen, unsigned char *sig,

unsigned int siglen, RSA *r) {

unsigned char hash[20];

BN_CTX *c;

int ret;

if (!(c = BN_CTX_new( ))) return 0;

if (!SHA1(msg, mlen, hash) || !RSA_blinding_on(r, c)) {

BN_CTX_free(c);

return 0;

}

ret = RSA_verify(NID_sha1, hash, 20, sig, siglen, r);

RSA_blinding_off(r);

BN_CTX_free(c);

return ret;

}

See Also

Recipe 7.4, Recipe 7.12

7.14. Securely Signing and Encrypting with RSA

Problem

You need to both sign and encrypt data using RSA.

Solution

Sign the concatenation of the public key of the message recipient and the data you actually wish to sign. Then concatenate the signature to the plaintext, and encrypt everything, in multiple messages if necessary.

Discussion

Naïve implementations where a message is both signed and encrypted with public key cryptography tend to be insecure. Simply signing data with a private key and then encrypting the data with a public key isn't secure, even if the signature is part of the data you encrypt. Such a scheme is susceptible to an attack called surreptitious forwarding . For example, suppose that there are two servers, S1 and S2. The client C signs a message and encrypts it with S1's public key. Once S1 decrypts the message, it can reencrypt it with S2's public key and make it look as if the message came from C.

In a connection-oriented protocol, it could allow a compromised S1 to replay a key transport between C and S1 to a second server S2. That is, if an attacker compromises S1, he may be able to imitate C to S2. In a document-based environment such as an electronic mail system, if Alice sends email to Bob, Bob can forward it to Charlie, making it look as if it came from Alice instead of Bob. For example, if Alice sends important corporate secrets to Bob, who also works for the company, Bob can send the secrets to the competition and make it look as if it came from Alice. When the CEO finds out, it will appear that Alice, not Bob, is responsible.

There are several strategies for fixing this problem. However, encrypting and then signing does not fix the problem. In fact, it makes the system far less secure. A secure solution to this problem is to concatenate the recipient's public key with the message, and sign that. The recipient can then easily determine that he or she was indeed the intended recipient.

One issue with this solution is how to represent the public key. The important thing is to be consistent. If your public keys are stored as X.509 certificates (see Chapter 10 for more on these), you can include the entire certificate when you sign. Otherwise, you can simply represent the public modulus and exponent as a single binary string (the DER-encoding of the X.509 certificate) and include that string when you sign.

The other issue is that RSA operations such as encryption tend to work on small messages. A digital signature of a message will often be too large to encrypt using public key encryption. Plus, you will need to encrypt your actual message as well! One way to solve this problem is to perform multiple public key encryptions. For example, let's say you have a 2,048-bit modulus, and the recipient has a 1,024-bit modulus. You will be encrypting a 16-byte secret and your signature, where that signature will be 256 bytes, for a total of 272 bytes. The output of encryption to the 1,024-bit modulus is 128 bytes, but the input can only be 86 bytes, because of the need for padding. Therefore, we'd need four encryption operations to encrypt the entire 272 bytes.

WARNING

In many client-server architectures where the client initiates a connection, the client won't have the server's public key in advance. In such a case, the server will often send a copy of its public key at its first opportunity (or a digital certificate containing the public key). In this case, the client can't assume that public key is valid; there's nothing to distinguish it from an attacker's public key! Therefore, the key needs to be validated using a trusted third party before the client trusts that the party on the other end is really the intended server. See Recipe 7.1.

Here is an example of generating, signing, and encrypting a 16-byte secret in a secure manner using OpenSSL, given a private key for signing and a public key for the recipient. The secret is placed in the buffer pointed to by the final argument, which must be 16 bytes. The encrypted result is placed in the third argument, which must be big enough to hold the modulus for the public key.

Note that we represent the public key of the recipient as the binary representation of the modulus concatenated with the binary representation of the exponent. If you are using any sort of high-level key storage format such as an X.509 certificate, it makes sense to use the canonical representation of that format instead. See Recipe 7.16 and Recipe 7.17 for information on converting common formats to a binary string.

#include <openssl/sha.h>

#include <openssl/rsa.h>

#include <openssl/objects.h>

#include <openssl/rand.h>

#include <string.h>

#define MIN(x,y) ((x) > (y) ? (y) : (x))

unsigned char *generate_and_package_128_bit_secret(RSA *recip_pub_key,

RSA *signers_key, unsigned char *sec, unsigned int *olen) {

unsigned char *tmp = 0, *to_encrypt = 0, *sig = 0, *out = 0, *p, *ptr;

unsigned int len, ignored, b_per_ct;

int bytes_remaining; /* MUST NOT BE UNSIGNED. */

unsigned char hash[20];

/* Generate the secret. */

if (!RAND_bytes(sec, 16)) return 0;

/* Now we need to sign the public key and the secret both.

* Copy the secret into tmp, then the public key and the exponent.

*/

len = 16 + RSA_size(recip_pub_key) + BN_num_bytes(recip_pub_key->e);

if (!(tmp = (unsigned char *)malloc(len))) return 0;

memcpy(tmp, sec, 16);

if (!BN_bn2bin(recip_pub_key->n, tmp + 16)) goto err;

if (!BN_bn2bin(recip_pub_key->e, tmp + 16 + RSA_size(recip_pub_key))) goto err;

/* Now sign tmp (the hash of it), again mallocing space for the signature. */

if (!(sig = (unsigned char *)malloc(BN_num_bytes(signers_key->n)))) goto err;

if (!SHA1(tmp, len, hash)) goto err;

if (!RSA_sign(NID_sha1, hash, 20, sig, &ignored, signers_key)) goto err;

/* How many bytes we can encrypt each time, limited by the modulus size

* and the padding requirements.

*/

b_per_ct = RSA_size(recip_pub_key) - (2 * 20 + 2);

if (!(to_encrypt = (unsigned char *)malloc(16 + RSA_size(signers_key))))

goto err;

/* The calculation before the mul is the number of encryptions we're

* going to make. After the mul is the output length of each

* encryption.

*/

*olen = ((16 + RSA_size(signers_key) + b_per_ct - 1) / b_per_ct) *

RSA_size(recip_pub_key);

if (!(out = (unsigned char *)malloc(*olen))) goto err;

/* Copy the data to encrypt into a single buffer. */

ptr = to_encrypt;

bytes_remaining = 16 + RSA_size(signers_key);

memcpy(to_encrypt, sec, 16);

memcpy(to_encrypt + 16, sig, RSA_size(signers_key));

p = out;

while (bytes_remaining > 0) {

/* encrypt b_per_ct bytes up until the last loop, where it may be fewer. */

if (!RSA_public_encrypt(MIN(bytes_remaining,b_per_ct), ptr, p,

recip_pub_key, RSA_PKCS1_OAEP_PADDING)) {

free(out);

out = 0;

goto err;

}

bytes_remaining -= b_per_ct;

ptr += b_per_ct;

/* Remember, output is larger than the input. */

p += RSA_size(recip_pub_key);

}

err:

if (sig) free(sig);

if (tmp) free(tmp);

if (to_encrypt) free(to_encrypt);

return out;

}

Once the message generated by this function is received on the server side, the following code will validate the signature on the message and retrieve the secret:

#include <openssl/sha.h>

#include <openssl/rsa.h>

#include <openssl/objects.h>

#include <openssl/rand.h>

#include <string.h>

#define MIN(x,y) ((x) > (y) ? (y) : (x))

/* recip_key must contain both the public and private key. */

int validate_and_retreive_secret(RSA *recip_key, RSA *signers_pub_key,

unsigned char *encr, unsigned int inlen,

unsigned char *secret) {

int result = 0;

BN_CTX *tctx;

unsigned int ctlen, stlen, i, l;

unsigned char *decrypt, *signedtext, *p, hash[20];

if (inlen % RSA_size(recip_key)) return 0;

if (!(p = decrypt = (unsigned char *)malloc(inlen))) return 0;

if (!(tctx = BN_CTX_new( ))) {

free(decrypt);

return 0;

}

RSA_blinding_on(recip_key, tctx);

for (ctlen = i = 0; i < inlen / RSA_size(recip_key); i++) {

if (!(l = RSA_private_decrypt(RSA_size(recip_key), encr, p, recip_key,

RSA_PKCS1_OAEP_PADDING))) goto err;

encr += RSA_size(recip_key);

p += l;

ctlen += l;

}

if (ctlen != 16 + RSA_size(signers_pub_key)) goto err;

stlen = 16 + BN_num_bytes(recip_key->n) + BN_num_bytes(recip_key->e);

if (!(signedtext = (unsigned char *)malloc(stlen))) goto err;

memcpy(signedtext, decrypt, 16);

if (!BN_bn2bin(recip_key->n, signedtext + 16)) goto err;

if (!BN_bn2bin(recip_key->e, signedtext + 16 + RSA_size(recip_key))) goto err;

if (!SHA1(signedtext, stlen, hash)) goto err;

if (!RSA_verify(NID_sha1, hash, 20, decrypt + 16, RSA_size(signers_pub_key),

signers_pub_key)) goto err;

memcpy(secret, decrypt, 16);

result = 1;

err:

RSA_blinding_off(recip_key);

BN_CTX_free(tctx);

free(decrypt);

if (signedtext) free(signedtext);

return result;

}

See Also

Recipe 7.1, Recipe 7.16, Recipe 7.17

7.15. Using the Digital Signature Algorithm (DSA)

Problem

You want to perform public key-based digital signatures, and you have a requirement necessitating the use of DSA.

Solution

Use an existing cryptographic library's implementation of DSA.

Discussion

DSA and Diffie-Hellman are both based on the same math problem. DSA only provides digital signatures; it does not do key agreement or general-purpose encryption. Unlike Diffie-Hellman, the construction is quite a bit more complex. For that reason, we recommend using an existing implementation. If you must implement it yourself, obtain the standard available from the NIST web site (http://www.nist.gov).

With DSA, the private key is used to sign arbitrary data. As is traditionally done with RSA signatures, the data is actually hashed before it's signed. The DSA standard mandates the use of SHA1 as the hash function.

Anyone who has the DSA public key corresponding to the key used to sign a piece of data can validate signatures. DSA signatures are most useful for authentication during key agreement and for non-repudiation. We discuss how to perform authentication during key agreement in Recipe 8.18, using Diffie-Hellman as the key agreement algorithm.

DSA requires three public parameters in addition to the public key: a very large prime number, p; a generator, g; and a prime number, q, which is a 160-bit prime factor of p - 1.[4] Unlike the generator in Diffie-Hellman, the DSA generator is not a small constant. Instead, it's a computed value derived from p, q, and a random number.

Most libraries should have a type representing a DSA public key with the same basic fields. We'll cover OpenSSL's API; other APIs should be similar.

OpenSSL defines a DSA object that can represent both the private key and the public key in one structure. Here's the interesting subset of the declaration:

typedef struct {

BIGNUM *p, *q, *g, *pub_key, *priv_key;

} DSA;

The function DSA_generate_parameters( ) will allocate a DSA object and generate a set of parameters. The new DSA object that it returns can be destroyed with the function DSA_free( ).

DSA *DSA_generate_parameters(int bits, unsigned char *seed, int seed_len,

int *counter_ret, unsigned long *h_ret,

void (*callback)(int, int, void *), void *cb_arg);

This function has the following arguments:

bits

Size in bits of the prime number to be generated. This value must be a multiple of 64. The DSA standard only allows values up to 1,024, but it's somewhat common to use larger sizes anyway, and OpenSSL supports that.

seed

Optional buffer containing a starting point for the prime number generation algorithm. It doesn't seem to speed anything up; we recommend setting it to NULL.

seed_len

If the starting point buffer is not specified as NULL, this is the length in bytes of that buffer. If the buffer is specified as NULL, this should be specified as 0.

counter_ret

Optional argument that, if not specified as NULL, will have the number of iterations the function went through to find suitable primes for p and q stored in it.

h_ret

Optional argument that, if not specified as NULL, will have the number of iterations the function went through to find a suitable generator stored in it.

callback

Pointer to a function that will be called by BN_generate_prime( ) to report status when generating the primes p and q. It may be specified as NULL, in which case no progress will be reported. See Recipe 7.4 for a discussion of BN_generate_prime( ).

cb_arg

Application-specific value that will be passed directly to the callback function for progress reporting if one is specified.

Note that DSA_generate_parameters( ) does not generate an actual key pair. Parameter sets can be reused across multiple users; key pairs cannot. An OpenSSL DSA object with the parameters set properly can be used to generate a key pair with the function DSA_generate_key( ) , which will allocate and load BIGNUM objects for the pub_key and priv_key fields. It returns 1 on success.

int DSA_generate_key(DSA *ctx);

With OpenSSL, there is an optional precomputation step to DSA signing. Basically, for each message you sign, DSA requires you to select a random value and perform some expensive math operations on that value. You can do this precomputation before there's actually data to sign, or you can wait until you have data to sign, which will slow down the signature process.

WARNING

To maintain security, the results of precomputation can only be used for a single signature. You can precompute again before the next signature, though.

DSA signature precomputation is a two-step process. First, you use DSA_sign_setup( ) , which will actually perform the precomputation of two values, kinv and r:

int DSA_sign_setup(DSA *dsa, BN_CTX *ctx, BIGNUM **kinvp, BIGNUM **rp);

This function has the following arguments:

dsa

Context object containing the parameters and the private key that will be used for signing.

ctx

Optional BN_CTX object that will be used for scratch space (see Recipe 7.4). If it is specified as NULL, DSA_sign_setup( ) will internally create its own BN_CTX object and free it before returning.

kinvp

Pointer to a BIGNUM object, which will receive the precomputed kinv value. If the BIGNUM object is specified as NULL (in other words, a pointer to NULL is specified), a new BIGNUM object will be automatically allocated. In general, it's best to let OpenSSL allocate the BIGNUM object for you.

rp

Pointer to a BIGNUM object, which will receive the precomputed r value. If the BIGNUM object is specified as NULL (in other words, a pointer to NULL is specified), a new BIGNUM object will be automatically allocated. In general, it's best to let OpenSSL allocate the BIGNUM object for you.

The two values computed by the call to DSA_sign_setup( ) must then be stored in the DSA object. DSA_sign_setup( ) does not automatically store the precomputed values in the DSA object so that a large number of precomputed values may be stored up during idle cycles and used as needed. Ideally, OpenSSL would provide an API for storing the precomputed values in a DSA object without having to directly manipulate the members of the DSA object, but it doesn't. The BIGNUM object returned as kinvp must be assigned to the kinv member of the DSA object, and the BIGNUM object returned as rp must be assigned to the r member of the DSA object. The next time a signature is generated with the DSA object, the precomputed values will be used and freed so that they're not used again.

Whether or not you've performed the precomputation step, generating a signature with OpenSSL is done in a uniform way by calling DSA_sign( ) , which maps directly to the RSA equivalent (see Recipe 7.12):

int DSA_sign(int md_type, const unsigned char *dgst, int dlen, unsigned char *sig,

unsigned int *siglen, DSA *dsa);

This function has the following arguments:

md_type

OpenSSL-specific identifier for the hash function. It is always ignored because DSA mandates the use of SHA1. For that reason, you should always specify NID_sha1, which is defined in the header file openssl/objects.h.

dgst

Buffer containing the digest to be signed. The digest should have been generated by the algorithm specified by the md_type argument, which for DSA must always be SHA1.

dlen

Length in bytes of the digest buffer. For SHA1, it should always be 20 bytes.

sig

Buffer into which the generated signature will be placed.

siglen

The number of bytes written into the signature buffer will placed in the integer pointed to by this argument. The number of bytes will always be the same size as the prime parameter q, which can be determined by calling DSA_size( ) with the DSA object that will be used to generate the signature.

dsa

DSA object to be used to generate the signature. The DSA object must contain the parameters and the private key for signing.

Here's a slightly higher-level function that wraps the DSA_sign( ) function, signing an arbitrary message:

#include <openssl/dsa.h>

#include <openssl/sha.h>

#include <openssl/objects.h>

int spc_DSA_sign(unsigned char *msg, int msglen, unsigned char *sig, DSA *dsa) {

unsigned int ignored;

unsigned char hash[20];

if (!SHA1(msg, msglen, hash)) return 0;

return DSA_sign(NID_sha1, hash, 20, sig, &ignored, dsa);

}

Verification of a signature is done with the function DSA_verify( ):

int DSA_verify(int type, unsigned char *md, int mdlen, unsigned char *sig,

int siglen, DSA *dsa);

The arguments for DSA_verify( ) are essentially the same as the arguments for DSA_sign( ). The DSA object must contain the public key of the signer, and the fourth argument, sig, must contain the signature that is to be verified. Unlike with DSA_sign( ), it actually makes sense to pass in the length of the signature because it saves the caller from having to check to see if the signature is of the proper length. Nonetheless, DSA_verify( ) could do without the first argument, and it could hash the message for you. Here's our wrapper for it:

#include <openssl/dsa.h>

#include <openssl/sha.h>

#include <openssl/objects.h>

int spc_DSA_verify(unsigned char *msg, int msglen, unsigned char *sig, int siglen,

DSA *dsa) {

unsigned char hash[20];

if (!SHA1(msg, msglen, hash)) return 0;

return DSA_verify(NID_sha1, hash, 20, sig, siglen, dsa);

}

See Also

§ NIST web site: http://www.nist.gov/

§ Recipe 7.4, Recipe 7.11, Recipe 8.18


[4] The size of q does impact security, and higher bit lengths can be useful. However, 160 bits is believed to offer good security, and the DSA standard currently does not allow for other sizes.

7.16. Representing Public Keys and Certificates in Binary (DER Encoding)

Problem

You want to represent a digital certificate or some other cryptographic primitive in a standard binary format, either for signing or for storing to disk.

Solution

There is an industry-standard way to represent cryptographic objects in binary, but it isn't very pretty at all. (You need to use this standard if you want to programmatically sign an X.509 certificate in a portable way.) We strongly recommend sticking to standard APIs for encoding and decoding instead of writing your own encoding and decoding routines.

When storing data on disk, you may want to use a password to encrypt the DER-encoded representation, as discussed in Recipe 4.10.

Discussion

ASN.1 is a language for specifying the fields a data object must contain. It's similar in purpose to XML (which it predates). Cryptographers use ASN.1 extensively for defining precise descriptions of data. For example, the definition of X.509 certificates is specified in the language. If you look at that specification, you can clearly see which parts of the certificate are optional and which are required, and see important properties of all of the fields.

ASN.1 is supposed to be a high-level specification of data. By that, we mean that there could be a large number of ways to translate ASN.1 data objects into a binary representation. That is, data may be represented however you want it to be internal to your applications, but if you want to exchange data in a standard way, you need to be able to go back and forth from your internal representation to some sort of standard representation. An ASN.1 representation can be encoded in many ways, though!

The cryptographic community uses distinguished encoding rules (DER) to specify how to map an ASN.1 specification of a data object to a binary representation. That is, if you look at the ASN.1 specification of an X.509 certificate, and you have all the data ready to go into the certificate, you can use DER and the ASN.1 specification to encode the data into an interoperable binary representation.

ASN.1 specifications of data objects can be quite complex. In particular, the specification for X.509v3 is vast because X.509v3 is a highly versatile certificate format. If you plan on reading and writing DER-encoded data on your own instead of using a cryptographic library, we recommend using an ASN.1 "compiler" that can take an ASN.1 specification as input and produce C data structures and routines that encode and parse data in a DER-encoded format. The Enhanced SNACC ASN.1 compiler is available under the GNU GPL fromhttp://www.getronicsgov.com/hot/snacc_lib.htm.

If you need to do sophisticated work with certificates, you may want to look at the freeware Certificate Management Library, available from http://www.getronicsgov.com/hot/cml_home.htm. It handles most operations you can perform on X.509 certificates, including retrieving certificates from LDAP databases.

Here, we'll show you the OpenSSL APIs for DER-encoding data objects and for converting binary data into OpenSSL data types. All of the functions in the OpenSSL API either convert OpenSSL's internal representation to a DER representation (the i2d functions) or convert DER into the internal representation (the d2i functions).

The basic i2d functions output to memory and take two arguments: the object to convert to DER and a buffer into which to write the result. The second argument is a pointer to a buffer of unsigned characters, represented as unsigned char **. That is, if you are outputting into an unsigned char *x, where x doesn't actually hold the string, but holds the address in memory where that string starts, you need to pass in the address of x.

TIP

OpenSSL requires you to pass in a pointer to a pointer because it takes your actual pointer and "advances" it. We don't like this feature and have never found it useful. In general, you should copy over the pointer to your buffer into a temporary variable, then send in the address of the temporary variable.

Note that you need to know how big a buffer to pass in as the second parameter. To figure that out, call the function with a NULL value as the second argument. That causes the function to calculate and return the size.

For example, here's how to DER-encode an RSA public key:

#include <openssl/rsa.h>

/* Returns the malloc'd buffer, and puts the size of the buffer into the integer

* pointed to by the second argument.

*/

unsigned char *DER_encode_RSA_public(RSA *rsa, int *len) {

unsigned char *buf, *next;

*len = i2d_RSAPublicKey(rsa, 0);

if (!(buf = next = (unsigned char *)malloc(*len))) return 0;

i2d_RSAPublicKey(rsa, &next); /* If we use buf here, return buf; becomes wrong */

return buf;

}

For each basic function in the i2d API, there are two additional functions—implemented as macros—that output to a FILE object or an OpenSSL BIO object, which is the library's generic IO abstraction.[5] The name of the base function is suffixed with _fp or _bio as appropriate, and the second argument changes to a FILE or a BIO pointer as appropriate.

The d2i API converts DER-encoded data to an internal OpenSSL representation. The functions in this API take three arguments. The first is a pointer to a pointer to the appropriate OpenSSL object (for example, an RSA ** instead of the expected RSA *). The second is a pointer to a pointer to the buffer storing the representation (i.e., a char ** instead of a char *). The third is the input length of the buffer (a long int). The first two arguments are pointers to pointers because OpenSSL "advances" your pointer just as it does in the i2d API.

The return value is a pointer to the object written. However, if the object cannot be decoded successfully (i.e., if there's an error in the encoded data stream), a NULL value will be returned. The first argument may be a NULL value, in which case an object of the appropriate type is allocated and returned.

Here's an example of converting an RSA public key from DER format to OpenSSL's internal representation:

#include <openssl/rsa.h>

/* Note that the pointer to the buffer gets copied in. Therefore, when

* d2i_... changes its value, those changes aren't reflected in the caller's copy

* of the pointer.

*/

RSA *DER_decode_RSA_public(unsigned char *buf, long len) {

return d2i_RSAPublicKey(0, &buf, len);

}

As with the i2d interface, all of the functions have macros that allow you to pass in a FILE or an OpenSSL BIO object, this time so that you may use one as the input source. Those macros take only two arguments, where the base function takes three. The first argument is the BIO or FILE pointer from which to read. The second argument is a pointer to a pointer to the output object (for example, an RSA **). Again, you can pass in a NULL value for this argument. The len argument is omitted; the library figures it out for itself. It could have figured it out for itself in the base API, but it requires you to pass in the length so that it may ensure that it doesn't read or write past the bounds of your buffer.

Table 7-3 lists the most prominent things you can convert to DER and back. The last two rows enumerate calls that are intended for people implementing actual infrastructure for a PKI, and they will not generally be of interest to the average developer applying cryptography.[6]

Table 7-3. Objects that can be converted to and from DER format

Kind of object

OpenSSL object type

Base encoding function

Base decoding function

Header File

RSA public key

RSA

i2d_RSAPublicKey()

d2i_RSAPublicKey()

openssl/rsa.h

RSA private key

RSA

i2d_RSAPrivateKey()

d2i_RSAPrivateKey()

openssl/rsa.h

Diffie-Hellman parameters

DH

i2d_DHparams()

d2i_DHparams()

openssl/dh.h

DSA parameters

DSA

i2d_DSAparams()

d2i_DSAparams()

openssl/dsa.h

DSA public key

DSA

i2d_DSAPublicKey()

d2i_DSAPublicKey()

openssl/dsa.h

DSA private key

DSA

i2d_DSAPrivateKey()

d2i_DSAPrivateKey()

openssl/dsa.h

X.509 certificate

X509

i2d_X509()

d2i_X509()

openssl/x509.h

X.509 CRL

X509_CRL

i2d_X509_CRL()

d2i_X509_CRL()

openssl/x509.h

PKCS #10 certificate signing request

X509_REQ

i2d_X509_REQ()

d2i_X509_REQ()

openssl/x509.h

PKCS #7 container

PKCS7

i2d_PCKS7()

d2i_PKCS7()

openssl/x509.h

See Also

§ Enhanced SNACC ASN.1 compiler: http://www.getronicsgov.com/hot/snacc_lib.htm

§ Certificate Management Library: http://www.getronicsgov.com/hot/cml_home.htm

§ Recipe 4.10


[5] There are three exceptions to this rule, having to do with the OpenSSL EVP interface. We don't discuss (or even list) the functions here, because we don't cover the OpenSSL EVP interface (it's not a very good abstraction of anything in our opinion). If you do want to look at this interface, it's covered in the book Network Security with OpenSSL.

[6] However, PKCS #7 can be used to store multiple certificates in one data object, which may be appealing to some, instead of DER-encoding multiple X.509 objects separately.

7.17. Representing Keys and Certificates in Plaintext (PEM Encoding)

Problem

You want to represent cryptographic data such as public keys or certificates in a plaintext format, so that you can use it in protocols that don't accept arbitrary binary data. This may include storing an encrypted version of a private key.

Solution

The PEM format represents DER-encoded data in a printable format. Traditionally, PEM encoding simply base64-encodes DER-encoded data and adds a simple header and footer. OpenSSL provides an API for such functionality that handles the DER encoding and header writing for you.

OpenSSL has introduced extensions for using encrypted DER representations, allowing you to use PEM to store encrypted private keys and other cryptographic data in ASCII format.

Discussion

Privacy Enhanced Mail (PEM) is the original encrypted email standard. Although the standard is long dead, a small subset of its encoding mechanism has managed to survive.

In today's day and age, PEM-encoded data is usually just DER-encoded data with a header and footer. The header is a single line consisting of five dashes followed by the word "BEGIN", followed by anything. The data following the word "BEGIN" is not really standardized. In some cases, there might not be anything following this word. However, if you are using the OpenSSL PEM outputting routines, there is a textual description of the type of data object encoded. For example, OpenSSL produces the following header line for an RSA private key:

-----BEGIN RSA PRIVATE KEY-----

This is a good convention, and one that is widely used.

The footer has the same format, except that "BEGIN" is replaced with "END". You should expect that anything could follow. Again, OpenSSL uses a textual description of the content.

In between the two lines is a base64-encoded DER representation, which may contain line breaks (\r\n, often called CRLFs for "carriage return and line feed"), which get ignored. We cover base64 in Recipe 4.5 and Recipe 4.6, and DER encoding in Recipe 7.16.

If you want to encrypt a DER object, the original PEM format supported that as well, but no one uses these extensions today. OpenSSL does implement something similar. First, we'll describe what OpenSSL does, because this will offer compatibility with applications built with OpenSSL that use this format—most notably Apache with mod_ssl. Next, we'll demonstrate how to use OpenSSL's PEM API directly.

We'll explain this format by walking through an example. Here's a PEM-encoded, encrypted RSA private key:

-----BEGIN RSA PRIVATE KEY-----

Proc-Type: 4,ENCRYPTED

DEK-Info: DES-EDE3-CBC,F2D4E6438DBD4EA8

LjKQ2r1Yt9foxbHdLKZeClqZuzN7PoEmy+b+dKq9qibaH4pRcwATuWt4/Jzl6y85

NHM6CM4bOV1MHkyD01tFsT4kJ0GwRPg4tKAiTNjE4Yrz9V3rESiQKridtXMOToEp

Mj2nSvVKRSNEeG33GNIYUeMfSSc3oTmZVOlHNp9f8LEYWNmIjfzlHExvgJaPrixX

QiPGJ6K05kV5FJWRPET9vI+kyouAm6DBcyAhmR80NYRvaBbXGM/MxBgQ7koFVaI5

zoJ/NBdEIMdHNUh0h11GQCXAQXOSL6Fx2hRdcicm6j1CPd3AFrTt9EATmd4Hj+D4

91jDYXElALfdSbiO0A9Mz6USUepTXwlfVV/cbBpLRz5Rqnyg2EwI2tZRU+E+Cusb

/b6hcuWyzva895YMUCSyDaLgSsIqRWmXxQV1W2bAgRbs8jD8VF+G9w= =

-----END RSA PRIVATE KEY-----

The first line is as discussed at the beginning of this section. Table 7-4 lists the most useful values for the data type specified in the first and last line. Other values can be found in openssl/pem.h.

Table 7-4. PEM header types

Name

Comments

RSA PUBLIC KEY

RSA PRIVATE KEY

DSA PUBLIC KEY

DSA PRIVATE KEY

DH PARAMETERS

Parameters for Diffie-Hellman key exchange

CERTIFICATE

An X.509 digital certificate

TRUSTED CERTIFICATE

A fully trusted X.509 digital certificate

CERTIFICATE REQUEST

A PKCS #10 certificate signing request

X509 CRL

An X.509 certificate revocation list

SSL SESSION PARAMETERS

The header line is followed by three lines that look like MIME headers. Do not treat them as MIME headers, though. Yes, the base64-encrypted text is separated from the header information by a line with nothing on it (two CRLFs). However, you should assume that there is no real flexibility in the headers. You should have either the two headers that are there, or nothing (and if you're not including headers, be sure to remove the blank line). In addition, the headers should be in the order shown above, and they should have the same comma-separated fields.

As far as we can determine, the second line must appear exactly as shown above for OpenSSL compatibility. There's some logic in OpenSSL to handle two other options that would add an integrity-checking value to the data being encoded, but it appears that the OpenSSL team never actually finished a full implementation, so these other options aren't used (it's left over from a time when the OpenSSL implementers were concerned about compliance with the original PEM RFCs). The first parameter on the "DEK-Info" line (where DEK stands for "data encrypting key") contains an ASCII representation of the algorithm used for encryption, which should always be a CBC-based mode. Table 7-5 lists the identifiers OpenSSL currently supports.

Table 7-5. PEM encryption algorithms supported by OpenSSL

Cipher

String

AES with 128-bit keys

AES-128-CBC

AES with 192-bit keys

AES-192-CBC

AES with 256-bit keys

AES-256-CBC

Blowfish

BF-CBC

CAST5

CAST-CBC

DES

DES-CBC

DESX

DESX

2-key Triple-DES

DES-EDE-CBC

3-key Triple-DES

DES-EDE3-CBC

IDEA

IDEA-CBC

RC2

RC2-CBC

RC5 with 128-bit keys and 12 rounds

RC5-CBC

The part of the DEK-Info field after the comma is a CBC initialization vector (which should be randomly generated), represented in uppercase hexadecimal.

The way encrypted PEM representations work in OpenSSL is as follows:

1. The data is DER-encoded.

2. The data is encrypted using a key that isn't specified anywhere (i.e., it's not placed in the headers, for obvious reasons). Usually, the user must type in a password to derive an encryption key. (See Recipe 4.10.[7]) The key-from-password functionality has the initialization vector double as a salt value, which is probably okay.

3. The encrypted data is base64-encoded.

The OpenSSL API for PEM encoding and decoding (include openssl/pem.h) only allows you to operate on FILE or OpenSSL BIO objects, which are the generic OpenSSL IO abstraction. If you need to output to memory, you can either use a memory BIO or get the DER representation and encode it by hand.

The BIO API and the FILE API are similar. The BIO API changes the name of each function in a predictable way, and the first argument to each function is a pointer to a BIO object instead of a FILE object. The object type on which you're operating is always the second argument to a PEM function when outputting PEM. When reading in data, pass in a pointer to a pointer to the encoded object. As with the DER functions described in Recipe 7.16, OpenSSL increments this pointer.

All of the PEM functions are highly regular. All the input functions and all the output functions take the same arguments and have the same signature, except that the second argument changes type based on the type of data object with which you're working. For example, the second argument to PEM_write_RSAPrivateKey( ) will be an RSA object pointer, whereas the second argument to PEM_writeDSAPrivateKey( ) will be a DSA object pointer.

We'll show you the API by demonstrating how to operate on RSA private keys. Then we'll provide a table that gives you the relevant functions for other data types.

Here's the signature for PEM_write_RSAPrivateKey( ) :

int PEM_write_RSAPrivateKey(FILE *fp, RSA *obj, EVP_CIPHER *enc,

unsigned char *kstr, int klen,

pem_password_cb callback, void *cb_arg);

This function has the following arguments:

fp

Pointer to the open file for output.

obj

RSA object that is to be PEM-encoded.

enc

Optional argument that, if not specified as NULL, is the EVP_CIPHER object for the symmetric encryption algorithm (see Recipe 5.17 for a list of possibilities) that will be used to encrypt the data before it is base64-encoded. It is a bad idea to use anything other than a CBC-based cipher.

kstr

Buffer containing the key to be used to encrypt the data. If the data is not encrypted, this argument should be specified as NULL. Even if the data is to be encrypted, this buffer may be specified as NULL, in which case the key to use will be derived from a password or passphrase.

klen

If the key buffer is not specified as NULL, this specifies the length of the buffer in bytes. If the key buffer is specified as NULL, this should be specified as 0.

callback

If the data is to be encrypted and the key buffer is specified as NULL, this specifies a pointer to a function that will be called to obtain the password or passphrase used to derive the encryption key. It may be specified as NULL, in which case OpenSSL will query the user for the password or passphrase to use.

cb_arg

If a callback function is specified to obtain the password or passphrase for key derivation, this application-specific value is passed directly to the callback function.

If encryption is desired, OpenSSL will use PKCS #5 Version 1.5 to derive an encryption key from a password. This is an earlier version of the algorithm described in Recipe 4.10.

This function will return 1 if the encoding is successful, 0 otherwise (for example, if the underlying file is not open for writing).

The type pem_password_cb is defined as follows:

typedef int (*pem_password_cb)(char *buf, int len, int rwflag, void *cb_arg);

It has the following arguments:

buf

Buffer into which the password or passphrase is to be written.

len

Length in bytes of the password or passphrase buffer.

rwflag

Indicates whether the password is to be used for encryption or decryption. For encryption (when writing out data in PEM format), the argument will be 1; otherwise, it will be 0.

cb_arg

This application-specific value is passed in from the final argument to the PEM encoding or decoding function that caused this callback to be made.

WARNING

Make sure that you do not overflow buf when writing data into it!

Your callback function is expected to return 1 if it successfully reads a password; otherwise, it should return 0.

The function for writing an RSA private key to a BIO object has the following signature, which is essentially the same as the function for writing an RSA private key to a FILE object. The only difference is that the first argument is the BIO object to write to instead of a FILE object.

int PEM_write_bio_RSAPrivateKey(BIO *bio, RSA *obj, EVP_CIPHER *enc,

unsigned char *kstr, int klen,

pem_password_cb callback, void *cbarg);

Table 7-6 lists the FILE object-based functions for the most useful PEM-encoding variants.[8] The BIO object-based functions can be derived by adding _bio_ after read or write.

Table 7-6. FILE object-based functions for PEM encoding

Kind of object

Object type

FILE object-based encoding function

FILE object-based decoding function

RSA public key

RSA

PEM_write_RSAPublicKey()

PEM_read_RSAPublicKey()

RSA private key

RSA

PEM_write_RSAPrivateKey()

PEM_read_RSAPrivateKey()

Diffie-Hellman parameters

DH

PEM_write_DHparams()

PEM_read_DHparams()

DSA parameters

DSA

PEM_write_DSAparams()

PEM_read_DSAparams()

DSA public key

DSA

PEM_write_DSA_PUBKEY()

PEM_read_DSA_PUBKEY()

DSA private key

DSA

PEM_write_DSAPrivateKey()

PEM_read_DSAPrivateKey()

X.509 certificate

X509

PEM_write_X509()

PEM_read_X509()

X.509 CRL

X509_CRL

PEM_write_X509_CRL()

PEM_read_X509_CRL()

PKCS #10 certificate signing request

X509_REQ

PEM_write_X509_REQ()

PEM_read_X509_REQ()

PKCS #7 container

PKCS7

PEM_write_PKCS7()

PEM_read_PKCS7()

The last two rows enumerate calls that are intended for people implementing actual infrastructure for a PKI, and they will not generally be of interest to the average developer applying cryptography.[9]

See Also

Recipe 4.5, Recipe 4.6, Recipe 4.10, Recipe 5.17,Recipe 7.16


[7] OpenSSL uses PKCS #5 Version 1.5 for key derivation. PKCS #5 is an earlier version of the algorithm described in Recipe 4.10. MD5 is used as the hash algorithm with an iteration count of 1. There are some differences between PKCS #5 Version 1.5 and Version 2.0. If you don't care about OpenSSL compatibility, you should definitely use Version 2.0 (the man pages even recommend it).

[8] The remainder can be found by looking for uses of the IMPLEMENT_PEM_rw macro in the OpenSSL crypto/pem source directory.

[9] PKCS #7 can be used to store multiple certificates in one data object, however, which may be appealing to some, instead of DER-encoding multiple X.509 objects separately.