Public key cryptography. More...
Data Structures | |
| struct | BC_RSA_PUBLIC_KEY |
| Structure that holds the RSA public key. More... | |
| struct | BC_RSA_PRIVATE_KEY |
| Structure that holds the RSA private key. More... | |
Enumerations | |
| enum | { BC_RSA_KEYL = BC_NANU_NEXT-0, BC_RSA_DLEN = BC_NANU_NEXT-1, BC_RSA_FMAT = BC_NANU_NEXT-2, BC_RSA_SEED = BC_NANU_NEXT-3 } |
Additional error codes for RSA operations. More... | |
Functions | |
| int | BC_RsaPrivateEncrypt (BC_NANU nanu, BC_RSA_PRIVATE_KEY *keys, void *data, unsigned dlen) |
| Encrypt a message with the private key. | |
| int | BC_RsaPrivateDecrypt (BC_NANU nanu, BC_RSA_PRIVATE_KEY *keys, void *data) |
| Decrypt a message with the private key. | |
| int | BC_RsaPrivateCore (BC_NANU nanu, BC_RSA_PRIVATE_KEY *keys, void *data) |
| Core private key RSA operation. | |
| int | BC_RsaPublicEncrypt (BC_NANU nanu, BC_RSA_PUBLIC_KEY *keys, void *data, unsigned dlen, unsigned seed) |
| Encrypt a message with the public key. | |
| int | BC_RsaPublicDecrypt (BC_NANU nanu, BC_RSA_PUBLIC_KEY *keys, void *data) |
| Decrypt a message with the public key. | |
| int | BC_RsaPublicCore (BC_NANU nanu, BC_RSA_PUBLIC_KEY *keys, void *data) |
| Core public key RSA operation. | |
Public key cryptography.
Public key cryptography refers to a class of cryptographic methods where you have different keys for encrypting and decrypting the message. There are several such asymmetric methods, the most frequently used ones are the Diffie-Hellman key exchange algorithm, the Digital Signature Algorithm (DSA) and the Rivest-Shamir-Adleman (RSA) algorithm. The library contains an implementation of the RSA algorithm.
The RSA public key cryptography is based on the mathematical properties of finite fields. The basic idea is the following: You select two big primes, p and q. These are typically several hundred to several thousand bits long. Then you calculate their product, n = p * q. The number n is called the modulus. Naturally, the length of the modulus is the sum of the lengths of p and q. The length of this modulus is the "key length", which, according to the current standards, is minimum 768 bits, typically 1024 or 2048 bits. Then you select a number, e, with certain properties. The value of e, which is called the public exponent, is fairly small, the largest value occuring in practice is 17 bits long. The SSH key generator tool typically chooses the value of 35, a mere 6 bits. Then you calculate a third number, d, which is called the private exponent. This number is in the same magnitude as the modulus, that is, 1024 or 2048 bits long. The way d is calculated from p, q and e guarantees some important properties. Let x be a number that is less than the modulus. Then the following will be true:
( xe mod n )d mod n = x
( xd mod n )e mod n = x
Furthermore, for the given n and e there's no number other than d for which the above holds true. Now to calculate d from p, q and e is easy but to calculate d from n and e is not possible, unless you manage to find p and q that make up n. Since factoring big numbers is a very hard problem, you can assume that d can not be calculated from n and e. A further property of modular exponentiation is that even if you know e, n and xe mod n, finding x from these is no simpler than finding p and q that make up n.
So, to use the above for cryptographic purposes, you publish n and e. Everybody can know those. On the other hand, you keep d (and of course p and q) secret. When someone wants to send you a secret message, they turn a message into a big number M (which is just a string of bytes, really) and calculate
C = Me mod n
and send the resulting number, C to you. Anyone can see C on transit but without knowing d they can't do anything with it. Only you can recover the message by calculating
M = Cd mod n
So, we have a way by which people can send you messages that only you can read. Unfortunately, if you want to send a secret message, the above doesn't work. If you calculate
A = Md mod n
then everybody who knows e and n can decode the message. It is still a useful operation, however. For any given M you are the only one who can generate A so that decoding A using e and n will result in M. Therefore, this method can be used to authenticate messages. Anyone can read it, but you can guarantee that it was originated by you and was not tampered width.
The (n,e) tuple is called the public key and you publish it. The (n,d) tuple is your private key and you keep it secret. So, if each party has his/her own private key and a public key, we have two basic operations now: when you want to send a secret message, you encode it with the recipient's public key and the recipient decodes it with his/her private key and when you want to send an authenticated (but not secret) message, you encode it with your private key and all recipients can verify its authenticity by decoding it with your public key. You can combine these to send secret, authenticated messages. Note that the proper algorithm for message authentication is DSA, which uses similar maths but is not the same as RSA.
The theory is simple enough (unless you actually want to understand why modular exponentiation has these properties, of course). There are a few practical issues, though. One is that usually the message you want to send is longer than 1024 or 2048 bits. The other is that modular exponentiation of large numbers is a very CPU intensive activity. Using the public key is relatively cheap, because e is small. However, since d is typically as large as the modulus, private key operations are several orders of magnitude slower. In addition, the time needed for public operations increases quadraticly with the key length (assuming that the public exponent remains the same) while the private operations' time requirement increases cubicly.
Thus, it is not feasible to use the RSA algorithm to encrypt the actual message itself. Instead, when you want to establish a secure communication channel with your party, you choose a random encryption key. It is called the session key. You encrypt it with the other party's public key and send it over, using RSA. You and your partner now share a randomly generated encryption key that can be used to encrypt and decrypt messages using a symmetric (and fast) cipher, such as AES. When you finish the current communication session, the key gets discareded (it is said that the session key is ephemeral) and if you start a new session, you generate a new key that you again exchange using RSA. The important point is that you use RSA to facilitate a secure key exchange, the encryption of the actual data is not done by RSA.
There are standards that describe the format of the RSA messages and the the format of the keys. The most important standard is the PKCS#1, published by RSA (the company founded by the inventors of RSA). PKCS#1 has several versions, the latest one is 2.1. However, the library implements the data encapsulation described in version 1.5 of the standard, because that is the most widely used method today.
The above standard also describes how the keys should be formatted and in turn it refers to the ASN.1 standard that defines a way of exchanging arbitrary structured information - a little bit like XML but binary, not text. From that point on, it's a Wild West. Every package that is based on RSA has its own favourite method of storing the information described by PKCS#1 and ASN.1 in some textual container and they have little utilities to convert keys from one package's format to the other's.
This library is no exception. The RSA functions in the library require the keys to be in a specific non-standard format. In particular, the keys are represented as C structure objects. That is more efficient than the DER encoding described by the standards and it is certainly more compiler friendly. A tool is provided in the package that can turn keys generated by the ssh-keygen program (part of the SSH package) to the format required by the library. See the documentation for the lpc-rsa, a key extractor for RSA program.
The RSA functions use the Natural numbers module in the library for the actual calculations. Although the RSA functions hide all the natural number processing details, there is one function in the natural number library, namely BC_NanuInit() which you will have to call directly, to prepare the workspace for the calculations performed by the RSA routines.
To use these functions, you need to include crypto/rsa.h.
The most time critical parts of the natural numbers library are written in assembly. Therefore, there's little difference between ARM and THUMB mode execution times. The table below gives you the run-time of private and public operations with various key lengths (the public exponent in all cases was 35) on a 60MHz LPC23xx chip.
Key Public Private [bit] [ms] [ms]
768 5 180 1024 8 360 2048 27 2330 4096 103 17150
| anonymous enum |
| int BC_RsaPrivateCore | ( | BC_NANU | nanu, | |
| BC_RSA_PRIVATE_KEY * | keys, | |||
| void * | data | |||
| ) |
Core private key RSA operation.
Calculates C = Md mod n where M is the message to be encrypted with the private key or the ciphertext encrypted with the public key, C is the encrypted ciphertext the decrypted message, respectively; d is the private exponent and n is the modulus.
C and M must have the same length as n.
This function is the core used in the RSA private key functions and it can be used to implement the 2.1 version RSA scheme. The core operation is the same, but the way the user message is turned into M is different for the version 1.5 and version 2.x RSA standards.
The first parameter is a natural number workspace object that has to have been obtained by a call to BC_NanuInit(). The memory needed for the calculations is 3 times the size of the modulus plus the administrative area and the stack depth must be at least 8. That, is, if you have a key with a modulus length of N (in bits), you need:
// // The length of the modulus in words // #define RSA_MOD_LEN (N/32) // // Minimum stack size required by the private RSA functions // #define RSA_STACK 8 // // The size of the workspace // #define RSA_WORKSPACE ( BC_NANU_POOL( RSA_STACK ) + 3 * RSA_MOD_LEN ) // // Define the workspace // static int workspace[ RSA_WORSPACE ]; // // Prepare the workspace before passing it to the RSA routine // nanu = BC_NanuInit( workspace, RSA_WORKSPACE, RSA_STACK );
The function returns the length of the modulus (in bytes) in case of success or one of the (negative) error codes defined in nanu.h or rsa.h in case of failure.
| nanu | An already initialised BC_NANU object | |
| keys | Pointer to an RSA private key structure | |
| data | Pointer to a data buffer that contains M at entry and will contain C at exit. |
| >0 | Success, the returned value is the length of the key (in bytes). | |
| BC_NANU_SOVR | Natural numbers library stack overflow | |
| BC_NANU_OMEM | Natural numbers library out of memory | |
| BC_NANU_SIZE | Invalid size in one of the key components | |
| BC_NANU_NANN | Invalid value in one of the key components | |
| BC_RSA_KEYL | Key length is invalid. The routine accepts keys with length of whole multiplies of 256, up to 8192. Other keylengths result in this error. |
| int BC_RsaPrivateDecrypt | ( | BC_NANU | nanu, | |
| BC_RSA_PRIVATE_KEY * | keys, | |||
| void * | data | |||
| ) |
Decrypt a message with the private key.
The function decrypts a message with the private key using the procedure described in the PKCS#1-1.5 document.
The function receives a BC_NANU object that must contain an already initialised natual number calculation workspace; see BC_RsaPrivateCore() for further details.
The function also receives a pointer to the private key structure and a pointer to a buffer containing the ciphertext. The ciphertext is exactly as long as the modulus. Upon return the buffer will contain the decrypted message, which is at least 3 bytes shorter than the modulus.
The function returns the length of the decrypted message in case of success or one of the error codes defined in nanu.h or rsa.h in case of failure.
| nanu | An already initialised BC_NANU object | |
| keys | Pointer to an RSA private key structure | |
| data | Pointer to a data buffer that contains the ciphertext at entry and will contain the cleartext at exit |
| >=0 | Success, the returned value is the length of the decrypted cleartext. | |
| BC_NANU_SOVR | Natural numbers library stack overflow | |
| BC_NANU_OMEM | Natural numbers library out of memory | |
| BC_NANU_SIZE | Invalid size in one of the key components | |
| BC_NANU_NANN | Invalid value in one of the key components | |
| BC_RSA_KEYL | Key length is invalid | |
| BC_RSA_DLEN | Cleartext is too long |
| int BC_RsaPrivateEncrypt | ( | BC_NANU | nanu, | |
| BC_RSA_PRIVATE_KEY * | keys, | |||
| void * | data, | |||
| unsigned | dlen | |||
| ) |
Encrypt a message with the private key.
The function encrypts a message with the private key using the procedure described in the PKCS#1-1.5 document.
The function receives a BC_NANU object that must contain an already initialised natual number calculation workspace; see BC_RsaPrivateCore() for further details.
The function also receives a pointer to the private key structure and a pointer to a buffer, at least as long as the modulus, that contains the message (starting at its first byte). The length of the message, which must be at least 3 bytes shorter than the modulus, is given in the last function argument. Upon return the buffer will contain the encrypted message, which is always exactly as long as the modulus.
The function returns BC_NANU_NANU in case of success or one of the error codes defined in nanu.h or rsa.h in case of failure.
| nanu | An already initialised BC_NANU object | |
| keys | Pointer to an RSA private key structure | |
| data | Pointer to a data buffer that contains the cleartext at entry and will contain the ciphertext at exit | |
| dlen | The length of the cleartext, in bytes |
| BC_NANU_NANU | Success | |
| BC_NANU_SOVR | Natural numbers library stack overflow | |
| BC_NANU_OMEM | Natural numbers library out of memory | |
| BC_NANU_SIZE | Invalid size in one of the key components | |
| BC_NANU_NANN | Invalid value in one of the key components | |
| BC_RSA_KEYL | Key length is invalid | |
| BC_RSA_DLEN | Cleartext is too long |
| int BC_RsaPublicCore | ( | BC_NANU | nanu, | |
| BC_RSA_PUBLIC_KEY * | keys, | |||
| void * | data | |||
| ) |
Core public key RSA operation.
Calculates C = Me mod n where M is the message to be encrypted with the public key or the ciphertext encrypted with the private key, C is the encrypted ciphertext the decrypted message, respectively; e is the public exponent and n is the modulus.
C and M must have the same length as n.
This function is the core used in the RSA public key functions and it can be used to implement the 2.1 version RSA scheme. The core operation is the same, but the way the user message is turned into M is different for the version 1.5 and version 2.x RSA standards.
The first parameter is a natural number workspace object that has to have been obtained by a call to BC_NanuInit(). The memory needed for the calculations is 3 times the size of the modulus plus the administrative area and the stack depth must be at least 4. That, is, if you have a key with a modulus length of N (in bits), you need:
// // The length of the modulus in words // #define RSA_MOD_LEN (N/32) // // Minimum stack size required by the public RSA functions // #define RSA_STACK 4 // // The size of the workspace // #define RSA_WORKSPACE ( BC_NANU_POOL( RSA_STACK ) + 3 * RSA_MOD_LEN ) // // Define the workspace // static int workspace[ RSA_WORSPACE ]; // // Prepare the workspace before passing it to the RSA routine // nanu = BC_NanuInit( workspace, RSA_WORKSPACE, RSA_STACK );
The function returns the length of the modulus (in bytes) in case of success or one of the (negative) error codes defined in nanu.h or rsa.h in case of failure.
| nanu | An already initialised BC_NANU object | |
| keys | Pointer to an RSA public key structure | |
| data | Pointer to a data buffer that contains M at entry and will contain C at exit. |
| >0 | Success, the returned value is the length of the key (in bytes). | |
| BC_NANU_SOVR | Natural numbers library stack overflow | |
| BC_NANU_OMEM | Natural numbers library out of memory | |
| BC_NANU_SIZE | Invalid size in one of the key components | |
| BC_NANU_NANN | Invalid value in one of the key components | |
| BC_RSA_KEYL | Key length is invalid. The routine accepts keys with length of whole multiplies of 256, up to 8192. Other keylengths result in this error. |
| int BC_RsaPublicDecrypt | ( | BC_NANU | nanu, | |
| BC_RSA_PUBLIC_KEY * | keys, | |||
| void * | data | |||
| ) |
Decrypt a message with the public key.
The function decrypts a message with the public key using the procedure described in the PKCS#1-1.5 document.
The function receives a BC_NANU object that must contain an already initialised natual number calculation workspace; see BC_RsaPublicCore() for further details.
The function also receives a pointer to the public key structure and a pointer to a buffer containing the ciphertext. The ciphertext is exactly as long as the modulus. Upon return the buffer will contain the decrypted message, which is at least 3 bytes shorter than the modulus.
The function returns the length of the decrypted message in case of success or one of the error codes defined in nanu.h or rsa.h in case of failure.
| nanu | An already initialised BC_NANU object | |
| keys | Pointer to an RSA private key structure | |
| data | Pointer to a data buffer that contains the ciphertext at entry and will contain the cleartext at exit |
| >=0 | Success, the returned value is the length of the decrypted cleartext. | |
| BC_NANU_SOVR | Natural numbers library stack overflow | |
| BC_NANU_OMEM | Natural numbers library out of memory | |
| BC_NANU_SIZE | Invalid size in one of the key components | |
| BC_NANU_NANN | Invalid value in one of the key components | |
| BC_RSA_KEYL | Key length is invalid | |
| BC_RSA_DLEN | Cleartext is too long |
| int BC_RsaPublicEncrypt | ( | BC_NANU | nanu, | |
| BC_RSA_PUBLIC_KEY * | keys, | |||
| void * | data, | |||
| unsigned | dlen, | |||
| unsigned | seed | |||
| ) |
Encrypt a message with the public key.
The function encrypts a message with the public key using the procedure described in the PKCS#1-1.5 document.
The function receives a BC_NANU object that must contain an already initialised natual number calculation workspace; see BC_RsaPublicCore() for further details.
The function also receives a pointer to the public key structure and a pointer to a buffer, at least as long as the modulus, that contains the message (starting at its first byte). The length of the message, which must be at least 3 bytes shorter than the modulus, is given in the last function argument. Upon return the buffer will contain the encrypted message, which is always exactly as long as the modulus.
The function returns BC_NANU_NANU in case of success or one of the error codes defined in nanu.h or rsa.h in case of failure.
| nanu | An already initialised BC_NANU object | |
| keys | Pointer to an RSA public key structure | |
| data | Pointer to a data buffer that contains the cleartext at entry and will contain the ciphertext at exit | |
| dlen | The length of the cleartext, in bytes | |
| seed | A number that is not 0, which will be used as a seed for a random number generator. You should generate the seed as random as you can, preferably using some hard to predict hardware value, also possibly the current date and time. Try to avoid using the same seed for consecutive encryptions. |
| BC_NANU_NANU | Success | |
| BC_NANU_SOVR | Natural numbers library stack overflow | |
| BC_NANU_OMEM | Natural numbers library out of memory | |
| BC_NANU_SIZE | Invalid size in one of the key components | |
| BC_NANU_NANN | Invalid value in one of the key components | |
| BC_RSA_KEYL | Key length is invalid | |
| BC_RSA_DLEN | Cleartext is too long | |
| BC_RSA_SEED | Incorrect seed value (i.e. it was 0) |
1.6.3