Chapter 8. Public Key Algorithms

In the previous chapters, we discussed almost all of the algorithms used by the SSL protocol that make it secure. The one remaining class of algorithms is public key cryptography, which is an essential element of protocols like SSL, S/MIME, and PGP.

Depending on the algorithm employed, public key cryptography is useful for key agreement, digital signing, and encryption. Three commonly used public key algorithms are supported by OpenSSL: Diffie-Hellman (DH), DSA (Digital Signature Algorithm), and RSA (so named for its inventors, Rivest, Shamir, and Adleman). It’s important to realize that these algorithms are not interchangeable. Diffie-Hellman is useful for key agreement, but cannot be used for digital signatures or encryption. DSA is useful for digital signatures, but is incapable of providing key agreement or encryption services. RSA can be used for key agreement, digital signing, and encryption.

Public key cryptography is expensive. Its strength is in the size of its keys, which are usually very large numbers. As a result, operations involving public key cryptography are slow. Most often, it is used in combination with other cryptographic algorithms such as message digests and symmetric ciphers.

Knowing when to use public key cryptography and how to combine it securely with other cryptographic algorithms is important. We’ll begin with a discussion of when it is appropriate to use public key cryptography, and when it isn’t. We’ll continue our discussion of public key cryptography by introducing each of the three algorithms supported by OpenSSL. For each one, we’ll discuss what they can and cannot do, as well as provide some examples of how to access their functionality at a low level. In addition, we’ll discuss how they can be used together to compliment each other. Finally, we’ll revisit our discussion of the EVP interface from Chapters 6 and 7, demonstrating how the interface can also be used with public key algorithms, which should be the preferred method, as long as it suits your needs.

When to Use Public Key Cryptography

Suppose that we want to create a secure communications system in which a group of people can send messages to one another. Using symmetric encryption, such a system can be easily devised. Everyone in the group agrees on a key to use when encrypting messages to each other. While this system provides data secrecy by limiting access to the messages to only those people in the group, it also has some serious drawbacks. For example, Alice cannot send a message to Bob without Charlie also being able to read it. Additionally, Charlie could forge a message so that it appears to have come from someone else in the group—or even worse, he could change a message that someone else sent.

In order to stop the threat of forgery or message corruption, we need to have some way to authenticate and verify the integrity of messages. At the same time, we need to provide for more granularity in encryption to allow private messages between individuals in the group. With symmetric encryption, these goals can be met by having each of our users share a unique key with each of the other users. With three members of the group, Alice, Bob, and Charlie, this would mean Alice has an Alice-Bob key and an Alice-Charlie key. When she receives a message from either Bob or Charlie, she decrypts it with the appropriate key, and if she recovers a message, she has authenticated the sender. Charlie is no longer able to forge or change a message from Alice to Bob because he does not have the Alice-Bob key.

While alleviating the original problems, we’ve managed to create new ones. The biggest problem is that this new system does not scale very well. If Dave is introduced into the group, he would need to agree on different keys to use with Alice, Bob, and Charlie. Every time that a new person joins the group, n-1 keys need to be created, in which n is the number of people now in the group. With 100 people in the group, there would have to be 4,950 different keys! Imagine, then, what would happen if Bob’s computer was broken into and all of his keys were compromised. New keys would now have to be created for Bob to communicate with everyone else in the group. Finally, this system does not provide non-repudiation . Non-repudiation prevents either party involved in a communication from denying involvement in that communication. For example, Alice could receive a message from Bob encrypted with the Alice-Bob key, but Bob can credibly deny ever sending it by claiming Alice fabricated the message and encrypted it herself; non-repudiation guarantees that this is not possible.

The use of public key cryptography solves each of these problems, but it does not do so all by itself. It must be combined properly with message digests and symmetric encryption. Not only does public key cryptography tend to scale well, it also allows us to negotiate keys online, authenticate communications, maintain data integrity, and provide non-repudiation. Whenever any of these things is one of your goals, you should turn to public key cryptography for a solution.

Public key cryptography is not a magical solution to all things cryptographic. It can often be better and clearer not to use public key algorithms. As an example, in Chapter 7, we discussed storing data in a cookie. We used a symmetric cipher to encrypt the data and a MAC to maintain its integrity. It was not our intention to share the information with anyone, but only to protect it from prying eyes and tampering. In such a situation, the algorithms that we used were the right tools for the job. By themselves, they were able to do what was required. Had we used public key cryptography, we still would have required the use of a symmetric cipher and a message digest, in addition to requiring a private and public key. Using public key cryptography would have only introduced an unnecessary layer of complexity and required significantly more processing power from the server.

Because public key cryptography also depends on other cryptographic algorithms to be secure, if those algorithms can be used securely without public key cryptography, then you shouldn’t be using public key cryptography. It may seem as though we’re stating the obvious. All too often, inexperienced programmers tend to treat public key cryptography as though it was the one and only solution to all of their cryptographic needs. The fact of the matter is public key cryptography is frequently over-kill for most situations. It’s all about choosing the right tool for the job.

Diffie-Hellman

The Diffie-Hellman algorithm was the first public key algorithm ever invented. Introduced in 1976 by Whitfield Diffie and Martin Hellman, it is a simple algorithm that allows two parties to agree upon a key using an unsecured channel. In other words, it allows a shared secret to be created. The process is sometimes referred to as key exchange, but with Diffie-Hellman, it is more accurately called key agreement.

The primary use of Diffie-Hellman is shared-secret negotiation. The algorithm itself can be made to provide for authentication, but OpenSSL doesn’t include any high level interfaces for using these features, so they must be implemented by the application if they’re desired. For this reason, most OpenSSL applications that use this algorithm will also use another for authentication. For our purposes, we will discuss Diffie-Hellman mainly from the perspective of key agreement. Interested readers should refer to RFC 2631 for more information on using it for authentication.

Diffie-Hellman guarantees a shared secret will be created that is suitable to use as the key to a symmetric algorithm. Failing to provide authentication through some other means, either with authenticated extensions to the implementation or through use of another algorithm such as DSA, leaves the protocol susceptible to man-in-the-middle attacks. We’ll discuss the details of this type of attack with regard to Diffie-Hellman toward the end of this section.

The Basics

The low-level interface to Diffie-Hellman provided by OpenSSL consists of a structure of the type DH and a set of functions that operate on that structure. The DH structure and functions are made accessible by including the openssl/dh.h header file. The DH structure itself contains many data members that are of little or no interest to us, but four members are important, as shown in the following abbreviated DH structure definition:

typedef struct dh_st
{
    BIGNUM *p;
    BIGNUM *g;
    BIGNUM *pub_key;
    BIGNUM *priv_key;
} DH;

The p and g members, known as Diffie-Hellman parameters, are public values that must be shared between the two parties using the algorithm to create a shared secret. Because they’re public values, no harm will come from a potential attacker discovering them, which means that they can be agreed upon beforehand or exchanged over an insecure medium. Typically, one side of the conversation generates the parameters and shares them with the peer.

The p member is a prime number that is randomly generated. For temporary keys, it is typically at least 512 bits in length, whereas persistent keys should more appropriately be at least 1,024 bits. These sizes correspond to the notion of ephemeral and static keys presented in Chapter 5. The prime that will be used for p is generated such that (p-1)/2 is also prime. Such a prime is known as a safe or strong prime . The g member, also known as the generator , is usually a small number greater than one. OpenSSL functions best when this number is either two or five. A value of two is sometimes used for performance reasons, but keep in mind that faster key generation also means that an attacker can break the algorithm faster. In general, we recommend that a value of five be used.

Using the two public parameters, p and g, each pair chooses a random large integer for the priv_key member. A value for the pub_key member is computed from the priv_key member and shared with the peer. It is important that only the value of the pub_key member be shared. The value of the priv_key member should never be shared.

Using the values of priv_key and the peer’s pub_key, each peer can independently compute the shared secret. The shared secret is suitable for use as the key for a symmetric cipher. The entire exchange between the peers can be done over an insecure medium. Even if someone captures the parameter and key exchange, the attacker will not be able to determine the shared secret.

Generating and Exchanging Parameters

Diffie-Hellman requires that both parties involved in the key exchange use the same parameters to generate public keys. This means the parameters either need to be agreed upon and exchanged before the conversation begins, or the parameters must be generated and exchanged as part of the key exchange. Either way, the parameters must first be generated by one party and given to the other, or perhaps generated by a third party and given to both. For the purposes of this discussion, we will assume that the generation of the parameters will be done as part of the key exchange process, although this is often not desirable because it can take a significant amount of time to generate the parameters.

The participants in the key agreement must first agree which party will be responsible for generating the parameters that they’ll both use. In a client/server scenario, the parameters are usually generated by the server. Often, the server will generate the parameters when it starts up or retrieve them from a file that contains already generated parameters, and use the same parameters for each client that connects. It is also common for both the client and server to have a copy of the parameters built into the respective applications.

OpenSSL provides the function DH_generate_parameters , which will create a new DH object that is initialized with fresh values for p and g. The generation of the parameters generates a value only for p. The value of g is specified by the caller and is not chosen randomly by OpenSSL. The value of g should be a small number greater than one, usually either two or five.

DH *DH_generate_parameters(int prime_len, int generator,
                            void (*callback)(int, int, void *), void *cb_arg);
prime_len

The size of the prime to be generated, specified in terms of bits.

generator

The value to be used for g. In general, either DH_GENERATOR_2 or DH_GENERATOR_5 should be used for this argument.

callback

A pointer to a function that will be called during the prime generation process to report the status of the prime generation. The callback is the same as the callback used by BN_generate_prime , which we discussed in Chapter 4. In fact, DH_generate_parameters uses BN_generate_prime, and it is BN_generate_prime that actually makes the calls to the callback function. This argument may be specified as NULL if no callbacks are desired.

cb_arg

A pointer to application-specific data. OpenSSL does not use this value for anything itself. It is used only when passed as an argument to the specified callback function.

Using the generation function alone can be dangerous. While the generation function does have validity checks for the prime that it generates, it could generate a prime that is not suitable for use with the algorithm. For this reason, the function DH_check should always be used to ensure that the generated prime is suitable.

int DH_check(DH *dh, int *codes);
dh

The DH object containing the parameters we wish to check.

codes

An integer that will be treated as a bit mask by DH_check and will contain the results of the check when the function returns successfully.

If the function encounters an error unrelated to the validity of the generated prime, the return will be zero; otherwise, it will be nonzero. When the function returns successfully, the codes argument will contain a bit mask that indicates whether the parameters are suitable for use or not. If none of the bits is set, the parameters should be considered suitable for use. If any of the following bits are set, the parameters may not be suitable for use. In most cases, the parameters should be thrown away, and new ones should be generated.

DH_CHECK_P_NOT_PRIME

If this bit is set, it indicates that the generated prime is not actually a prime number. Ordinarily, this bit should never be set when the parameters are generated using DH_generate_parameters, but it could very well be set when checking parameters retrieved from disk or from a peer.

DH_CHECK_P_NOT_SAFE_PRIME

If this bit is set, it indicates that the generated prime is not safe. That is, (p-1)/2 is not also a prime number. As with DH_CHECK_P_NOT_PRIME, this bit should never be set when the parameters were generated using DH_generate_parameters, but it could very well be set when checking parameters retrieved from disk or from a peer.

DH_NOT_SUITABLE_GENERATOR

If this bit is set, it indicates that the generated prime and the generator are not suitable for use together. The parameters don’t necessarily need to be thrown away and regenerated if this bit is set. Instead, the generator could be changed and the check retried.

DH_UNABLE_TO_CHECK_GENERATOR

If this bit is set, a nonstandard generator is being used, so the DH_check function is unable to check to see that the prime and the generator are suitable for use. If you know that you’ve set a nonstandard generator intentionally, it’s up to you to decide whether it is safe to ignore this bit being set or not.

Once the parameters have been generated, they can be transmitted to the peer. The details of how the data is sent depend on the medium that is being used for the exchange. To transmit the parameters over a TCP connection, the BIGNUM functions BN_bn2bin and BN_bin2bn are obvious candidates.

The party that generates the parameters calls DH_generate_parameters to obtain a DH object. The party that is receiving the parameters must also obtain a DH object. This is easily done by calling the function DH_new , which will allocate and initialize a new DH object. The parameters that are received from the peer can then be directly assigned to the DH object’s p and g data members, using the appropriate BIGNUM functions.

When we’re done with a DH object, we must be sure to destroy it by calling the function DH_free , and passing the pointer returned by either DH_generate_parameters or DH_new as the only argument.

Computing Shared Secrets

Now that parameters have been generated and received by the two peers, each peer must generate a key pair and exchange their public keys. Remember that the private key must not be shared at all. Once this is done, each peer can independently compute the shared secret, and the algorithm will have done its job. With authenticated Diffie-Hellman, the public/private key pairs can persist beyond usage for a single key-agreement. In these cases, we must be wary of a special class of attack against Diffie-Hellman, which is discussed at the end of this section.

OpenSSL provides the function DH_generate_key for generating public and private keys. It requires as its only argument a DH object that has the parameters, p and g, filled in. If the keys are generated successfully, the return from the function will be nonzero. If an error occurs, the return will be zero.

Once the keys have been generated successfully, each peer must exchange their public key with the other peer. The details of how to exchange the value of the public key varies depending on the medium that is being used, but in a typical case in which the communication is taking place over an established TCP connection, the functions BN_bn2bin and BN_bin2bn will once again work for the exchange of the DH object’s pub_key data member.

With the parameters and public key now exchanged, each party in the exchange can use his own private key and the peer’s public key to compute the shared secret using the function DH_compute_key .

int DH_compute_key(unsigned char *secret, BIGNUM *pub_key, DH *dh);
secret

A buffer that will be used to hold the shared secret. It must be allocated by the caller and should be big enough to hold the secret. The number of bytes required to hold the secret can be determined with a call to DH_size, passing the DH object as the only argument.

pub_key

The peer’s public key.

dh

The DH object that contains the parameters and the caller’s private key.

After the shared secret is computed, the DH object is no longer needed unless more secrets will be generated and exchanged. It can be safely destroyed using the DH_free function.

In certain cases, Diffie-Hellman can be subject to a type of attack known as a small-subgroup attack . This attack results in a reduction of the computational complexity of brute-forcing the peer’s private key value. Essentially, a small-subgroup attack can result in the victim’s private key being discovered. There are several different methods of protecting Diffie-Hellman against this type of attack. The simplest method is to use ephemeral keying. If both parties stick to ephemeral keying and use a separate method of authentication, small-subgroup attacks are thwarted. This isn’t always feasible, however, mostly due to computational expense. If static keys will be used, two simple mathematical checks can be performed on the public key received from a peer to ensure these attacks aren’t possible. If the key passes both tests, it’s safe to use. The first test verifies that the supplied key is greater than 1 and less than the value of the p parameter. The second test computes y q mod p, in which y is the key to test and q is another large prime. If the result of this operation is 1, the key is safe; otherwise, it is not. The q parameter is not generated by OpenSSL even though there is a placeholder for it in the DH structure. An algorithm for generating q can be found in RFC 2631. If you’re interested in the other methods or more detailed information on the attack, we recommend that you read RFC 2785.

Practical Applications

When we began our discussion of Diffie-Hellman, we mentioned that it provides key agreement and authentication. Use of the authentication features of this protocol is not very common; thus, pairing Diffie-Hellman with another algorithm for authentication is often done. The threat is that mistakenly leaving out authentication can lead to susceptibility to man-in-the-middle attacks. To execute such an attack, the attacker sits in between two hosts that are trying to communicate and intercepts all of the messages. For example, suppose that Alice and Bob plan to use Diffie-Hellman to make a shared secret. Charlie could intercept all messages from Alice to Bob and all messages from Bob to Alice. From this position, Charlie can agree upon a key with Alice and a different key with Bob. When the attacker receives a message from Alice, he decrypts it with the key he negotiated with her and reads the message. He can then encrypt the message using the key he negotiated with Bob and pass it along to him. Alice and Bob will believe that they’re communicating securely. They’ll be completely unaware that Charlie is eavesdropping and worse, possibly even altering their messages, inserting forged messages, or not passing the messages along at all.

To alleviate this problem, Diffie-Hellman should always be used with some method of authentication, most commonly from another algorithm. This is accomplished by authenticating the messages containing public values for the Diffie-Hellman agreement. Using signatures, each party would exchange their public keys to use for signing before the conversation begins, and then sign the public value before sending it. The details will be explained in the following section.

Digital Signature Algorithm (DSA)

The DSA algorithm was developed by the National Institute for Standards and Testing (NIST) and the National Security Agency (NSA). It was first proposed in 1991 and stirred up a significant amount of controversy. Finally, in 1994, it became a standard. As its name implies, the DSA algorithm is useful for computing digital signatures, but that is the only thing for which it can be used. It is not capable of providing key agreement or encryption without extension.

Using a private key, the user can compute a signature for an arbitrary piece of data. Anyone possessing the public key that corresponds to the private key used to compute a signature can then verify that signature. The algorithm works in conjunction with the Secure Hash Algorithm (SHA). Essentially, the hash of the data to be signed is computed, and the hash is actually signed, rather than the data itself. The public key that corresponds to the private key used to compute a digital signature can then be used to obtain the hash of the data from the signature. This hash is compared with the hash computed by the party verifying the signature. If they match, the data is considered authentic. If they don’t match, the data is not identical to the data that was originally signed.

A digital signature is useful for verifying the integrity of data, ensuring that it has not been corrupted or tampered with. It also provides non-repudiation since only one person should have access to the private key used to compute a signature. The utility of a digital signature when combined with a key exchange algorithm such as Diffie-Hellman is easy to see. If the two parties performing a key exchange trust that the public key actually belongs to the party with which they’re communicating, a digital signature can be used to prevent a man-in-the-middle attack.

The Basics

Similar to the low-level interface to Diffie-Hellman, the low-level interface to DSA provided by OpenSSL consists of a DSA structure and a set of functions that operate on that structure. The DSA structure and functions are made accessible by including the openssl/dsa.h header file. The DSA structure itself contains many data members that are of little or no interest to us, but five members are important, as shown in the following abbreviated DSA structure definition:

typedef struct dsa_st
{
    BIGNUM *p;
    BIGNUM *q;
    BIGNUM *g;
    BIGNUM *pub_key;
    BIGNUM *priv_key;
} DSA;

The p, q, and g members, known as DSA parameters, are public values that must be generated before a key pair can be generated. Because they’re public values, no harm will come if a potential attacker discovers them. The same parameters can be safely used to generate multiple keys. In fact, RFC 2459 specifies a mechanism in which DSA parameters for a certificate can be inherited from the certificate of the issuer. Using parameter inheritance not only reduces the size of certificates, it also enforces the sharing of parameters.

The p member is a prime number that is randomly generated. Initially, the proposed standard fixed the length of the prime at 512 bits. Due to much criticism, this was later changed to allow a range between 512 and 1,024 bits. The length of the prime must be a multiple of 64 bits, however. OpenSSL does not enforce the 1,024-bit upper bound, but it’s not a good idea to use a prime larger than 1,024 bits—many programs may not be able to use the keys that result from such a large prime. The q member is a prime factor of p-1. The value of q must also be exactly 160 bits in length. The g member is the result of a mathematical expression involving a randomly chosen integer, as well as p and q.

Using the three public parameters (p, q, and g), the public and private keys can be computed. Public parameters used to compute the keys are required to generate or verify a digital signature. The parameters must therefore be exchanged along with the public key in order for the public key to be useful. Of course, the private key should never be distributed.

Generating Parameters and Keys

Like Diffie-Hellman, DSA requires generation of parameters before keys can be generated. Once a set of parameters is generated, many keys can be created from it. This does not mean that only people with keys created from the same parameters can interoperate; it simply means that in order to do so, all of the parameter values must be included as part of the public key.

The interface for generating DSA parameters is similar to the interface for generating Diffie-Hellman parameters. The function DSA_generate_parameters will create a new DSA object that is initialized with fresh values for p, q, and g. Generation of DSA parameters is more complex than Diffie-Hellman parameter generation, although it is typically much faster, especially for large key lengths.

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);
bits

The size of the prime to be generated, specified in terms of bits. Remember that the maximum allowed by the standard is 1,024 bits.

seed

An optional buffer containing data that gives the function a starting point to begin looking for primes. This argument can be specified as NULL, and generally should be.

seed_len

The number of bytes contained in the seed buffer. If the seed buffer is specified as NULL, this argument must be specified as 0.

counter_ret

An optional argument that will receive the number of iterations the function went through to find primes that satisfy the requirements for p and q. This argument may be specified as NULL.

h_ret

An optional argument that will receive the number of iterations the function went through to find a value for h, which is the random number used to calculate g. This argument may be specified as NULL.

callback

A pointer to a function that will be called during the prime generation process to report the status of the prime generation. The callback is the same as the callback used by BN_generate_prime, which we discussed in Chapter 4. This argument may be specified as NULL if no callbacks are desired.

cb_arg

A pointer to application-specific data. OpenSSL does not use this value for anything itself. It is passed as an argument to the specified callback function.

Once parameters have been generated, key pairs can be generated. The function DSA_generate_key is provided by OpenSSL for doing just that. It requires as its only argument a DSA object that has the parameters p, q, and g filled in. If the keys are generated successfully, the return from the function will be nonzero. If an error occurs, the return will be zero. The generated public and private keys are stored in the supplied DSA object’s pub_key and priv_key members, respectively.

Once the keys are computed, they can be used for creating digital signatures and verifying them. The DSA object containing the parameters and keys must remain in memory to perform these operations, of course, but when the DSA object is no longer needed, it should be destroyed by calling DSA_free and passing the DSA object as its only argument.

Signing and Verifying

Digital signatures must be computed over a message digest of the data that will be signed. The DSA standard dictates that the message digest algorithm used for this is SHA1, which has output of the same size as the DSA q parameter. This is no coincidence. In fact, the DSA algorithm cannot be used to compute a signature for data that is larger than its q parameter.

OpenSSL provides a low-level interface for computing signatures that does allow you to use DSA to sign arbitrary data. By this, we mean that OpenSSL does not enforce the requirement to use SHA1 as the message digest to sign, or even to sign a message digest at all! We strongly recommend against using the low-level interface for signing and verifying in favor of using the EVP interface, which we describe later in this chapter. However, we will briefly cover the three low-level functions for creating and verifying DSA signatures.

Computing a digital signature can be a processor-intensive operation. For this reason, OpenSSL provides a function that allows for the pre-computation of the portions of a signature that do not actually require the data to be signed.

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

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

ctx

An optional BIGNUM context that will be used in the pre-computation. If this argument is specified as NULL, a temporary context will be created and used internally.

kinvp

Receives a dynamically allocated BIGNUM that will hold the pre-computed kinv value. This value can then be placed into the DSA object’s kinv member so that it will be used when signing.

rp

Receives a dynamically allocated BIGNUM that will hold the pre-computed r value. This value can then be placed into the DSA object’s r member so that it will be used when signing.

Care must be taken when pre-computing the kinv and r values for signing. Both values must be placed in the DSA object. If only one is placed in the object, the memory used by the other will be leaked. Additionally, the values cannot be reused. They will be destroyed by the signing function. It may seem tempting to save a copy of the pre-computed values and reuse them for signing multiple pieces of data, but you must not. An integral part of a DSA signature is a 160-bit (actually, the same size as the q parameter, but that should always be 160 bits) random number known as k. If the same value for k is used more than once, it is possible for an attacker to discover the private key.

The function DSA_sign is provided for actually computing a digital signature using the DSA algorithm. If pre-computation of kinv and r has not been done, the function will perform the computation itself.

int DSA_sign(int type, const unsigned char *dgst, int len,
             unsigned char *sigret, unsigned int *siglen, DSA *dsa);
type

Ignored for DSA signing. The argument is present only for consistency with the RSA signing function.

dgst

The buffer of data that will be signed, which should always be an SHA1 hash.

len

The number of bytes in the data buffer for signing. It should always be the length of the message digest, but can never be larger than the size of the q parameter, which is fixed at 160 bits or 20 bytes.

sigret

A buffer that will receive the signature. It must be large enough to hold the signature. The minimum size for the buffer can be determined with a call to DSA_size, passing the DSA object that is being used for signing as the only argument.

siglen

Receives the number of bytes of the sigret buffer that were filled with the signature. This argument cannot be NULL.

dsa

The DSA object to use to sign the contents of the data buffer.

The function DSA_verify is used to verify signatures and is similar to the function used to create them.

int DSA_verify(int type, const unsigned char *dgst, int len,
               unsigned char *sigbuf, int siglen, DSA *dsa);
type

Ignored for DSA verification. The argument is present only for consistency with the RSA verification function.

dgst

The message digest of the data. This digest should be computed on the data prior to calling this function. This is used to compare with the digest in the signature.

len

The number of bytes in the digest buffer.

sigbuf

The signature that will be verified.

siglen

The number of bytes contained in the signature buffer.

dsa

The DSA object to use to verify the signature.

Practical Applications

DSA does not provide a mechanism for key agreement. It cannot create a shared secret for us, whereas an algorithm such as Diffie-Hellman can. Thus, it is common to employ a protocol that uses Diffie-Hellman and DSA to deliver two-way authentication.

First, we need to establish that the client and server each have a public/private key pair and can exchange public keys offline, prior to beginning secure communications. At the same time, we need to assume they agree upon parameters for Diffie-Hellman key agreement. Overall, these assumptions are not egregious. A protocol might begin with each party computing public and private Diffie-Hellman keys. At this point, instead of sending that unauthenticated data to the opposite end, each party will sign the message containing the public value with their own private DSA key. After this is done, they can send the signed messages.

The value of that signature is that it assures both parties that the public keys actually came from their respective peer because only the server has the server’s private key, and only the client has the client’s private key. If both parties can verify the signature on the public key they’ve received, they can be sure that there is no man-in-the-middle. What we’ve just described is known as two-way authentication, because each party independently verifies the other.

RSA

Invented in 1977, RSA was the first public key algorithm capable of both digitally signing and encrypting data. Despite the fact that it was patented, it became the de facto public key algorithm. Almost any company that used public key cryptography in their products licensed the technology. It was patented only in the United States, and the patent expired on September 20, 2000.

Like other public key algorithms, RSA employs a public and private key pair. Although the mathematics behind the algorithm are easy to understand, we won’t discuss them here. Many other texts explain the algorithm in detail. For our purposes, it is sufficient to say that the algorithm’s strength lies in the infeasibility of factoring extremely big numbers, and after 25 years of extensive cryptanalysis, it remains unbroken.

The Basics

Just like the low-level interfaces to Diffie-Hellman and DSA, the low-level interface to RSA provided by OpenSSL consists of an RSA structure and a set of functions that operate on that structure. The RSA structure and functions are made accessible by including the openssl/rsa.h header file. The RSA structure itself contains many data members that are of little or no interest to us, but five members are important, as shown in the following abbreviated RSA structure definition:

typedef struct rsa_st
{
    BIGNUM *p;
    BIGNUM *q;
    BIGNUM *n;
    BIGNUM *e;
    BIGNUM *d;
} RSA;

The p and q members are both randomly chosen large prime numbers. These two numbers are multiplied together to obtain n, which is known as the public modulus. References to the strength of RSA actually refer to the bit length of the public modulus. Once the private key has been computed, p and q may be discarded, but they should never be disclosed. However, keeping the values for p and q around is a good idea; having them available increases the efficiency with which private key operations are performed.

The e member, also known as the public exponent, should be a randomly chosen integer such that it and (p-1)(q-1) are relatively prime . Two numbers are relatively prime if they share no factors other than one; they may or may not actually be prime. The public exponent is usually a small number, and in practice, is usually either 3 or 65,537 (also referred to as Fermat’s F4 number). Using e, p, and q, the value of d is computed.

Together, the n and e members are the public key, and the d member is the private key.

Generating Keys

The RSA algorithm does not require parameters to generate keys, which makes key generation a much simpler affair. OpenSSL provides a single function to create a new RSA key pair, which will create a new RSA object that is initialized with a fresh key pair.

RSA *RSA_generate_key(int num, unsigned long e,                
                      void (*callback)(int, int, void *), void *cb_arg);
num

Specifies the number of bits in the public modulus. The minimum this should be to ensure proper security is 1,024 bits, though we recommend 2,048 bits.

e

The value to use for the public exponent. OpenSSL does not attempt to generate this value randomly, but instead requires you to specify it. You may specify any number you like, but we recommend that you use one of the two constants, RSA_3 or RSA_F4.

callback

A pointer to a function that will be called during the prime generation process to report the status of the prime generation. The callback is the same as the callback used by BN_generate_prime, which we discussed in Chapter 4. This argument may be specified as NULL if no callbacks are desired.

cb_arg

A pointer to application-specific data. OpenSSL does not use this value for anything itself. It is used only when passed as an argument to the specified callback function.

Once keys are generated, it is a good idea to call RSA_check_key to verify that the keys generated by RSA_generate_key are actually usable. The function requires an RSA object as its only argument. The RSA object should be completely filled in, including values for its p, q, n, e, and d members. If the return value from the function is 0, it indicates that there is a problem with the keys, and that they should be regenerated. If the return value from the function is 1, it indicates that all tests passed, and that the keys are suitable for use. If the return value from the function is -1, an error occurred in performing the tests.

When the RSA key is no longer needed, it should be freed by calling RSA_free and passing the RSA object as its only argument.

Data Encryption, Key Agreement, and Key Transport

Now that we’ve explored key setup, we can look at the different methods of using those keys. The RSA algorithm allows for secrecy because it can encrypt data. Data encrypted with a public key can be decrypted only by an entity possessing the corresponding private key.

Before looking at the specifics of these operations, we must first discuss blinding . Any RSA operation involving a private key is susceptible to timing attacks. Given an RSA operation as a black box that we feed data into and collect results from, an attacker can discern information about our key material by measuring the amount of time it takes the various operations to complete. Blinding is essentially a change in the implementation that removes any correlation between the amount of time taken for an operation and the private key value.

The function RSA_blinding_on enables blinding for the RSA object passed into it as the first argument. This means that any operation on the RSA object involving the private key will be guarded from timing attacks. The second argument is an optional BN_CTX object, which may be specified as NULL. Likewise, the function RSA_blinding_off disables blinding for the RSA object passed into it. If you ever design a system that allows arbitrary operations on a private key, such as any system that automatically signs data, enabling blinding is important for the safety of the private key.

With RSA, it is most common to perform encryption with a public key and decryption with the corresponding private key. The functions RSA_public_encrypt and RSA_private_decrypt provide the means to perform these operations. It is also possible to perform encryption with a private key and decryption with the corresponding public key. Two functions, RSA_private_encrypt and RSA_public_decrypt , are provided by OpenSSL. They’re intended for those who wish to implement signing at a very low level. In general, they should be avoided. Each of the four functions returns the number of bytes, including padding, that were encrypted or decrypted, or -1 if an error occurs.

int RSA_public_encrypt(int flen, unsigned char *from, unsigned char *to,
                       RSA *rsa, int padding);
flen

Specifies the number of bytes in the buffer to be encrypted.

from

A buffer containing the data to be encrypted.

to

A buffer that will be used to hold the encrypted data. It should be large enough to hold the largest possible amount of encrypted data, which can be determined by calling RSA_size and passing the RSA object that is being used to encrypt as its only argument.

rsa

The RSA object that contains the public key to use to perform the encryption.

padding

Specifies which of the built-in padding types supported by OpenSSL should be used when padding is necessary.

int RSA_private_decrypt(int flen, unsigned char *from, unsigned char *to,
                         RSA *rsa, int padding);
flen

Specifies the number of bytes of data in the buffer to be decrypted.

from

A buffer containing the data to be decrypted.

to

A buffer that will be used to hold the decrypted data. It should be large enough to hold the largest possible amount of decrypted data, which can be determined by calling RSA_size and passing the RSA object that is being used to decrypt as its only argument.

rsa

The RSA object that contains the private key to use to perform the decryption.

padding

Specifies which of the built-in padding types supported by OpenSSL was used when the data was encrypted. The padding for decryption must be the same as the padding used for encryption.

The encryption performed by RSA requires that the data to be encrypted be appropriately formed. If the data to be encrypted does not fit the requirements, it must be padded. OpenSSL supports several types of padding for RSA encryption:

RSA_PKCS1_PADDING

If this type of padding is used, the length of the data to be encrypted must be smaller than RSA_size(rsa)-11. This is an older method of padding that has since been replaced by RSA_PKCS1_OAEP_PADDING. It should be used only for compatibility with older applications.

RSA_PKCS1_OAEP_PADDING

If this type of padding is used, the length of the data to be encrypted must be smaller than RSA_size(rsa)-41. This type of padding is the recommended method for all new applications.

RSA_SSLV23_PADDING

This type of padding is an SSL-specific modification to the RSA_PKCS1_PADDING type. Under normal circumstances, this type of padding is rarely used.

RSA_NO_PADDING

This disables automatic padding by the encryption function, and assumes that the caller will perform the padding. It requires that the data to be encrypted is exactly RSA_size(rsa) bytes.

Signing and Verifying

As with DSA, RSA computes signatures over a chunk of data by first cryptographically hashing the data to obtain a digest value. This digest and the signer’s private key are then used as input to the signing process. Verification can be performed by executing the same hash and using this digest value along with the signature value and the signing party’s public key.

int RSA_sign(int type, unsigned char *m, unsigned int m_len,   
             unsigned char *sigret, unsigned int *siglen, RSA *rsa)
type

The message digest algorithm that obtains the cryptographic hash of the data to be signed. The algorithm is specified using NID_sha1 for SHA1, NID_ripemd160 for RipeMD-160, or NID_md5 for MD5.

m

A buffer containing the data to be signed.

m_len

The number of bytes in the data buffer that will be considered in the signature.

sigret

A buffer that will receive the signature. It must be big enough to hold the largest possible signature, which can be determined with a call to RSA_size, passing the RSA object being used to sign as its only argument.

siglen

Receives the number of bytes that were written to the signature buffer. This argument must not be NULL.

rsa

The RSA object containing the private key to use to sign the data.

You will notice that the signature of the function is identical to the DSA signing function; however, the arguments are interpreted differently. The same is true for the signature verification function.

int RSA_verify(int type, unsigned char *m, unsigned int m_len,
               unsigned char *sigbuf, unsigned int siglen, RSA *rsa);
type

The message digest algorithm used to obtain the cryptographic hash of the data that will be verified. The algorithm specified must be the same that was used to compute the signature that will be verified.

m

A buffer containing the data that was signed and is to be verified.

m_len

Specifies the number of bytes contained in the data buffer that will be considered.

sigbuf

A buffer containing the signature to be verified.

siglen

Specifies the number of bytes contained in the signature buffer.

rsa

The RSA object containing the public key to use to verify the signature.

Practical Applications

It may be tempting to consider implementing a system in which we rely solely on RSA for secrecy. This is actually not a good idea for a variety of reasons, but most importantly because RSA is very slow compared to symmetric ciphers. Additionally, because we can encrypt only small chunks of data at a time with RSA keys alone, the best use for RSA’s data encryption capabilities is for key exchange. There are several ways that RSA can be used in this manner.

One-way authenticating key transport

The client chooses a random session key, encrypts it with the server’s public key, and sends the encrypted data to the server. The server decrypts the message and recovers the session key. As with Diffie-Hellman, this shared secret can then be used with a symmetric cipher. This protocol is slightly better than Diffie-Hellman, though, because there is one-way authentication. The client has authenticated the server since no other party could have recovered the session key. This feature allows a party that knows someone’s public key to compose an encrypted message for that person without any direct communication at the time of composition.

Two-way authenticating key transport

Essentially, the client takes the same steps as in the protocol above, but before sending the message to the server, it signs the message. This way, the server can verify the signature using the client’s public key, thus authenticating the client, and recover a session key. The client has authenticated the server because no other party could recover that session key.

Two-way authenticating key agreement

Both the client and the server choose random data to use as key material, encrypt it with the opposite party’s public key, sign the message with their own private key, and send the data to the opposite party. Because both parties have signed their messages, each one can authenticate the other. Additionally, each party has their own secret random number and the other party’s number. Each party can then combine the two numbers in some well-known way, generally by XOR or by hashing the concatenation of the data, to obtain the shared secret.

While we’ve scratched the surface of the theory involved in designing secure protocols, we advise against trying to design your own, because doing it correctly and securely is very hard. Using a well-known and verifiably secure protocol such as SSL or TLS is highly recommended for all but academic situations.

The EVP Public Key Interface

In Chapters 6 and 7, we discussed OpenSSL’s EVP interface, which is a high-level layer of abstraction that can be used with message digests and symmetric ciphers. It probably won’t surprise you to learn that the interface can also be used with two of the public key algorithms that we’ve discussed in this chapter, DSA and RSA. Two sets of functions are provided for digital signatures and data encryption. They work very much like the functions that we’ve discussed in previous chapters.

The two new sets of EVP functions require the use of an EVP_PKEY object, used to hold the public or private key that is required. An EVP_PKEY object is therefore simply a container that can hold either a DSA or an RSA object. Actually, an EVP_PKEY object can also hold a DH object, but since Diffie-Hellman can be used only for key agreement, the EVP interface cannot actually make use of a DH object. With this in mind, we will limit our discussion to DSA and RSA keys.

An EVP_PKEY object is created by calling the EVP_PKEY_new function, which will return either a new EVP_PKEY object or NULL if an error occurred. Conversely, an EVP_PKEY object is destroyed by calling the EVP_PKEY_free function, passing the EVP_PKEY object to be destroyed as its only argument. When an EVP_PKEY object is first created, it is an empty container. Obviously, this is not very useful.

Two functions, EVP_PKEY_assign_DSA and EVP_PKEY_assign_RSA , are used to populate the EVP_PKEY object with either a DSA object or an RSA object. Each function requires exactly two arguments. The first is the EVP_PKEY object to assign to, and the second is the key object to be assigned. Once you assign a key object to an EVP_PKEY object, that object becomes “owned” by the EVP_PKEY object. In other words, the EVP_PKEY object takes responsibility for destroying the key object. You should never attempt to destroy a key object once it has been assigned to an EVP_PKEY object. An EVP_PKEY object can hold only a single object at a time; if you assign it multiple objects, the last one you assign to it is the one that it will contain, and all of the previous assignments will be destroyed.

Alternatively, either EVP_PKEY_set1_DSA or EVP_PKEY_set1_RSA can be used to populate the EVP_PKEY object with either a DSA object or an RSA object. The functions’ signatures are identical to EVP_PKEY_assign_DSA and EVP_PKEY_assign_RSA. The difference between the two is that these two functions do not cause the EVP_PKEY object to assume ownership of the assigned key object. Instead, the key object’s reference count is incremented when it is assigned, and decremented when it would otherwise be destroyed.

It is also possible to obtain a pointer to the key object contained by an EVP_PKEY object. If you know the type of key object the EVP_PKEY object contains, you can use the appropriate function for the key type, either EVP_PKEY_get1_DSA or EVP_PKEY_get1_RSA . These two functions increment the reference count on the key object that is returned, so you must be sure to call either DSA_free or RSA_free as appropriate when you’re done with the object. If you don’t know the type of key object the EVP_PKEY object contains, the function EVP_PKEY_type will return either EVP_PKEY_DSA or EVP_PKEY_RSA.

Signing and Verifying

The EVP interface provides a means to create and verify digital signatures using either DSA or RSA keys. Creating a digital signature requires a private key, while verifying one requires the public key that corresponds to the private key used to create the signature. When a digital signature is created for a piece of data, that same data is required to verify the signature.

The first step in creating a digital signature using the EVP interface is to initialize an EVP_MD_CTX object, discussed in Chapter 6. An EVP_MD_CTX object can be allocated dynamically using malloc or new, or it can be allocated statically as either a global or a local variable. In either case, EVP_SignInit must be called to initialize the object.

int EVP_SignInit(EVP_MD_CTX *ctx, const EVP_MD *type);
ctx

The EVP_MD_CTX to be initialized.

type

The message digest to use to compute the hash, which is actually signed instead of the data itself. The message digest is specified the same as the message digest is specified in a call to EVP_DigestInit. If you’re using an RSA key for signing, you may choose from EVP_md2, EVP_md4, EVP_md5, EVP_sha1, EVP_mdc2, and EVP_ripemd160. If you’re using a DSA key for signing, you must use EVP_dss1.

The “engine” release and Version 0.9.7 of OpenSSL deprecate the EVP_SignInit function. Instead, EVP_SignInit_ex should be used, which adds a third argument. The additional argument is a pointer to an engine object that has been initialized. It may also be specified as NULL, which causes the default software implementation to be used.

Once a context has been initialized, the data to be signed must be fed into it. This is done by calling EVP_SignUpdate as many times as necessary to feed in all the data. The function works identically to EVP_DigestUpdate and, in fact, is actually implemented as a preprocessor macro that simply calls EVP_DigestUpdate.

int EVP_SignUpdate(EVP_MD_CTX *ctx, const void *buf, unsigned int len);
ctx

Specifies the context that is being used. It should be initialized using EVP_SignInit or EVP_SignInit_ex.

buf

A buffer that contains the data to be signed. For each call to EVP_SignUpdate for the same context, the data from this buffer is concatenated with the data specified in previous calls.

len

Specifies the number of bytes of data contained in the data buffer.

Once all of the data to be signed has been added into the context, the signature can be computed. This is done by calling EVP_SignFinal . Once EVP_SignFinal has been called, the context is no longer valid, and must be reinitialized with EVP_SignInit or EVP_SignInit_ex before it can be used to create another signature. Note that this does not mean that a dynamically allocated context object has been freed. OpenSSL has no way of knowing whether the context object was dynamically allocated or not, so it is up to the caller to free that memory as appropriate.

int EVP_SignFinal(EVP_MD_CTX *ctx, unsigned char *sig, unsigned int *siglen,
                  EVP_PKEY *pkey);
ctx

The initialized context that contains the data to be signed.

sig

A buffer that will receive the signature. This buffer must be large enough to hold the largest possible signature. The function EVP_PKEY_size can be used to find out how large the buffer must be. It requires a single argument, which is the EVP_PKEY object that will be used to compute the signature.

siglen

Receives the number of bytes written into the signature buffer. This value must not be NULL, even if you’re not interested in the information. Normally, this value is the same as the return from EVP_PKEY_size.

pkey

The EVP_PKEY object that will be used to compute the signature. It must contain either a DSA or an RSA private key.

EVP_SignFinal will return zero if an error occurred in computing the signature, and nonzero if the signature computed successfully.

Verifying a signature is as simple as computing one. The set of functions to verify a signature match the functions to compute a signature, save for their names. Before a signature can be verified, a context must be initialized by calling EVP_VerifyInit . The “engine” release and Version 0.9.7 of OpenSSL deprecate EVP_VerifyInit in favor of EVP_VerifyInit_ex , which requires a third argument that is an engine object or NULL to use the default software implementation of the chosen message digest algorithm.

int EVP_VerifyInit(EVP_MD_CTX *ctx, const EVP_MD *type);
ctx

The context object that will be initialized.

type

The message digest algorithm to use. The message digest used for verification must be the same that was used for creating the signature that will be verified.

With an initialized context, EVP_VerifyUpdate should be called as many times as necessary to supply the context with all of the data that was purportedly used to create the signature. The data for verifying a signature should be the same data used to create the signature. If the signature and the data do not match, the verification will fail. This, of course, is the whole point of a digital signature—to verify that the data is intact and has not been modified in any way.

int EVP_VerifyUpdate(EVP_MD_CTX *ctx, const void *buf, unsigned int len);
ctx

The context object that was initialized with EVP_VerifyInit or EVP_VerifyInit_ex.

buf

A buffer that contains the data to be verified.

len

Specifies the number of bytes contained in the data buffer.

Once all of the data has been passed into the context along with EVP_VerifyUpdate, EVP_VerifyFinal must be called to perform the actual verification of the signature. Its signature matches that of EVP_SignFinal, but its arguments and return value are interpreted differently.

int EVP_VerifyFinal(EVP_MD_CTX *ctx, unsigned char *sigbuf,
                    unsigned int siglen, EVP_PKEY *pkey);
ctx

The initialized context that contains the data to be signed.

sigbuf

A buffer containing the signature to be verified.

siglen

Specifies the number of bytes contained in the signature buffer.

pkey

The EVP_PKEY object that will be used to verify the signature. It must contain either a DSA or an RSA public key. The key should match the private key that was used to create the signature.

EVP_VerifyFinal will return -1 if an error occurs in verifying the signature. If the signature does not match the data, the return value will be 0, and if the signature is valid, the return value will be 1.

Encrypting and Decrypting

The EVP interface also provides an interface for enveloping data using RSA keys. Data enveloping is the process of encrypting a chunk of data with RSA, typically for securely sending it to a recipient. Initially, it may seem that enveloping is equivalent to using RSA to encrypt all of our data, but this is not correct. Because public key algorithms are inappropriate for encrypting large amounts of data, enveloping requires the sender to generate a random key, also called a session key, and encrypt that key using the public key of the intended recipient. The actual data is then encrypted with a symmetric cipher using the session key. Incorrectly implementing this process often causes bugs or vulnerabilities in programs. To avoid this, OpenSSL provides features in the EVP interface referred to as the “envelope” encryption/decryption interface that handles all of the subtleties correctly.

One of the features offered by the EVP interface for data encryption is the ability to encrypt the same data using several public keys. A single session key is generated and encrypted using each public key that is supplied. The recipients can then use their respective private keys to decrypt the session key, and thus decrypt the data using the appropriate symmetric cipher. The support for this feature is wholly contained by the context initialization function, which means the interface for encrypting for multiple recipients is the same as encrypting for a single recipient.

The act of encrypting data using a public key through the EVP interface is called sealing. A set of functions, EVP_SealInit, EVP_SealUpdate, and EVP_SealFinal, are provided to encrypt an arbitrary amount of data. Each of these functions requires an EVP_CIPHER_CTX object to maintain state. This object can be allocated either dynamically or statically and must be initialized prior to use. Initialization of the context object is achieved by calling EVP_SealInit. Unlike the other EVP context initialization functions that we’ve discussed, EVP_SealInit has not been deprecated in the “engine” release or Version 0.9.7 of OpenSSL.

int EVP_SealInit(EVP_CIPHER_CTX *ctx, EVP_CIPHER *type,        
                 unsigned char **ek, int *ekl, unsigned char *iv,
                 EVP_PKEY **pubk, int npubk);
ctx

The context to be initialized.

type

The symmetric cipher used to perform the actual encryption. A large number of symmetric ciphers and variations are supported by OpenSSL; we don’t list all of the options here. Chapter 6 discusses the supported ciphers in detail and provides a comprehensive list of suitable options for this argument.

ek

An array of buffers. There must be as many elements allocated for the array as there are public keys specified for encryption. Each element in the array is a buffer that must be large enough to hold the encrypted session key. The size required for each buffer can be determined by calling EVP_PKEY_size , passing the EVP_PKEY object for each buffer as its only argument.

ekl

An array that will receive the actual encrypted key length for each public key. Again, there must be as many elements allocated for the array as there are public keys specified for encryption.

iv

A buffer that contains the initialization vector to use. If not specified as NULL, the initialization vector buffer should contain at least as many bytes as defined by the constant EVP_MAX_IV_LENGTH. Not all ciphers require an initialization vector. For such ciphers, this argument can be specified as NULL.

pubk

An array of public keys to use to encrypt the randomly generated session key. Each element in the array should be an EVP_PKEY object that contains an RSA public key.

npubk

Specifies the number of public keys contained in the pubk array. It also determines the size requirements for the ek and ekl arrays.

Example 8-1 demonstrates how to call EVP_SealInit .

Example 8-1. Calling EVP_SealInit
ek = (unsigned char **)malloc(sizeof(unsigned char *) * npubk);
ekl = (int *)malloc(sizeof(int) * npubk);
pubk = (EVP_PKEY **)malloc(sizeof(EVP_PKEY *) * npubk);
 
for (i = 0;  i < npubk;  i++)
{
    pubk[i] = EVP_PKEY_new(  );
    EVP_PKEY_set1_RSA(pubk[i], rsakey[i]);
    ek[i] = (unsigned char *)malloc(EVP_PKEY_size(pubk[i]));
}
 
EVP_SealInit(ctx, type, ek, ekl, iv, pubk, npubk);

Once initialization has been performed, the remainder of the sealing process is very much like encrypting with a symmetric cipher, as described in Chapter 6. In fact, it is identical, with the exception of the names of the functions that are used. The initialization function takes care of generating the session key and encrypting it using the public key of each recipient. It also prepares the context to perform the encryption of the actual data using the selected symmetric cipher.

int EVP_SealUpdate(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl,
                   unsigned char *in, int inl);
ctx

The context previously initialized by EVP_SealInit.

out

A buffer that will receive the encrypted data. Refer to Chapter 6 for the details of how to compute the size of this buffer.

outl

Receives the number of bytes written to the encrypted data buffer.

in

A buffer that contains the data to be encrypted.

inl

Specifies the number of bytes contained in the unencrypted data buffer.

After the data to be encrypted has been fed into EVP_SealUpdate, EVP_SealFinish must be called to finish the job. It will perform any necessary padding and write any remaining encrypted data into its output buffer. Once EVP_SealFinish has been called, EVP_SealInit must be called again before the context can be reused.

int EVP_SealFinal(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl);
ctx

The context previously initialized by EVP_SealInit. Used by EVP_SealUpdate to encrypt data.

out

A buffer that will receive any final encrypted data.

outl

Receives the number of bytes written to the encrypted data buffer.

When the seal process is complete, the encrypted session key, initialization vector (if any), and encrypted data must all be sent to the recipient in order for the recipient to decrypt the data successfully. The recipient can then use the EVP interface to unseal or open (decrypt) the data.

The function EVP_OpenInit initializes a context for decryption. Like EVP_SealInit, it has not been deprecated by the “engine” release or Version 0.9.7 of OpenSSL.

int EVP_OpenInit(EVP_CIPHER_CTX *ctx, EVP_CIPHER *type, unsigned char *ek,
                 int ekl, unsigned char *iv, EVP_PKEY *pkey);
ctx

The context object that will be initialized.

type

The symmetric cipher to use to decrypt the data. It should be the same cipher that was used to encrypt the data.

ek

A buffer containing the public key-encrypted session key.

ekl

Specifies the number of bytes contained in the encrypted session key buffer.

iv

A buffer containing the initialization vector that was used with the symmetric cipher to encrypt the data.

pkey

An EVP_PKEY object that contains an RSA private key that will be used to decrypt the session key.

Initializing the context decrypts the session key and prepares the context for decrypting the data using the specified symmetric cipher. The rest of the process is identical to decrypting data that has been encrypted with a symmetric cipher, i.e., the functions EVP_OpenUpdate and EVP_OpenFinal are identical to the functions EVP_DecryptUpdate and EVP_DecryptFinal in every way, except for their names.

int EVP_OpenUpdate(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl,
                   unsigned char *in, int inl);
ctx

The initialized context that will be used to decrypt the data.

out

Specifies a buffer to which decrypted data will be written. It must be as large as the input buffer.

outl

Receives the number of bytes written to the decrypted data buffer.

in

Specifies a buffer from which encrypted data will be decrypted.

inl

Specifies the number of bytes contained in the encrypted data buffer.

Once all of the data to be decrypted has been fed into EVP_OpenUpdate, EVP_OpenFinal should be called to complete the job. Remember from our discussion of ciphers in Chapter 6 that when a block cipher is being used, the final block of decrypted data will not be written to the output buffer until EVP_DecryptFinal is called (or, in this case, EVP_OpenFinal). Once EVP_OpenFinal is called, the context cannot be reused until EVP_OpenInit is called again.

int EVP_OpenFinal(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl);
ctx

Specifies the context to finalize decryption for.

out

Specifies a buffer to which decrypted data will be written.

outl

Receives the number of bytes written to the decrypted data buffer.

Encoding and Decoding Objects

Generating key pairs and keeping them in memory all of the time isn’t very useful. It’s often desirable to generate a key pair and save it to a file. Conversely, if a key pair is saved in a file, it also needs to be readable from the file. One solution to the problem is simply to write the data members from the various objects that we’ve discussed to a format of our own design. Doing this will certainly work, but it has the major drawback of not being compatible with any other software with which we may want to use our keys.

As luck would have it, OpenSSL supports two standard formats for storing and exchanging key pairs. The first is binary form known as DER(Distinguished Encoding Rules). This type of file is suitable for use in binary files or for transfer over a network connection, but is not ideal for all situations, particularly text-based communications such as email. The second format that OpenSSL supports is known as PEM (Privacy Enhanced Mail), which is defined in RFCs 1421, 1422, 1423, and 1424. PEM data is base64-encoded and provides the ability to encrypt the data before encoding it.

Without delving into the details of the DER and PEM encodings, we need to know some of the properties of each. The biggest difference, as we’ve stated above, is that DER is a binary encoding and PEM is text-based. Due partially to this fact, a file may contain only a single DER-encoded object, but can contain many PEM objects. In general, if we need to write data to disk, we should use PEM; however, many third-party applications accept objects only in the DER encoding. For objects stored in files, the command-line utility allows for encoding conversion for most common object types.

Writing and Reading DER-Encoded Objects

OpenSSL provides functions for many types of objects that write the DER representation of the object into a buffer. Each of the functions has a similar signature: the OpenSSL object as the first argument and a buffer as the second. The functions return the number of bytes written to the buffer or, if the buffer is specified as NULL, the number of bytes that would have been written to the buffer is returned. In addition, if a buffer is specified, it is advanced to the byte after the last byte written in order to facilitate writing multiple DER objects to the same buffer.

int i2d_OBJNAME(OBJTYPE *obj, unsigned char **pp);

You’ll note that the second parameter is specified as a pointer to the buffer. This is done so that the pointer can be advanced after the data is written to it. Example 8-2 demonstrates how these functions can be used by writing an RSA public key into a dynamically allocated buffer. The function returns the buffer that was allocated to hold the DER-encoded key and stores the length of the buffer in the second argument.

Example 8-2. DER-encoding an RSA public key
unsigned char *DER_encode_RSA_public(RSA *rsa, int *len)
{
    unsigned char *buf, *next;
 
    *len = i2d_RSAPublicKey(rsa, NULL);
    buf = next = (unsigned char *)malloc(*len);
    i2d_RSAPublicKey(rsa, &next);
    return buf;
}

Likewise, a function is provided for each type of object that reads the DER representation of the object from a buffer and creates the appropriate object in memory. Again, each of the functions has a similar signature. The first argument is an object to populate with the data obtained from the buffer, which is specified as the second argument. The third argument specifies the number of bytes contained in the buffer that should be used for constructing the object.

               OBJTYPE *d2i_OBJNAME(OBJTYPE **obj, unsigned char **pp, long length);

If the first argument is specified as NULL, a new object of the appropriate type is created and populated with the data recovered from the buffer. If it is specified as a pointer to NULL, a new object of the appropriate type is created and populated in the same manner, but the argument is updated to receive the newly created object. Finally, if a pointer to an existing object is specified, the existing object is populated with the data recovered from the buffer. In all cases, the return value of the function is the object that was populated, or NULL if an error occurred in recovering the data. Example 8-3 demonstrates DER-decoding an RSA public key.

Example 8-3. DER-decoding an RSA public key
RSA *DER_decode_RSA_public(unsigned char *buf, long len)
{
    RSA *rsa;
 
    rsa = d2i_RSAPublicKey(NULL, &buf, len);
    return rsa;
}

The two functions d2i_PublicKey and d2i_PrivateKey have a different signature from the others. The first argument to these two functions is an integer that specifies the type of key (DH, DSA, or RSA), which is encoded in the buffer. One of the constants EVP_PKEY_DH, EVP_PKEY_DSA, or EVP_PKEY_RSA must be specified to indicate the type of key to expect. The rest of the arguments to these two functions are the same, except they are shifted to make room for the argument that specifies the type of key. The function d2i_AutoPrivateKey is provided to allow OpenSSL to attempt to guess the type of private key that is stored in the buffer. Table 8-1 lists some DER-encoding functions.

Table 8-1. Functions for reading and writing DER encodings of public key objects

Type of object

OpenSSL object type

Function to write the DER representation

Function to read the DER representation

Diffie-Hellman parameters

DH

i2d_DHparams

d2i_DHparams

DSA parameters

DSA

i2d_DSAparams

d2i_DSAparams

DSA public key

DSA

i2d_DSAPublicKey

d2i_DSAPublicKey

DSA private key

DSA

i2d_DSAPrivateKey

d2i_DSAPrivateKey

RSA public key

RSA

i2d_RSAPublicKey

d2i_RSAPublicKey

RSA private key

RSA

i2d_RSAPrivateKey

d2i_RSAPrivateKey

EVP_PKEY public key

EVP_PKEY

i2d_PublicKey

d2i_PublicKey

EVP_PKEY private key

EVP_PKEY

i2d_PrivateKey

d2i_PrivateKey

EVP_PKEY private key

EVP_PKEY

N/A

d2i_AutoPrivateKey

The two classes of functions above are useful for reading and writing structures to flat memory, but they still require us to write them to or read them from either a file or BIO. To help with this task, OpenSSL provides functions and macros that handle writing to files and streams. Each of the function names listed in Table 8-1 can be used as a base for building a new name to read or write DER encodings to or from a file or BIO. By appending _bio or _fp to the names of the functions, names for functions for writing to or reading from a BIO or file can be made. For example, the function i2d_DSAparams_bio will write the DER representation of DSA parameters to the specified BIO. The definitions for the BIO and file interface functions are in the header file openssl/x509.h.

There are three exceptions to this rule. The first is that there is no BIO or file equivalent function for i2d_PublicKey or d2i_PublicKey. The second exception is that there is no equivalently named function for the d2i_AutoPrivateKey function. The third exception is that d2i_PrivateKey_bio and d2i_PrivateKey_fp both behave as though they were named d2i_AutoPrivateKey_bio and d2i_AutoPrivateKey_fp.

For the functions that write DER representations to a BIO or a file, the first argument is either a BIO or FILE object, depending on the function that is used. The second argument is the object that will be DER-encoded and written to the BIO or file. The return value from these functions is zero if an error occurs; otherwise, it is nonzero to indicate success.

The functions that read DER representations also require a BIO or FILE object as their first argument, depending on which function is used. The second argument is a pointer to an object of the type that is being read, which is treated just the same as the first argument to the base functions. That is, when NULL or a pointer to NULL is specified, a new object of the appropriate type is created; otherwise, the specified object is populated. The return value from these functions is NULL if an error occurs; otherwise, the return is the object that was populated. Example 8-4 demonstrates.

Example 8-4. Reading and writing DER-encoded objects using the BIO and file functions
BIO *bio = BIO_new(BIO_s_memory(  ));
RSA *rsa = RSA_generate_key(1024, RSA_F4, NULL, NULL);
i2d_RSAPrivateKey_bio(bio, rsa);
 
FILE *fp = fopen("rsakey.der", "rb");
RSA *rsa = NULL;
d2i_RSAPrivateKey_fp(fp, &rsa);

Writing and Reading PEM-Encoded Objects

The same objects that can be read and written in DER format can also be read and written in PEM format. The interface to the PEM format is somewhat different from the DER interface. To begin with, it supports writing to and reading from a BIO or a file; it does not support memory buffers like the DER interface does. As it turns out, this isn’t much of a limitation since you can just use a memory BIO. All of the function declarations can be found in the header file openssl/pem.h.

The functions for writing public keys and parameters all share the same basic signature. The functions for writing to a BIO require a BIO object as the first argument and the object type as the second argument. The functions for writing to a file require a FILE object as the first argument and the object type as the second argument. Each of the functions, regardless of whether they’re writing to a BIO object or a FILE object, return zero if an error occurs and nonzero if the operation was successful.

The functions for writing private keys are a bit more complex because the PEM format allows them to be encrypted before they’re encoded and written out. Each function requires a BIO or FILE object to write to, the object to be written, a password callback function, and symmetric cipher information.

int PEM_write_OBJNAME(FILE *fp, OBJTYPE *obj, const EVP_CIPHER *enc,
                      unsigned char *kstr, int klen, pem_password_cb callback,
                      void *cb_arg);
fp

The file to write to.

obj

The object that contains the data to be written. The type of this object can be DSA, EVP_PKEY, or RSA.

enc

The optional cipher to use to encrypt the key data. This can be any symmetric cipher object supported by OpenSSL. Refer to Chapter 6 for a comprehensive list of the available options. If this is specified as NULL, the key data is written unencrypted, and the remaining arguments are ignored.

kstr

An optional buffer that contains the password or passphrase to use for encryption. If this is specified as non-NULL, the password callback function is ignored, and the contents of this buffer are used.

klen

Specifies the number of bytes contained in the kstr buffer.

callback

A callback function to obtain the password or passphrase for encrypting the key data. Its signature is described below.

cb_arg

Application-specific data that is passed to the callback function. If the callback function and the kstr buffer are specified as NULL, this is interpreted as a NULL-terminated, C-style string that is used as the password or passphrase to encrypt the key data. If it is also specified as NULL, the default password callback function is used. The default password callback function prompts the user to enter the password or passphrase.

The functions that are used to write to a BIO object have the same signature, except that the FILE object is replaced with a BIO object. The return values from functions that write private keys are the same as the values from functions that write public keys and parameters: zero if an error occurs, nonzero otherwise. The password callback function, if one is used, has the following signature:

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

A buffer that the password or passphrase will be written into.

len

The size of the password buffer.

rwflag

Indicates whether the password or passphrase will be used to encrypt or decrypt the PEM data. When writing PEM data, this argument will be nonzero. When reading PEM data, this argument will be zero.

cb_arg

Application-specific. It is passed from the function that caused the password callback function to be called.

The functions for reading public keys, private keys, and parameters all share a similar signature. Each requires a BIO or a FILE object to read from, an object to populate with the data that was read, and a password callback function.

               OBJTYPE *PEM_read_OBJNAME(FILE *fp, OBJTYPE **obj, pem_password_cb callback,
                           void *cb_arg);
fp

The file to read from.

obj

The object to populate with the data that is read. If it is specified as NULL, a new object of the appropriate type is created and populated. If it is specified as a pointer to NULL, a new object of the appropriate type is also created and populated. In addition, it receives a pointer to the newly created object.

callback

A callback function to obtain the password or passphrase if one is required. A password or passphrase is required only if the PEM data being read is encrypted. Normally, only private keys are encrypted. The callback function may be specified as NULL.

cb_arg

Application-specific data that is passed to the callback function. If the callback is specified as NULL, this argument is interpreted as a NULL-terminated, C-style string containing the password or passphrase to use. If both the callback and this argument are specified as NULL, the default password callback function is used.

The functions that are used to read from a BIO have the same signature, except that the FILE object is replaced with a BIO object. The return value from the reading functions is always a pointer to the object that was populated with the data that was read. If an error occurs reading the PEM data, NULL is returned. See Table 8-2.

Table 8-2. Functions for reading and writing PEM encodings of public key objects

Type of object

OpenSSL object type

Function to write the PEM representation

Function to read the PEM representation

Diffie-Hellman parameters

DH

PEM_write_DHparams

PEM_write_bio_DHparams

PEM_read_DHparams

PEM_read_bio_DHparams

DSA parameters

DSA

PEM_write_DSAparams

PEM_write_bio_DSAparams

PEM_read_DSAparams

PEM_read_bio_DSAparams

DSA public key

DSA

PEM_write_DSA_PUBKEY

PEM_write_bio_DSA_PUBKEY

PEM_read_DSA_PUBKEY

PEM_read_bio_DSA_PUBKEY

DSA private key

DSA

PEM_write_DSAPrivateKey

PEM_write_bio_DSAPrivateKey

PEM_read_DSAPrivateKey

PEM_read_bio_DSAPrivateKey

RSA public key

RSA

PEM_write_RSA_PUBKEY

PEM_write_bio_RSA_PUBKEY

PEM_read_RSA_PUBKEY

PEM_read_bio_RSA_PUBKEY

RSA private key

RSA

PEM_write_RSAPrivateKey

PEM_write_bio_RSAPrivateKey

PEM_read_RSAPrivateKey

PEM_read_bio_RSAPrivateKey

EVP_PKEY public key

EVP_PKEY

PEM_write_PUBKEY

PEM_write_bio_PUBKEY

PEM_read_PUBKEY

PEM_read_bio_PUBKEY

EVP_PKEY private key

EVP_PKEY

PEM_write_PrivateKey

PEM_write_bio_PrivateKey

PEM_read_PrivateKey

PEM_read_bio_PrivateKey