# # # # # # # ####### # # #### # ### # # ##### # # # ##### # ## # # # # # # # # # # # # # # # # # # # # # # # # ## # # # # # # # #### # ### # ##### # # # ##### # # # ## # ### # P o r t a b l e E n c r y p t i o n L i b r a r y Version 2.00 Copyright Peter Gutmann, Eric Young, and Colin Plumb 1992 - 1996 The cryptlib encryption library provides an easy-to-use interface which allows even inexperienced crypto programmers to easily add strong encryption and authentication services to their software. The library contains DES, triple DES, IDEA, MDC/SHS, RC2, RC4, RC5, SAFER, SAFER-SK, Blowfish, and Blowfish-SK conventional encryption, MD2, MD4, MD5, RIPEMD-160 and SHA hash algorithms, and Diffie-Hellman, DSA, and RSA public-key encryption, as well as a comprehensive high-level interface with functions such as cryptCreateSignature() and cryptExportKey(). The library is supplied as source code for Unix (shared or static libraries), DOS, Windows (16- and 32-bit DLL's), and the Amiga. cryptlib provides a standardised interface to a number of popular encryption algorithms, as well as providing a high-level interface which hides most of the implementation details and provides an operating-system-independant encoding method which makes it easy to transfer encrypted data from one system to another. Although use of the the high-level interface is recommended, experience programmers can directly access the lower-level encryption routines for implementing custom encryption protocols or methods not provided by the library. The cryptlib API serves as an interface to a range of plug-in encryption modules which allow encryption algorithms to be added in a fairly transparent manner. The standardised API allows any of the algorithms and modes supported by the library to be used with a minimum of coding effort. As such the main function of the library is to provide a standard, portable, easy-to-use interface between the underlying encryption routines and the user software. In addition the easy-to-use high-level routines allow for the exchange of encrypted session keys and the creation and checking of digital signatures with a minimum of programming overhead. cryptlib has been written to be as idiot-proof as possible. On initialization it performs extensive self-testing against test data from encryption standards documents, and the API's check each parameter and function call for errors before any actions are performed, with error reporting down to the level of individual parameters. cryptlib implements a security perimeter around the encryption functions, with encryption contexts consisting of an arbitrary handle referring to (hidden) data held within the library. No outside access to state variables or keying information is possible, provided the underlying OS provides some form of memory protection. If the OS supports it, all sensitive information used by the library will be page-locked to ensure it is never swapped to disk. Author Contact Information: Peter Gutmann, pgut001@cs.auckland.ac.nz The cryptlib home page, http://www.cs.auckland.ac.nz/~pgut001/cryptlib.html, contains more information on the latest cryptlib developments. Usage Conditions ---------------- This software is distributed as copyrighted freeware, with copyrights on individual encryption modules being held by the contributing authors. You are free to use the code in any way you want, with the following restrictions: - If you make any changes to the code, you should send a copy of the changes to the author or authors to allow them to integrate the changes into the code. This is to allow a central consistent version to be maintained. - If you use the library as part of a product, you should offer a copy to the authors of the library. This is to let the authors know that their work is being usefully applied. You should also give the authors credit in your software and/or documentation. This is to let others know that the authors work is being usefully applied :-). - Any commercial software you create with this code may not be merely a set or subset of the encryption library, with or without minor added functionality. In particular you can't sell the library (or any modified form of it) as "your" encryption product. You can sell your own product which utilizes the encryption library, but you can't charge for the library itself or claim it as yours. This is to stop people adding their own wrappers and selling it as "their" encryption product. These terms are pretty much identical to the library GPL, which seem to be about the least restrictive usage terms around apart from outright public domain software. Patent Issues ------------- This library contains a number of algorithms which are covered by patents. These algorithms are Diffie-Hellman, DSA, IDEA, RC5, and RSA. A number of patent holders have very generously granted a license for royalty-free use of the algorithms in the library under certain circumstances, as explained below. Diffie-Hellman and DSA: The practice of Diffie-Hellman key exchange is covered by United States Patent No 4,200,770 ('Cryptographic Apparatus and Method') which expires in September 1997. The Canadian equivalent expires in September 1998. The practice of all other public key algorithms is covered by United States Patent No 4,218,582 ('Public Key Cryptographic Apparatus and Method') which expires in October 1998. Numerous equivalent patents have been issued in Europe and Japan which expire in October 1998. These patents are licensed exclusively by Cylink Corporation of Sunnyvale, California. Cylink has granted a license to all users of this library for non-commercial use, including research by non-profit institutions. This means you may incorporate this library in software which is distributed for free, provided you include the following notice in the software and all collateral documentation which states: The use of the public key algorithms in this software is covered by US Patents No 4,200,770 ('Cryptographic Apparatus and Method') and 4,218,582 ('Public Key Cryptographic Apparatus and Method') which are licensed exclusively by Cylink Corporation. In order to promote open standards for public key algorithms, Cylink has initiated a low cost licensing program for commercial use of public key. For more information, contact Cylink's web page at www.cylink.com or e-mail legal@cylink.com. IDEA: The IDEA algorithm is patented by Ascom Systec Ltd. of CH-5506 Maegenwil, Switzerland, who allow it to be used on a royalty-free basis for certain non-profit applications. Commercial users must obtain a license from the company in order to use IDEA. IDEA may be used on a royalty-free basis under the following conditions: Free use for private purposes: The free use of software containing the algorithm is strictly limited to non revenue generating data transfer between private individuals, ie not serving commercial purposes. Requests by freeware developers to obtain a royalty-free license to spread an application program containing the algorithm for non-commercial purposes must be directed to Ascom. Special offer for shareware developers: There is a special waiver for shareware developers. Such waiver eliminates the upfront fees as well as royalties for the first US$10,000 gross sales of a product containing the algorithm if and only if: 1. The product is being sold for a minimum of US$10 and a maximum of US$50. 2. The source code for the shareware is available to the public. Special conditions for research projects: The use of the algorithm in research projects is free provided that it serves the purpose of such project and within the project duration. Any use of the algorithm after the termination of a project including activities resulting from a project and for purposes not directly related to the project requires a license. Ascom Tech requires the following notice to be included for freeware products: This software product contains the IDEA algorithm as described and claimed in US patent 5,214,703, EPO patent 0482154 (covering Austria, France, Germany, Italy, the Netherlands, Spain, Sweden, Switzerland, and the UK), and Japanese patent application 508119/1991, "Device for the conversion of a digital block and use of same" (hereinafter referred to as "the algorithm"). Any use of the algorithm for commercial purposes is thus subject to a license from Ascom Systec Ltd. of CH-5506 Maegenwil (Switzerland), being the patentee and sole owner of all rights, including the trademark IDEA. Commercial purposes shall mean any revenue generating purpose including but not limited to: i) Using the algorithm for company internal purposes (subject to a site license). ii) Incorporating the algorithm into any software and distributing such software and/or providing services relating thereto to others (subject to a product license). iii) Using a product containing the algorithm not covered by an IDEA license (subject to an end user license). All such end user license agreements are available exclusively from Ascom Systec Ltd and may be requested via the WWW at http://www.ascom.ch/systec or by email to idea@ascom.ch. Use other than for commercial purposes is strictly limited to non-revenue generating data transfer between private individuals. The use by government agencies, non-profit organizations, etc is considered as use for commercial purposes but may be subject to special conditions. Any misuse will be prosecuted. RC5: The RC5 algorithm is patented by RSA Data Security Inc. 100 Marine Parkway, Redwoord City, California 94065, ph.+1 415 595-8782, fax +1 415 595-1873, and cannot be used commercially in the US without a license. RSA: The RSA algorithm is patented by RSA Data Security Inc. 100 Marine Parkway, Redwoord City, California 94065, ph.+1 415 595-8782, fax +1 415 595-1873, and cannot be used commercially in the US without a license. RSA licenses can most easily be obtained by waiting until the year 2000 when the patent expires. Library Basics -------------- Like the standard C file I/O libraries which work with FILE objects, this library works with an opaque "encryption context" object of type CRYPT_CONTEXT. To encrypt data, you create an encryption context, load a user key into it, en/decrypt data, and destroy it when you've finished. The library also uses an opaque "key collection" object of type CRYPT_KEYSET, which will be discussed later on. The CRYPT_CONTEXT type is the more important of the two, and the one you'll be using the most. This concept lends itself to implementation either as a C++ class or as C routines. Throughout this document all examples are given in C, translation to C++ is a simple step. The overall library structure is as follows: Library core Plug-in modules +---------------+--------------+--------------+ | | Blowfish | Blowfish-SK | | +--------------+--------------+ | | DES | DESX | | +--------------+--------------+ | |2k.Triple DES |3k.Triple DES | | +--------------+--------------+ | | IDEA | MDC/SHS | | +--------------+--------------+ | | Safer | Safer-SK | | +--------------+--------------+ | | RC2 | RC4 | | +--------------+--------------+ | | RC5 | | Crypt +--------------+--------------+ / \ | |Diffie-Hellman| DSA | User / ---- \ | Library +--------------+--------------+ Programs \ ---- / | | RSA | \ / | API +--------------+--------------+ | | MD2 | MD4 | | +--------------+--------------+ | | MD5 | SHA | | +--------------+--------------+ | | RIPEMD-160 | | +--------------+--------------+ | export encrypted key | | import encrypted key | | -----------------------------+ | create digital signature | | check digital signature | | -----------------------------+ | object-manipulation functions| +---------------------------------------------+ Access to the libraries encryption functionality is provided at two levels, via the low-level API which gives direct access to the encryption functions, and via the high-level API which provides more abstract functions such as "export public-key encrypted session key" and "export encrypted data object". The recommended interface is the high-level one, since it hides all the details required to work with the encryption algorithms and takes care of issues like managing keys and data types. The low-level library API serves as an interface to a range of plug-in encryption modules which allow encryption algorithms to be added in a fairly transparent manner. The standardised API allows any of the algorithms and modes supported by the library to be used with a minimum of coding effort. As such the main function of the library is to provide a standard, portable, easy-to-use interface between the underlying encryption routines and the user software. The high-level API provides services such as routines to export and import encrypted keys, to create and check digital signatures, and to export, query, and import encrypted and signed data objects. In the following discussion all high-level functions will be marked with a `(H)' after the title, all low-level functions will be marked with a `(L)' after the title, and universal functions which are used for both high-level and low-level work will be marked with a `(U)' after the title. You can skip the sections marked `(L)' unless you want to use the low-level routines as building blocks for your own custom encryption protocol. Portability ----------- The library automatically takes care of endianness conversion to the form used by the local system in a manner invisible to the end user. In the case of public-key (PKC) algorithms which work with integers stored as multibyte strings, the user can specify whether the strings are in big- or little-endian order. Building the Library -------------------- Unix The makefile by default will build the statically-linked library. To build the shared library, use 'make shared'. Once the library has been built, use 'make testlib' to build the library self-test program and 'make testapp' to build the test application. For example to build the shared library and self-test program, you would use 'make shared;make testlib'. testlib will run fairly extensive self-tests of the library, you should run this after you've built the library to make sure everything is working OK. testapp will allow PGP-like encryption and signing of data. Depending on your system setup and priviledges you may need to either copy the shared library to /lib or set the LD_LIBRARY_PATH environment variable to use the shared library. For any common Unix system the library will build without any problems, but in some cases you may need to edit lib_rand.c and possible cryptapi.c if you're running an unusual Unix variant which puts include files in strange places or has broken Posix support. If you get compiler errors from lib_rand.c, you may need to change the header files included in the __UNIX__ conditional compilation section. If you get error messages from the linker about mlock(), the problem will be in cryptapi.c. Windows The crypt.mak and crypt32.mak makefiles are for version 1.5x and 4.x of Visual C and will build the 16- and 32-bit DLL versions of the library. The IDEA code will give many warnings, the DES code will give one or two warnings, and the bnlib code will give one warning (these are all other peoples code), the remaining code should compile without warnings. Once the DLL has been built, test.mak and test32.mak will build the library self-test program, which is a console application. You should run this after you've built the library to make sure everything is working OK. The use of the 16-bit DLL on a Win32 system is not recommended, as the randomness-polling required by some of the high-level public-key related routines performs very poorly in the emulated 16-bit environment. DOS No makefile is provided for the DOS version, but the library can be built from the same files as the 16-bit Windows version. The library is about 300K in size (200K of which is code), and any attempt to use the high-level key export routines will fail with a CRYPT_NORANDOM error code unless a /dev/random-style driver is available because there isn't any way to reliably obtain random data under DOS. Using the library under DOS is possible, but not recommended. Other systems The library should be fairly portable to other systems, the only two parts which need attention is the memory locking in cryptapi.c (the library will work without this, but won't be as secure as a version with memory locking because sensitive data may be paged out to disk) and the randomness-gathering in lib_rand.c (the library won't work without this, the code will generate a compiler error). The idea behind the randomness-gathering code is to perform a comprehensive poll of every possible entropy source in the system in a separate thread or background task ("slowPoll"), as well as providing a less useful but much faster poll of quick-response sources ("fastPoll"). To find out what to compile, look at the Unix makefile which contains all the necessary source files (the OBJS dependencies) and compiler options (the bnlib files need to have NDEBUG and HAVE_CONFIG_H=1 defined). Link all these into a library (as the makefile does) and then compile and link testlib.c and testhl.c to create the self-test program. There is additional code included which will lead to noticeable speedups on some systems. bnlib includes 68000, 68020, and PPC asm routines (as well as Unix 80x86, i960 and Alpha code which isn't currently used by the Unix makefile). You should modify your build options as appropriate to use these if possible. Depending on your compiler you may get a few warnings about code in libdes (one or two), bnlib (one or two), and the IDEA code (many). This code is by other people and I haven't bothered changing it to get rid of warnings because they're mostly related to the use of C as a high-level assembler and changing things around could be risky. Interfacing with the Library (U) -------------------------------- All necessary constants, types, structures, and function prototypes are defined in the crypt API header file capi.h. You need to include this file in each module which uses the encryption library. In addition in some environments you will need to call the cryptInit() and cryptEnd() functions at the start and finish of your program. cryptInit() initializes the library for use, and cryptEnd() performs various cleanup functions including automatic garbage collection of any encryption information you may have forgotten to destroy. Where the encryption library is implemented as a DLL, there is no need to call cryptInit() or cryptEnd() as these are called automatically by the operating system when the DLL is loaded and unloaded. If you call cryptInit() and the library has already been initialized, it will return CRYPT_INITED. If you call cryptEnd() and the library hasn't been initialized yet, it will return CRYPT_NOTINITED. These aren't really serious errors, but can serve as a notification that there's something wrong in your code. To make the use of cryptEnd() easier, you may want to use the C atexit() function to have cryptEnd() called automatically when your program exits. Any use of the encryption library when it isn't implemented as a DLL will then be as follows: #include "capi.h" cryptInit(); /* Use encryption library routines */ cryptEnd(); Creating/Destroying Encryption Contexts (U) ------------------------------------------- Encryption contexts are the basic objects which the library works with. You can't do anything without them. To create an encryption context, you must specify the encryption algorithm and mode you want to use for that context. The encryption algorithms and modes are given in the library header file, and are updated along with the library itself. For example, to create and destroy an encryption context for DES in CBC mode, you would use the following code: CRYPT_CONTEXT cryptContext; cryptCreateContext( cryptContext, CRYPT_ALGO_DES, CRYPT_MODE_CBC ); /* Perform en/decryption */ cryptDestroyContext( cryptContext ); If you don't want to choose an encryption algorithm and mode, you can let the library do it for you by using CRYPT_USE_DEFAULT in place of the algorithm or mode. The default encryption algorithm is two-key triple DES, the default encryption mode is CFB. In the case of the public-key (PKC) algorithms the encryption mode is either CRYPT_MODE_PUBKEY or CRYPT_MODE_PRIVKEY, depending on whether you want to perform private-key or public-key operations within that encryption context. For example to create and destroy an encryption context for checking an RSA signature you would use the following code: CRYPT_CONTEXT cryptContext; cryptCreateContext( &cryptContext, CRYPT_ALGO_RSA, CRYPT_MODE_PUBKEY ); /* Perform signature check */ cryptDestroyContext( cryptContext ); Like standard encryption contexts, PKC contexts will accept CRYPT_USE_DEFAULT for the algorithm to tell the library to choose the algorithm for you. The default PKC algorithm is RSA. In the case of the hash algorithms the encryption mode is usually CRYPT_MODE_NONE since the algorithms require no key, except for keyed hash functions (MACs). For example to create and destroy an encryption context for creating an MD5 hash of a message you would use the following code: CRYPT_CONTEXT cryptContext; cryptCreateContext( &cryptContext, CRYPT_ALGO_MD5, CRYPT_MODE_NONE ); /* Perform hash */ cryptDestroyContext( cryptContext ); Like standard encryption contexts, hash contexts will accept CRYPT_USE_DEFAULT for the algorithm to tell the library to choose the algorithm for you. The default hash algorithm is SHA-1. Note that the CRYPT_CONTEXT is passed to cryptCreateContext() by reference, as cryptCreateContext() modifies it when it creates the encryption context. In all other routines in the encryption library, CRYPT_CONTEXT is passed by value. The availability of certain algorithms and encryption modes in the library does not mean that their use is recommended. Some are only present because they are needed for certain protocols or required by some standards. The CRYPT_USE_DEFAULT choice for cryptCreateContext() will always choose a secure algorithm. Some high-level functions will create encryption contexts for you, for example cryptImportKey() will create an encryption context for the imported key. These can be destroyed with cryptDestroyContext() just as if they had been directly created with cryptCreateContext(). The cryptCreateContext() and cryptDestroyContext() functions take care of issues like initialization, memory management, and erasure of data after use. Although you should try to call cryptDestroyContext() for every cryptCreateContext(), this is sometimes not possible, so the library performs automatic garbage collection of any remaining encryption contexts when cryptEnd() is called. Extended Initialization (U) --------------------------- The cryptCreateContext() function has a companion function cryptCreateContextEx() which may be used to perform an extended, algorithm-specific initialisation. The second parameter passed to the function is an algorithm-dependant structure used to specify extra information to be used in the initalisation. Not all algorithms will support extended initialisation parameters. The structures have the name CRYPT_INFO_, and are laid out as follows: Structure CRYPT_INFO_DES: /* Whether to use DES or DESX. The default is to use DES */ int isDESX; Structure CRYPT_INFO_3DES: /* Whether to use two-key or three-key triple DES. The default is to use two-key triple DES as per various ANSI and ISO standards */ int isThreeKey; Structure CRYPT_INFO_BLOWFISH: /* Whether to use Blowfish or Blowfish-SK. The default is to use the original algorithm Blowfish. Blowfish-SK is a version which improves the key schedule to simplify the setup process, allow longer keys, and allow the number of key setup iterations to be specified */ int useBlowfishSK; /* The number of iterations used during the Blowfish-SK key setup process triggered by a cryptLoadContext() call. Using a high value (10 or more) will greatly slow down password-guessing attacks by making the key setup process painfully slow. Values in the range of 10-20 are recommended for most systems */ int keySetupIterations; Structure CRYPT_INFO_MDCSHS: /* The number of iterations used during the key setup process triggered by a cryptLoadContext() call. Using a high value (500 or more) will greatly slow down password-guessing attacks by making the key setup process painfully slow. Values in the range of 50-100 are recommended for most systems */ int keySetupIterations; Structure CRYPT_INFO_RC5: /* The number of rounds of encryption. The default setting is 12 rounds */ int rounds; Structure CRYPT_INFO_SAFER: /* Whether to use SAFER or SAFER-SK. The default is to use the original algorithm SAFER. SAFER-SK is a version which improves the key schedule to eliminate some possible weaknesses when SAFER is used as a keyed hash function */ int useSaferSK; /* The number of rounds of encryption. The default settings are 6 rounds for SAFER-K64, 8 rounds for SAFER-SK64, and 10 rounds for SAFER-K128 and SAFER-SK128 */ int rounds; Structure CRYPT_INFO_SHA: /* Whether to use SHA or SHA1. The default is to use SHA1 */ int isSHA; You can specify that the default value for the algorithm and mode be loaded into a field of the CRYPT_INFO_xxx structure by setting it to CRYPT_USE_DEFAULT. The library will then set the field to the appropriate value. For example to create a SAFER encryption context using the improved SAFER-SK key schedule and with the standard number of rounds for SAFER-SK, you would use: CRYPT_INFO_SAFER cryptInfoEx; cryptInfoEx.rounds = CRYPT_USE_DEFAULT; cryptInfoEx.useSaferSK = 1; cryptCreateContextEx( &cryptContext, cryptAlgo, cryptMode, &cryptInfoEx ); Generating and Deriving Keys into Encryption Contexts (H) --------------------------------------------------------- Once you've created an encryption context, the next step is to load a key into it. These keys will typically be either one-off session keys which are discarded after use, or long-term storage keys which are used to protect fixed data such as files or private keys. You can create a session key with cryptGenerateContext(): cryptGenerateContext( cryptContext, CRYPT_USE_DEFAULT ); which will generate a default-length key for the encryption context. If you want to generate a key of a particular length, you can specify it as the second parameter. For example to generate a 256-bit (32-byte) key you would use: cryptGenerateContext( cryptContext, 32 ); Keys generated by the library are useful when used with cryptExportKey()/ cryptImportKey() (described later on). Since cryptExportKey() usually encrypts the generated key using public-key encryption, you shouldn't make it too long or it'll be too big to be encrypted. Unless there's a specific reason for choosing the key length you should leave the length parameter set to CRYPT_USE_DEFAULT and let the library choose the correct key length for you. The only time when you may need to explicitly specify a key length is when you're using very short (in the vicinity of 512 bits) public keys to export Blowfish, MDC/SHS, or triple DESX keys. In this case the public key isn't large enough to export the full-length keys for these algorithms, and cryptExportKey() will return the error code CRYPT_DATASIZE to indicate that there's too much data to export. The solution is to either specify a shorter key length for cryptGenerateContext(), or, preferably, to use a longer public key. This is only a problem with very short public keys, when using the recommended public key size of 1024 bits this situation will never occur. Before you use cryptGenerateContext() you should read the section "Random Numbers" below since an understanding of this is essential before you use cryptGenerateContext(). If you don't manage the use of random data properly, cryptGenerateContext() will fail with CRYPT_NORANDOM. cryptGenerateContext() only makes sense for conventional-key encryption contexts and will return the error code CRYPT_NOTAVAIL for a PKC or hash encryption context to indicate that this operation is not available for PKCs or hash algorithms. To summarise the steps so far, you can set up an encryption context in its simplest form so that it's ready to encrypt data with: CRYPT_CONTEXT cryptContext; cryptCreateContext( &cryptContext, CRYPT_USE_DEFAULT, CRYPT_USE_DEFAULT ); cryptGenerateContext( cryptContext, CRYPT_USE_DEFAULT ); /* Encrypt data */ cryptDestroyContext( cryptContext ); Note the extensive use of default parameters. Unless you choose to override them, you can rely on the library to choose the correct algorithm and options for you in each case. Sometimes you will need to obtain a fixed-length encryption key from a variable-length password or passphrase. You can do this with the cryptDeriveContext() function: cryptDeriveContext( cryptContext, passPhrase, passPhraseLength ); which takes a passphrase and converts it into an encryption key in a format suitable for use with the encryption context. By default this will repeatedly hash the input passphrase with the SHA1 hash function to generate the key, and will iterate the hashing process 100 times to make a passphrase-guessing attack more difficult. If you want to use a hash function other than SHA1 or use an iteration count of other than 10 iterations, you can use cryptDeriveContextEx(), which takes a hash algorithm and iteration count parameters in addition to the other cryptDeriveContext() parameters: cryptDeriveContext( cryptContext, passPhrase, passPhraseLength, hashAlgorithm, iterationCount ); For example to derive an encryption key from the passphrase "This is a passphrase", using 100 iterations of RIPEMD-160 instead of the default 10 iterations of SHA1, you would use: char *passPhrase = "This is a passphrase"; cryptDeriveContextEx( cryptContext, passPhrase, strlen( passPhrase ), CRYPT_ALGO_RIPEMD160, 100 ); To summarise the steps so far, you can set up an encryption context in its simplest form so that it's ready to encrypt data with: CRYPT_CONTEXT cryptContext; cryptCreateContext( &cryptContext, CRYPT_USE_DEFAULT, CRYPT_USE_DEFAULT ); cryptDeriveContext( cryptContext, passPhrase, strlen( passPhrase ) ); /* Encrypt data */ cryptDestroyContext( cryptContext ); Note again the use of default parameters. Unless you choose to override them, you can rely on the library to choose the correct algorithm and options for you in each case. Loading Keys into Encryption Contexts (L) ----------------------------------------- You can also manually load a key into an encryption context with the cryptLoadContext() function. For example to load the key "Secret key" into a conventional encryption context you would use: cryptLoadContext( cryptContext, "Secret key", 10 ); Unless you need to perform low-level key management yourself, you should use the high-level routines to load keys into encryption contexts. The previous key load should really have been done with cryptDeriveContext() rather than cryptLoadContext(). Some hardware modules which enforce red/black separation will not allow plaintext keys to pass across the library interface. In this case the key parameter passed to cryptLoadContext() will be a key selector or key encryption key to be passed to the underlying hardware. For example to pass a key selector to a key stored inside a DES hardware module you would use: cryptLoadContext( cryptContext, &keySelector, sizeof( keySelector ) ); For PKC encryption a key will typically have a number of components so you can't load the key directly. Instead you load the key components into a CRYPT_PKCINFO structure and then pass this to cryptLoadContext(): cryptLoadContext( cryptContext, &rsaKey, CRYPT_UNUSED ); More information on working with CRYPT_PKCINFO data structures is given below. For conventional-key encryption contexts you can also load an IV into the context, although for encryption you would usually leave this to the library to perform automatically. To load an IV you would use: cryptLoadIV( cryptContext, iv, ivSize ); To retrieve an IV which has been generated by the library you would use: cryptRetrieveIV( cryptContext, iv ); cryptLoadIV() and cryptRetrieveIV() will return the error code CRYPT_NOTAVAIL for a PKC encryption context to indicate that these operations are not available for PKCs. The hash algorithms usually don't require keys or IV's, and cryptLoadIV() and cryptRetrieveIV() will return the error code CRYPT_NOTAVAIL for hash encryption contexts. The cryptLoadContext(), cryptLoadIV(), and cryptRetrieveIV() functions take care of issues like initialization, memory management, endianness conversion, and key setup. If you need to reserve space for conventional and PKC keys, IV's, and hash values, you can use the CRYPT_MAX_KEYSIZE, CRYPT_MAX_PKCSIZE, CRYPT_MAX_IVSIZE, and CRYPT_MAX_HASHSIZE defines to determine the mount of memory you need. No key, IV, or hash value used by the library will ever need more storage than the settings given in these defines. Working with PKC Keys (L) ------------------------- Since PKC keys have multiple components, you can't pass them directly to cryptLoadContext(). Instead, you load them into a CRYPT_PKCINFO structure and then pass that to cryptLoadContext(). There are several CRYPT_PKCINFO structures, one for each type of PKC supported by the library. These have the following members: Structure CRYPT_PKCINFO_DH (Diffie-Hellman key exchange): p Prime g Base Structure CRYPT_PKCINFO_RSA (Rivest-Shamir-Adelman public-key encryption): n Modulus e Public exponent d Private exponent p Prime factor 1 q Prime factor 2 u Mult.inverse of q, mod p e1 Private exponent 1 (PKCS) e2 Private exponent 2 (PKCS) Structure CRYPT_PKCINFO_DSA (Digital Signature Algorithm digital signatures): p Prime modulus q Prime divisor g Element of order q mod p x Public random integer y Private random integer The components of CRYPT_PKCINFO_DH need not be present for the library routines to work. The library has built-in values for 512-bit, 768-bit, 1024-bit, 1280-bit, 1536-bit, 2048-bit, 3072-bit, and 4096-bit Diffie-Hellman key exchange, and will use those if the components are not set. The values used by the library are taken from the SKIP standard. The e1 and e2 components of CRYPT_PKCINFO_RSA may not be present in some keys. The library will make use of them if they are present, but can also work without them. Private-key operations are marginally faster if these two values are present. The multibyte integer strings which make up PKC keys can be stored in either big-endian or little-endian format. If the integer string is little-endian, it will be stored in the format: "xxxxxxxxxxxxxxx00000000000000000000000000000000000000" (where "xxx" are the digits of the number). If the integer string is big-endian, it will be stored in the format: "000000000000000000000000000000000000000000000xxxxxxxxxxxxxxx" For example the number "123456789" would be stored in little-endian format as: "987654321000000000000000000000000000000000000000000000000000" (with the least-significant digit stored first, going through to the most-significant digit, and padded with zeroes to fill the number). In big-endian format this is: "000000000000000000000000000000000000000000000000000123456789" In practice the numbers won't be stored with excessively long precision as they are in the above examples, so instead of being stored with 60 digits of precision of which 51 bytes contain zero padding, they would be stored with 9 digits of precision: "987654321" and: "123456789" A multibyte integer therefore consists of two parameters, the data itself and the precision to which it is stored. When you load multibyte integer components into a CRYPT_PKCINFO structure you need to specify both of these parameters. Before you can use the CRYPT_PKCINFO structure, you need to initialise it with cryptInitComponents(), which takes as parameter the endianness of the multibyte integer strings which make up PKC keys, either CRYPT_COMPONENTS_LITTLENDIAN or CRYPT_COMPONENTS_BIGENDIAN, and the type of the key, either CRYPT_KEYTYPE_PRIVATE or CRYPT_KEYTYPE_PUBLIC: CRYPT_PKCINFO_RSA rsaKey; cryptInitComponents( rsaKey, CRYPT_COMPONENTS_LITTLENDIAN, CRYPT_KEYTYPE_PRIVATE ); Now you can initialise whatever parameters you need with the multibyte integer strings used in PKC's by using cryptSetComponent(), specifying the integer length in bits: cryptSetComponent( rsaKey.n, modulus, 1024 ); cryptSetComponent( rsaKey.e, pubExponent, 17 ); cryptSetComponent( rsaKey.d, privExponent, 1024 ); You can now pass the result to cryptLoadContext() as explained above. Once you've finished working with the CRYPT_PKCINFO information, use cryptDestroyComponents() to destroy the information: cryptDestroyComponents( rsaKey ); Since the library has built-in default values for 512-bit, 768-bit, 1024-bit, 1280-bit, 1536-bit, 2048-bit, 3072-bit, and 4096-bit Diffie-Hellman key exchange, you don't need to specify these unless you've generated your own values and would prefer to use these instead of the default ones. To use the default values, call cryptInitComponents() with the size in bits of the key and the key type set to CRYPT_UNUSED: CRYPT_PKCINFO_DH dhKey; cryptInitComponents( dhKey, 1024, CRYPT_UNUSED ); The library will then use its default keys when you call cryptLoadContext(). Encrypting/Decrypting Data (U) ------------------------------ Now that the encryption context is set up, you're ready to encrypt or decrypt data. To encrypt or decrypt a block of data you use: cryptEncrypt( cryptContext, buffer, length ); and: cryptDecrypt( cryptContext, buffer, length ); If a block encryption mode is being used, these functions will recognise a call with length = 0 as a courtesy call to indicate that this is the last data block and will take whatever special action is necessary for this case. Hash algorithms don't actually encrypt the data being hashed and can be called via cryptEncrypt() or cryptDecrypt(). They require a final call with length = 0 as a courtesy call to indicate to the hash function that this is the last data block and will take whatever special action is necessary for this case. The hash value can be retrieved by calling cryptQueryContext() to query the encryption context information, as explained below. If you call cryptEncrypt() or cryptDecrypt() after the final call with length = 0, the function will return CRYPT_COMPLETE to indicate that the hashing has completed and cannot be continued. The public-key algorithms encrypt a single block of data equal in length to the size of the public key being used. For example if you are using a 1024-bit public key then the length of the data to be encrypted should be 128 bytes. Since the length is fixed, the length parameter should be set to CRYPT_USE_DEFAULT to tell the function to use whatever length is appropriate for the key. Preparation of the block of data to be encrypted requires special care; in general you should use high-level functions such as cryptExportKey()/ cryptImportKey() and cryptCreateSignature()/cryptCheckSignature() rather than cryptEncrypt() and cryptDecrypt() when working with public-key algorithms. If the en/decryption operation fails due to incorrect public or private key parameters or incorrectly formatted input data, the function will return CRYPT_PKCCRYPT to indicate that the operation failed. If the encryption context doesn't support the operation you are trying to perform (for example calling cryptEncrypt() with a DSA public key), the function will return CRYPT_NOTAVAIL to indicate that this functionality is not available. If the key loaded into an encryption context doesn't allow the operation you are trying to perform (for example calling cryptDecrypt() with an encrypt-only key), the function will return CRYPT_KEYPERM to indicate that the context doesn't have the required key permissions to perform the requested operation. Exchanging Keys (H) ------------------- Although you can now encrypt and decrypt data with your encryption context, the key you're using is locked inside the context and (if you used cryptGenerateContext() to create it) won't be known to you or the person you're trying to communicate with. To share the key with another party, you need to export it from the context and the other party needs to import it into an encryption context of their own. To do this, you use the cryptExportKey() and cryptImportKey() functions in combination with a public-key encryption context. Let's say you've created a key in an encryption context cryptContext and want to send it to someone whose public key is in the encryption context publicKeyContext (you can also pass in a private key if you want, cryptExportKey() will only use the public key components, although unless you're the government it's not clear why you'd want to be in posession of someone elses private key). To do this you'd use: CRYPT_CONTEXT publicKeyContext, cryptContext; BYTE *encryptedKey; int encryptedKeyLength; /* Generate a key */ cryptCreateContext( &cryptContext, CRYPT_USE_DEFAULT, CRYPT_USE_DEFAULT ); cryptGenerateContext( cryptContext, CRYPT_USE_DEFAULT ); /* Export the key using a public-key encrypted blob */ cryptExportKey( encryptedKey, &encryptedKeyLength, publicKeyContext, cryptContext ); The resulting public-key encrypted blob is placed in the memory buffer pointed to by encryptedKey, and the length is stored in encryptedKeyLength. This leads to a small problem: How do you know how big to make the buffer? The answer is to use cryptExportKey() to tell you. If you pass in a null pointer for encryptedKey, the function will set encryptedKeyLength to the size of the resulting blob, but not do anything else. You can then use code like: cryptExportKey( NULL, &encryptedKeyLength, publicKeyContext, cryptContext ); encryptedKey = malloc( encryptedKeyLength ); cryptExportKey( encryptedKey, &encryptedKeyLength, publicKeyContext, cryptContext ); to create the exported key blob. Alternatively, you can just allocate a reasonably sized block of memory and use that to hold the encrypted key. "Reasonably sized" means a few Kb, a 4K block is plenty (an encrypted key blob for a 1024-bit public key is only about 200 bytes long). If the encryption context contains too much data to encode using the given public key (for example trying to export an encryption context with a 600-bit key using a 512-bit public key) the function will return CRYPT_DATASIZE. As a rule of thumb a 1024-bit public key should be large enough to export the default key sizes for any encryption context. You don't need to use public-key encryption to export a key blob, it's also possible to use a conventional key to export another conventional key. For example if you were using the key "This is a secret key" in an encryption context sharedContext which was also known to the other party, you would use: CRYPT_CONTEXT sharedContext, cryptContext; BYTE *encryptedKey; int encryptedKeyLength; /* Load the key into an encryption context */ cryptCreateContext( &sharedContext, CRYPT_USE_DEFAULT, CRYPT_USE_DEFAULT ); cryptDeriveContext( sharedContext, "This is a secret key", 20 ); /* Generate a key */ cryptCreateContext( &cryptContext, CRYPT_USE_DEFAULT, CRYPT_USE_DEFAULT ); cryptGenerateContext( cryptContext, CRYPT_USE_DEFAULT ); /* Export the key using a conventionally encrypted blob */ cryptExportKey( encryptedKey, &encryptedKeyLength, sharedContext, cryptContext ); This kind of key export isn't as convenient as using public keys since it requires that both sides know the encryption key in sharedContext. Now that you've exported the key, the other party needs to import it. This is done using the cryptImportKey() function and the private key corresponding to the public key used by the sender: CRYPT_CONTEXT privateKeyContext, cryptContext; /* Import the key from the public-key encrypted blob */ cryptImportKey( encryptedKey, privateKeyContext, &cryptContext ); All the parameters necessary to recreate the encryption context cryptContext are transmitted in encrypted form in the exported key blob, and cryptImportKey() takes care of initialising the encryption context and setting up the key inside it. A single function call is all it takes to import a key. To summarise, sharing an encryption context between two parties using public-key encryption involves the following steps: /* Party A creates the required encryption context and generates a key into it */ cryptCreateContext( &cryptContext, CRYPT_USE_DEFAULT, CRYPT_USE_DEFAULT ); cryptGenerateContext( cryptContext, CRYPT_USE_DEFAULT ); /* Party A exports the key using party B's public key */ cryptExportKey( encryptedKey, &encryptedKeyLength, publicKeyContext, cryptContext ); /* Party B imports the key using their private key */ cryptImportKey( encryptedKey, privateKeyContext, &cryptContext ); Doing the same with conventional encryption involves changing the last two steps to: /* Party A exports the key using the shared conventional key */ cryptExportKey( encryptedKey, &encryptedKeyLength, sharedContext, cryptContext ); /* Party B imports the key using the shared conventional key */ cryptImportKey( encryptedKey, sharedContext, &cryptContext ); The library supports a third kind of key export/import which doesn't actually export or import a key but merely provides a means of agreeing on a shared secret key with another party. You don't need to use cryptGenerateContext() or cryptDeriveKey()/cryptLoadContext() for this one, the act of performing the key exchange will create a random, secret shared key. To use this form of key exchange, both parties call cryptExportKey() to generate the blob to send to the other party, and then both in turn call cryptImportKey() to import the blob sent by the other party. Since there's a two-way exchange of messages, either both parties must create an identical "template" encryption context so cryptExportKey() knows what kind of key to export, or one party has to wait for the initial exported key blob from the other party and create an identical template to export. For now lets assume that both sides know they'll be using Blowfish in CFB mode. The first step of the key exchange is thus: /* Create the key template */ cryptCreateContext( &cryptContext, CRYPT_ALGO_BLOWFISH, CRYPT_MODE_CFB ); /* Export the key using the template */ cryptExportKey( encryptedKey, &encryptedKeyLength, dhContext, cryptContext ); cryptDestroyContext( cryptContext ); Note that there's no need to load a key into the template, since this is generated automatically as part of the export/import process. In addition the template is destroyed once the key has been exported, since there's no further use for it - it acts merely as a template to tell cryptExportKey() what to do. Both parties now exchange encryptedKey blobs, and then use: cryptImportKey( encryptedKey, dhContext, &cryptContext ); to create the cryptContext containing the shared key. If one side wants to control the encryption type, this exchange becomes somewhat more complicated since a simultaneous exchange of encryptedKey blobs is no longer possible. Lets assume that party A gets to set the encryption type being used: /* Party A creates the key template and exports it to party B */ cryptCreateContext( &cryptContext, CRYPT_ALGO_BLOWFISH, CRYPT_MODE_CFB ); cryptExportKey( encryptedKey, &encryptedKeyLength, dhContext, cryptContext ); cryptDestroyContext( cryptContext ); /* Party B imports the encrypted key blob and uses the resulting cryptContext as a template to create their own encrypted key blob for party A */ cryptImportKey( encryptedKey, dhContext, &cryptContext ); cryptExportKey( encryptedKey, &encryptedKeyLength, dhContext, cryptContext ); /* Party A imports the encrypted key blob created by party B */ cryptImportKey( encryptedKey, dhContext, &cryptContext ); If the en/decryption part of the export/import operation fails due to incorrect public or private key parameters, the function will return CRYPT_PKCCRYPT to indicate that the operation failed. If the input data or decrypted data is corrupt and the key couldn't be recovered, the function will return CRYPT_BADDATA. In general CRYPT_PKCCRYPT will be returned for incorrect public or private key parameters and CRYPT_BADDATA will be returned if the input data has been corrupted. You can treat both of these as "key export/import failed" unless you want to include special-case error handling for them. Before you use cryptExportKey() you should read the section "Random Numbers" below since an understanding of this is essential before you use cryptExportKey(). If you don't manage the use of random data properly, cryptExportKey() will fail with CRYPT_NORANDOM. Signing Data (H) ---------------- PKC's can also be used to generate digital signatures. A digital signature is created by signing the contents of a hash context with a private key to create a signature blob and verified by checking the signature blob with the corresponding public key. To do this, you use the cryptCreateSignature() and cryptCheckSignature() functions in combination with a public-key encryption context. Let's say you've hashed some data with a hash context hashContext and want to sign it with your private key in the encryption context privateKeyContext. To do this you'd use: CRYPT_CONTEXT privateKeyContext, hashContext; BYTE *signature; int signatureLength; /* Generate a hash context */ cryptCreateContext( &hashContext, CRYPT_USE_DEFAULT, CRYPT_USE_DEFAULT ); /* Hash the data */ cryptEncrypt( hashContext, data, dataLength ); cryptEncrypt( hashContext, data, 0 ); /* Sign the hash to create a signature blob */ cryptCreateSignature( signature, signatureLength, privateKeyContext, hashContext ); The resulting signature blob is placed in the memory buffer pointed to by signature, and the length is stored in signatureLength. This leads to the same problem with allocating the buffer which was described above with cryptExportKey(), and the solution is again the same: You use cryptCreateSignature() to tell you how big to make the buffer. If you pass in a null pointer for signature, the function will set signatureLength to the size of the resulting blob, but not do anything else. You can then use code like: cryptCreateSignature( NULL, &signatureLength, privateKeyContext, hashContext ); signature = malloc( signatureLength ); cryptCreateSignature( signature, &signatureLength, privateKeyContext, hashContext ); to create the signature blob. Alternatively, you can just allocate a reasonably sized block of memory and use that to hold the signature. "Reasonably sized" means a few Kb, a 4K block is plenty (a signature blob for a 1024-bit public key is only about 200 bytes long). If the hash context contains too much data to encode using the given private key (for example trying to sign a hash context with a 768-bit hash using a 512-bit private key) the function will return CRYPT_DATASIZE. As a rule of thumb a 1024-bit private key should be enough to sign any hash context. Now that you've created the signature, the other side needs to check it. This is done using the cryptCheckSignature() function and the public key corresponding to the private key used to create the signature (you can also pass in a private key if you want, cryptCheckSignature() will only use the public key components, although it's not clear why you'd be in posession of someone elses private key). To do this you'd use: CRYPT_CONTEXT privateKeyContext, hashContext; BYTE *signature; int signatureLength; /* Generate a hash context */ cryptCreateContext( &hashContext, CRYPT_USE_DEFAULT, CRYPT_USE_DEFAULT ); /* Hash the data */ cryptEncrypt( hashContext, data, dataLength ); cryptEncrypt( hashContext, data, 0 ); /* Check the signature using the signature blob */ cryptCheckSignature( signature, publicKeyContext, hashContext ); If the en/decryption part of the signature create/check operation fails due to incorrect public or private key parameters, the function will return CRYPT_PKCCRYPT to indicate that the operation failed. If the signature is corrupt and couldn't be recovered, the function will return CRYPT_BADDATA. In general CRYPT_PKCCRYPT will be returned for incorrect public or private key parameters and CRYPT_BADDATA will be returned if the signature has been corrupted. Finally, if the signature doesn't match the hash context, the function will return CRYPT_BADSIG. You can treat all three of these as "signature generation/check failed" unless you want to include special-case error handling for them. Encrypted Object Management (H) ------------------------------- If you look at the previous sections on key exchange and signature generation you'll notice that there are a lot of assumptions made about various parameters. How do you know which private/public keys to use to decrypt an encrypted key blob or check a signature? How do you know which hash algorithm to use to hash the data for a signature check? This is where the libraries object management routines come in. These are used to hide the messy details of working with encrypted and signed data. Each object type wraps up a particular type of data such as signed or encrypted data into a format which the library can work with. The simplest form of object is the RawData object of type CRYPT_OBJECT_RAW_DATA, which encapsulates a block of raw data such as an email message or a file: +--------+ | Data | +--------+ Once you've created the data object, you can perform other operations on it such as encrypting or signing it. To encrypt the data, you encapsulate it within an EncryptedData object of type CRYPT_OBJECT_ENCRYPTED_DATA: +--------------+ +---------------+ +--------+ | | EncryptedData | | Data | | +---------------+ +--------+ | +--------------+ To sign the data, you encapsulate it within a SignedData object of type CRYPT_OBJECT_SIGNED_DATA: +--------------+ +------------+ +--------+ | +-----------+ | SignedData | | Data | | | Signature | +------------+ +--------+ | +-----------+ +--------------+ In this case the SignedData object is followed by a Signature object of type CRYPT_OBJECT_SIGNATURE which contains the signature, although this could be stored seperately. These objects can be nested as required. For example to sign and encrypt a block of data, you would combine the previous two types of encapsulation to obtain: // +----------------------------------// | +--------------+ // +-----------------+ +---------------+ +------------+ +--------+ | // | PKCEncryptedKey | | EncryptedData | | SignedData | | Data | |// +-----------------+ +---------------+ +------------+ +--------+ // | +-------------// +----------------------------// // // //-----------+ // | //-----------+ | //| Signature | | // +-----------+ | // | //-----------------+ // Here another type of object, a PKCEncryptedKey object of type CRYPT_OBJECT_PKCENCRYPTED_KEY, is prepended to the outermost EncryptedData object to allow the recipient to decrypt it. This object could also be an EncryptedKey object of type CRYPT_OBJECT_ENCRYPTED_KEY. This data block is typical of the kind of structure which a program like PGP would create and contains an encrypted session key followed by an encrypted data block containing a digitally signed message. There are three functions which are used to work with these objects, cryptExportObject() to create and export them, cryptImportObject() to import them, and cryptQueryObject() to return information on them. The simplest is cryptQueryObject(), with returns information on an object in a CRYPT_OBJECT_INFO structure: CRYPT_OBJECT_INFO cryptObjectInfo; cryptQueryObject( object, &cryptObjectInfo ); The CRYPT_OBJECT_INFO structure contains the following fields: /* The object type, object size, header size, and payload size */ CRYPT_OBJECT_TYPE type; long size; int headerSize; long payloadSize; /* The encryption algorithm and mode for EncryptedKey, PKCEncryptedKey, Signature, and SignedData objects */ CRYPT_ALGO cryptAlgo; CRYPT_MODE cryptMode; /* The key ID for PKCEncryptedKey and Signature objects, consisting of the key ID hash algorithm and the key ID and key ID length */ CRYPT_ALGO keyIDalgo; unsigned char keyID[ CRYPT_MAX_HASHSIZE ]; int keyIDsize; /* The key derivation algorithm and iteration count for EncryptedKey objects */ CRYPT_ALGO keySetupAlgo; int keySetupIterations; /* The control vector and algorithm-specific information for EncryptedKey objects. The algorithm-specific information can be passed directly to cryptCreateContextEx() for any algorithm (even those which would normally use cryptCreateContext()) */ long controlVector; void *cryptContextExInfo; /* The cookie and cookie size for EncryptedKey, PKCEncryptedKey, EncryptedData, and SignedData objects */ unsigned char cookie[ CRYPT_MAX_HASHSIZE ]; int cookieSize; /* The IV and IV size for EncryptedData objects */ unsigned char iv[ CRYPT_MAX_IVSIZE ]; int ivSize; You'll never need to use most of these fields (or even know they exist) unless you're writing your own object managment routines. The only really useful fields you need to know about for now are the four at the start, which contain the object type (such as CRYPT_OBJECT_SIGNED_DATA for a SignedData object), the total object size, the size of the objects header, and the payload size. The object header contains information on the object such as type, length, and various attributes. The payload size is the size of the actual data in the object: <------------- size -------------> +- Object -------+-----------------+ | | | | Header | Payload | | | | +----------------+-----------------+ <- headerSize -> <- payloadSize -> The payload is either another object or raw data. Now that we have a way of obtaining information on objects, we can construct a general-purpose handler for processing them: CRYPT_OBJECT_INFO cryptObjectInfo; cryptQueryObject( object, &cryptObjectInfo ); switch( cryptObjectInfo.type ) { case CRYPT_OBJECT_PKCENCRYPTED_KEY: cryptImportKey( object, ... ); break; case CRYPT_OBJECT_ENCRYPTED_KEY: cryptImportKey( object, ... ); break; case CRYPT_OBJECT_SIGNATURE: cryptCheckSignature( object, ... ); break; ... } In order to process the various types of data objects, we use the cryptImportObject() function. The simplest type of object is the RawData object: int payloadStart; long payloadLength; cryptImportObject( object, &payloadStart, &payloadLength ); The data contained in the object begins at [ object + payloadStart ] and continues for payloadLength bytes. Other object types are more complex and involve the use of encryption contexts. These require the use of the extended cryptImportObjectEx() function to import them. For example if cryptQueryObject() reports a SignedData object then we need to pass it a pointer to an encryption context: CRYPT_CONTEXT hashContext; int payloadStart; long payloadLength; cryptImportObjectEx( object, &payloadStart, &payloadLength, &hashContext ); This is how we can tell in advance which hash algorithm we need to use when checking a digital signature - the object tells us what to use, and cryptImportObjectEx() creates and initialises the hash context for us. Checking a SignedData object therefore involves passing the signed data object and an empty hash context to cryptImportObjectEx(), which sets up the hash context ready for use with the signed data. The same thing goes for encrypted data objects: CRYPT_CONTEXT cryptContext; int payloadStart; long payloadLength; cryptImportKey( encryptedKey, privateKeyContext, &cryptContext ); [...] cryptImportObjectEx( object, &payloadStart, &payloadLength, &cryptContext ); The object tells us what encryption parameters to use, and cryptImportObject() sets the encryption context up appropriately. Note that we have to import a key into the encryption context before we can pass it to cryptImportObject(). Typically an EncryptedData object is preceded by an EncryptedKey or PKCEncryptedKey object to allow it to be decrypted. We can now extend the encrypted object handler to the following: CRYPT_OBJECT_INFO cryptObjectInfo; cryptQueryObject( object, &cryptObjectInfo ); switch( cryptObjectInfo.type ) { case CRYPT_OBJECT_PKCENCRYPTED_KEY: cryptImportKey( object, ... ); break; case CRYPT_OBJECT_ENCRYPTED_KEY: cryptImportKey( object, ... ); break; case CRYPT_OBJECT_SIGNATURE: cryptCheckSignature( object, ... ); break; case CRYPT_OBJECT_ENCRYPTED_DATA: cryptImportObjectEx( object, ... ); break; case CRYPT_OBJECT_SIGNED_DATA: cryptImportObjectEx( object, ... ); break; case CRYPT_OBJECT_RAW_DATA: break; } This code provides a skeleton outline for a function which will unwrap arbitrarily nested objects, decrypting and checking signatures as it goes until it finds the raw data which makes up the message, at which point it will stop. This shows how complex encryption systems can be built up using the object management functions. Of course if you're using a relatively fixed message format with (say) an encrypted key followed by encrypted, signed data, you don't need to write such general-purpose code. A simple fixed handler for the nested objects example in the previous diagram can be written as: CRYPT_CONTEXT decryptContext, sessionKeyContext, hashContext; unsigned char *encryptedData, signedData, payload, signature; int payloadStart; long payloadSize; /* Recreate the session key by importing the encrypted key */ cryptImportKey( exportedKey, decryptContext, &sessionKeyContext ); /* Set up the session key to decrypt the encrypted data */ encryptedData = exportedKey + payloadStart + payloadSize; cryptImportObjectEx( encryptedData, &payloadStart, &payloadSize, /* Decrypt the encrypted data */ signedData = encryptedData + payloadStart; cryptDecrypt( sessionKeyContext, signedData, payloadSize ); cryptDestroyContext( sessionKeyContext ); /* Import the signed data object */ cryptImportObjectEx( signedData, &payloadStart, &payloadSize, &hashContext ); /* Hash the signed data */ payload = signedData + payloadStart; cryptEncrypt( hashContext, payload, payloadSize ); cryptEncrypt( hashContext, payload, 0 ); /* Check the signature */ signature = signedData + payloadStart + payloadSize; cryptCheckSignature( signature, signContext, hashContext ); cryptDestroyContext( hashContext ); /* Import the raw data object */ cryptImportObject( payload, &payloadStart, &payloadSize ); Note the use of the payloadStart and payloadSize values to unwrap each layer of nested objects until the raw data is revealed. The routines used to create objects are slightly more complex than the ones used to import them. They work more or less like cryptExportKey() and cryptCreateSignature(), but they export various types of data objects rather than keys or signatures. Unlike the other two functions, they don't export the data itself, only the header or wrapper for the blob. Since the data can be of arbitrary length and may not all be available when you export the blob, this allows you to create the header seperately and then attach the data to it as required. Let's say you want to create the simplest type of object, a RawData object containing 4 bytes of information. To do this you'd use: BYTE *rawDataObject; cryptCreateObject( rawDataObject, &rawDataObjectLength, CRYPT_OBJECT_RAW_DATA, 4 ); The resulting data blob (or at least the header for the blob) is placed in the memory buffer pointed to by rawDataObject, and the length of the blob header is stored in rawDataObjectLength. This leads to the same problem with allocating the buffer which was described above with cryptExportKey() and cryptCreateSignature(), and the solution is again the same: You use cryptExportObject() to tell you how big to make the buffer. If you pass in a null pointer for rawDataObject, the function will set rawDataObjectLength to the size of the resulting blob, but not do anything else. You can then use code like: cryptExportObject( NULL, &rawDataObjectLength, CRYPT_OBJECT_RAW_DATA, 4 ); rawDataObject = malloc( rawDataObjectLength ); cryptExportObject( rawDataObject, &rawDataObjectLength, CRYPT_OBJECT_RAW_DATA, 4 ); to create the data object blob. Alternatively, you can just allocate a reasonably sized block of memory and use that to hold the object blob. "Reasonably sized" means a few hundred bytes, a 1K block is plenty. Some types of data objects are more complex, and require an extended form of the cryptExportObject() function. This is cryptExportObjectEx(), which takes an extra parameter in the form of an encryption context. For an EncryptedData object the context contains the encryption context being used to encrypt the data. For a SignedData object the context contains the hash context being used to hash the data. For example to create a SignedData object you would use: CRYPT_CONTEXT hashContext; BYTE *signedDataObject; int signedDataObjectLength; /* Generate a hash context */ cryptCreateContext( &hashContext, CRYPT_USE_DEFAULT, CRYPT_USE_DEFAULT ); /* Create the SignedData object */ cryptExportContextEx( signedDataObject, &signedDataObjectLength, CRYPT_OBJECT_SIGNED_DATA, hashContext ); You can now hash the data using the hash context and then sign it with cryptCreateSignature(). You don't have to create the SignedData object before you hash the data or create the signature, you can create it at any point during the hashing or signing operation. The same goes for the creation of EncryptedData objects. In both cases the encryption contexts are used merely as templates to tell the library how to process the object. With this information you can now create the code to produce the fixed-format messages shown the previous diagram: CRYPT_CONTEXT sessionKeyContext, hashContext; BYTE *exportedKeyObject, *encryptedDataObject, *signedDataObject; BYTE *rawDataObject, *signature; int encryptedDataObjectSize, signedDataObjectSize, rawDataObjectSize; int signatureSize; /* Create an encryption context for the session key and a hash context for the signature */ cryptCreateContext( &sessionKeyContext, cryptAlgo, CRYPT_MODE_CFB ); cryptGenerateContext( sessionKeyContext ); cryptCreateContext( &hashContext, CRYPT_ALGO_SHA, CRYPT_MODE_NONE ); /* Export the session key */ cryptExportKey( exportedKeyObject, &exportedKeySize, cryptContext, sessionKeyContext ); /* Assemble the EncryptedData, SignedData, and RawData objects */ cryptExportObjectEx( encryptedDataObject, &encryptedDataObjectSize, CRYPT_OBJECT_ENCRYPTED_DATA, signedDataObjectSize + rawDataObjectSize + dataLength + signatureSize, sessionKeyContext ); cryptExportObjectEx( signedDataObject, &signedDataObjectSize, CRYPT_OBJECT_SIGNED_DATA, rawDataObjectSize + dataLength, hashContext ); cryptExportObject( rawDataObject, &rawDataObjectSize, CRYPT_OBJECT_RAW_DATA, dataLength ); memcpy( rawDataObject + rawDataObjectSize, data, dataLength ); /* Hash the RawData object, sign the hash, and store the result in the SignedData object */ cryptEncrypt( hashContext, rawDataObject, dataLength + rawDataObjectSize ); cryptEncrypt( hashContext, rawDataObject, 0 ); cryptCreateSignature( signature, &signatureSize, signContext, hashContext ); cryptDestroyContext( hashContext ); /* Encrypt the SignedData and Signature objects */ cryptEncrypt( sessionKeyContext, signedDataObject, signedDataObjectSize + rawDataObjectSize + dataLength + signatureSize ); cryptDestroyContext( sessionKeyContext ); The buffer management hasn't been shown in the code to simplify it, but consists of using cryptExportObject() and cryptExportObjectEx() with a NULL buffer to query the size of each object, followed by a call to allocate a buffer big enough to hold the objects. The order of the objects in the buffer is: [ ExportedKey ][ EncryptedData[ SignedData[ RawData[ data ] ][ Signature ] ] ] with exportedKeyObject pointing to the start of the buffer, encryptedDataObject pointing to the start of the EncryptedData object in the buffer, and so on. Once everything is complete, the entire buffer can be written to disk or sent to a remote machine as required. A complete example of the above two code fragments is given in the file test.c. A longer example which will process arbitrary-length pieces of data and allows data to be signed, public-key encrypted, conventional-key encrypted, or signed and public/conventional-key encrypted, is given in the file testapp.c. Most of the code in both examples actually consists of buffer management rather than encryption. Random Numbers (U) ------------------ The cryptlib cryptGenerateContext() and cryptExportKey() functions require access to a source of cryptographically strong random numbers. These numbers are obtained by taking system information and stirring it into an internal data pool using the Secure Hash Algorithm. The random-data-gathering operation is controlled with the cryptAddRandom() function, which can be used to either inject your own random information into the internal pool or to tell the library to poll the system for random information. To add your own random data (such as keystroke timings when the user enters a password) to the pool, use: cryptAddRandom( buffer, bufferLength ); The library can also perform its own polling for random information. There are two polling methods, a fast poll which returns immediately and retrieves a moderate amount of random information, and a slow poll which may take some time but which retrieves much larger amounts of random information. A fast poll is performed with: cryptAddRandom( NULL, CRYPT_RANDOM_FASTPOLL ); In general you should sprinkle these throughout your code to build up the amount of randomness in the pool. A slow poll is performed with: cryptAddRandom( NULL, CRYPT_RANDOM_SLOWPOLL ); The effect of this call varies depending on the operating system. Under DOS the call returns immediately (see below). Under Windows 3.x the call will get all the information it can in about 1 second, then return (there is usually more information present in the system than can be obtained in 1 second). Under Unix, Windows 95, and Windows NT the call will spawn one or more separate processes or threads to perform the polling and will return immediately. Before the first call to cryptGenerateContext() or cryptExportKey() you must perform at least one slow poll (or, in some cases, several fast polls - see below) in order to accumulate enough random information in the pool to safely generate a key into an encryption context or export a key. Since many systems will perform a nonblocking poll, you can usually do this by calling the slow poll routine on program startup so that the random information will have accumulated by the time you call cryptGenerateContext() or cryptExportKey(): [ program startup ] cryptAddRandom( NULL, CRYPT_RANDOM_SLOWPOLL ); [ other code, slow poll runs in the background ] cryptGenerateContext( cryptContext, ... ); If you forget to perform a slow poll beforehand, the cryptGenerateContext() or cryptExportKey() call will block until the slow poll completes. The fact that the call is blocking is usually fairly obvious. If no reliable random data is available then functions such as cryptGenerateContext() and cryptExportKey() which require it will return the error CRYPT_NORANDOM. cryptGenerateContext() spawns another slow poll once it completes to refresh the random pool, so after the first call there's no need to perform another slow poll, although it can never hurt to do so, especially if your program is waiting around for other things such as a network or disk I/O request to complete. The information obtained by the slow and fast polls under various operating systems and DOS is as follows: DOS Since DOS is so simple, it provides very little random information. A fast poll simply adds the value of the system clock to a 1-second resolution (which is next to useless). A slow poll relies on the presence of a /dev/random-style driver such as Robert Rothenburg Walking-Owl's noise.sys, which records the timing between keystrokes, variations in disk access times, clock drift while the system is idling at the command line, the variation in EGA/VGA retrace events, and mouse movements. The addition of external random data via cryptaddrandom() is strongly recommended under DOS. Without either this or the presence of a /dev/random-style driver, the random numbers (and therefor the encryption keys) generated by the library will be extremely easy to guess and will provide virtually no security. For this reason any functions which require random numbers will always return CRYPT_NORANDOM unless a /dev/random driver is present or external randomness is added via cryptAddRandom(). At least one /dev/random slow poll is required to accumulate enough randomness for use by the library. Windows 3.x A fast poll adds the number of bytes free in the global heap, the cursor position when the last message was received, a 55ms time for the last message, whether the system queue has any events in it, the number of active tasks, the 55ms time since Windows started, the handle of the window with the mouse capture and input focus, the current mouse cursor and caret position, the largest free memory block, number of lockable pages, number of unlocked pages, number of free and used pages, number of swapped pages, 1ms execution time of the current task and virtual machine, and percentage free and memory segment of the user and GDI heaps. A slow poll adds the linear address, size in bytes, handle, lock count, owner, object type, and segment type of every object in the global heap, the module name, handle, reference count, executable path, and next module link of every loaded module, the task handle, parent task handle, instance handle, stack segment and offset, stack size, number of pending events, task queue, code segment and instruction pointer, and the name of the module executing the task for every running task. At least one slow poll or five fast polls are required to accumulate enough randomness for use by the library. Windows 95 A fast poll adds the handle of the active window, the handle of the window with mouse capture, the handle of the clipboard owner, the handle of the start of the clipboard viwer list, the pseudohandle of the current process, the current process ID, the pseudohandle of the current thread, the current thread ID, the number of milliseconds since Windows started, the handle of the desktop window, the handle of the window with the keyboard focus, whether the system queue has any events in it, the cursor position for the last message in the queue, the millisecond time for the last message in the queue, the handle of the window with the clipboard open, the handle of the process heap, the handle of the processes window station, a bitmap of the types of events in the input queue, the current caret and mouse cursor position, the percentage of memory in use, bytes of physical memory available, bytes of free physical memory, bytes in the paging file, free bytes in the paging file, user bytes of address space, and free user bytes of memory, the thread and process creation time, exit time, time in kernel mode, and time in user mode in 100ns intervals, the minimum and maximum working set size for the current process, the name of the desktop, console window title, position and size for new windows, window flags, and handles for stdin, stdout, and stderr. A slow poll adds the process ID, heap ID, size in bytes, handle, linear address, type, and lock count of every object on the local heap, the module ID, process ID, global usage count, module usage count, base address, size in bytes, handle, and executable path of every module in the system, the usage count, process ID, heap ID, module ID, number of threads, parent process ID, base priority class, and executable path of every running process, and the thread ID, process ID, base priority, and priority level change of every executing thread in the system. At least one slow poll or three fast polls are required to accumulate enough randomness for use by the library. Windows NT A fast poll adds the handle of the active window, the handle of the window with mouse capture, the handle of the clipboard owner, the handle of the start of the clipboard viwer list, the pseudohandle of the current process, the current process ID, the pseudohandle of the current thread, the current thread ID, the number of milliseconds since Windows started, the handle of the desktop window, the handle of the window with the keyboard focus, whether the system queue has any events in it, the cursor position for the last message in the queue, the millisecond time for the last message in the queue, the handle of the window with the clipboard open, the handle of the process heap, the handle of the processes window station, a bitmap of the types of events in the input queue, the current caret and mouse cursor position, the percentage of memory in use, bytes of physical memory available, bytes of free physical memory, bytes in the paging file, free bytes in the paging file, user bytes of address space, and free user bytes of memory, the thread and process creation time, exit time, time in kernel mode, and time in user mode in 100ns intervals, the minimum and maximum working set size for the current process, the name of the desktop, console window title, position and size for new windows, window flags, and handles for stdin, stdout, and stderr. A slow poll adds the number of bytes read from and written to disk, the disk read and write time, the number of read and write operations, the depth of the I/O queue, a large number of network statistics such as the number of bytes sent and received, the number of SMB's sent and received, the number of paging, nonpaging, and cached bytes sent and received, the number of failed initial and failed completion operations, the number of reads, random reads, large/small SMB reads, writes, random writes, large and small SMB writes, the number of reads and writes denied, the number of sessions, failed sessions, reconnects, and hung sessions, and various lan manager statistics, and an equally large number of system performance-related statistics such covering virtually every aspect of the operation of the system (the exact details vary from machine to machine, but the library will query every available performance counter and system monitor). At least one slow poll or three fast polls are required to accumulate enough randomness for use by the library. UNIX A fast poll adds the current time to a reasonably high resolution (usually milliseconds or microseconds), the total user and system time, resident set size, page fault statistics, number of I/O operations, messages sent and received, signals received, number of context switches, and various other system statistics. A slow poll varies with the Unix flavour being used. Under Linux the library will try to use the /dev/random driver, which continually accumulates random data from the system. If this is not present it will use a variety of information on disk I/O, network traffic, NFS traffic, packet filter statistics, multiprocessor statistics, process information, users, VM statistics, process statistics, open files, inodes, terminals, vector processors, streams, and loaded code. The exact data collected depends on the system, but generally includes quite detailed operating statistics and information. At least one slow poll is required to accumulate enough randomness for use by the library. Controlling the Operation of the Library (L) -------------------------------------------- In order to allow you to control various parts of its operation, the library provides a cryptIoctl() function so you can tune its performance. This is called as: cryptIoctl( ioctlCode, ioctlInformation, cryptContext ); The ioctlCode specifies the type of IOCTL function to perform. The ioctlInformation contains the information passed to the library to control its operation. The cryptContext is the context which the option applies to, or NULL if the option is to be applied globally. The IOCTL functions are as follows: CRYPT_IOCTL_NONE Do nothing. CRYPT_IOCTL_MEMORY Change the way in which memory blocks which must store sensitive information are allocated. By default the library will try to lock all pages containing keys and sensitive information to prevent them from being paged to disk, but will continue if the memory locking fails. Specifying CRYPT_MEMORY_NOLOCK as the memory lock type will disable the locking of memory. Specifying CRYPT_MEMORY_LOCK will give the default behaviour. Specifying CRYPT_MEMORY_FORCELOCK will cause memory allocations to fail if the pages cannot be locked, and the library will return the error code CRYPT_NOLOCK. This is a more stringent check than CRYPT_MEMORY_LOCK, which will try to lock the pages but continue anyway if they can't be locked. A CRYPT_IOCTL_MEMORY call will only affect memory allocated after the call is made, so you can't pass a cryptContext to the function. The settings will continue in effect until the next call to CRYPT_IOCTL_MEMORY is made. Locking pages is unnecessary under DOS, impossible under Windows 3.x and Windows 95, possible under Windows NT, and requires root priviledges under Unix. Each encryption context will lock approximately two 4K pages (or equivalent regions) of memory, each call to cryptExportKey() or cryptImportKey() will temporarily lock one page, and the library itself keeps one page locked. This creates no noticeable effect on system performance when used in moderation. CRYPT_IOCTL_KEYCOOKIE CRYPT_IOCTL_SIGCOOKIE Control the export of key and signature cookies. By default the library will export key and signature cookies. You can disable the export of cookies by setting the IOCTL parameter to 0, enable it by setting it to 1, and restore the default behaviour by setting it to CRYPT_USE_DEFAULT. Passing in a cryptContext will set the option for the encryption context, passing in a NULL pointer will set the option for the entire library. An export cookie option set for an encryption context will override an export cookie option set for the entire library. For example if the library as a whole is set to export key cookies but a particular cryptContext is set to not export a cookie, the cookie for that context won't be exported. The structures used by the cryptIoctl() function are as follows: Structure CRYPT_IOCTLINFO_MEMORY: /* What to do with memory blocks */ int memoryLockType; Structure CRYPT_IOCTLINFO_COOKIE: /* What to do with key or signature cookies */ int exportCookie; For example to disable the export of signature cookies for the entire library you would use: CRYPT_IOCTLINFO_COOKIE cryptIoctlInfoCookie; cryptIoctlInfoCookie.exportCookie = FALSE; cryptIoctl( CRYPT_IOCTL_SIGCOOKIE, &cryptIoctlInfoCookie, NULL ); Working with Newer Versions of the Library ------------------------------------------ Your software can automatically support new encryption algorithms and modes as they are added to the library if you check for the range of supported algorithms and modes instead of hard-coding in the values which existed when you wrote the program. In order to support this, the library predefines the values CRYPT_ALGO_FIRST_CONVENTIONAL and CRYPT_ALGO_LAST_CONVENTIONAL for the first and last possible conventional encryption algorithms, CRYPT_ALGO_FIRST_PKC and CRYPT_ALGO_LAST_PKC for the first and last possible public-key encryption algorithms, and CRYPT_ALGO_FIRST_HASH and CRYPT_ALGO_LAST_HASH for the first and last possible hash algorithms. By checking each algorithm within this range using cryptAlgoAvailable(), your software can automatically incorporate any new algorithms as they are added. For example to scan for all available conventional encryption algorithms you would use: CRYPT_ALGO cryptAlgo; for( cryptAlgo = CRYPT_ALGO_FIRST_CONVENTIONAL; cryptAlgo <= CRYPT_ALGO_LAST_CONVENTIONAL; cryptAlgo++ ) if( cryptStatusOK( cryptAlgoAvailable( cryptAlgo ) ) /* Perform action with algorithm */; The action to perform would typically be building a list of available algorithms and allowing the user to choose the one they preferred. The same can be done for the public-key and hash algorithms. The library also predefines CRYPT_MODE_FIRST_CONVENTIONAL and CRYPT_MODE_LAST_CONVENTIONAL which cover the range of available conventional encryption modes. Once you've determined which conventional algorithm to use you can use: CRYPT_MODE cryptMode; for( cryptMode = CRYPT_MODE_FIRST_CONVENTIONAL; cryptMode <= CRYPT_MODE_LAST_CONVENTIONAL; cryptMode++ ) if( cryptStatusOK( cryptModeAvailable( cryptAlgo, cryptMode ) ) /* Perform action with algorithm */; If your code follows these guidelines, it will automatically handle any new encryption algorithms and modes which are added in newer versions of the library. If you are using the shared library or DLL form of the encryption library, your softwares' encryption capabilities will be upgraded every time the library is upgraded. Error Checking -------------- When the library is initialised, each of the built-in encryption algorithms goes through a self-test procedure which checks the implementation using standard test vectors and methods given with the algorithm specification (typically FIPS publications, ANSI standards, or standard reference implementations). This self-test is used to verify that each encryption algorithm is performing as required. Each function in the library performs extensive error checking (although monitoring of error codes has been left out in the code samples below for readability). The file TEST.C, included with the crypt library, includes better error handling than the short examples below. For functions which complete with no error, the library will return CRYPT_OK. For functions which complete with some form of error internal to the library itself, the library will return CRYPT_ERROR (this situation should never occur and should be reported to the library author). Other errors are: CRYPT_SELFTEST The encryption code or hardware has failed an internal self-test. This code is returned if an attempt is made to use the encryption module which failed the self-test. CRYPT_BADPARM1... There is a problem with a parameter passed to a library CRYPT_BADPARM15 function. The exact code depends on the parameter in error. If the function cannot resolve the exact parameter error type it will return CRYPT_BADPARM. CRYPT_NOMEM There is not enough memory available to perform this operation (generally the operation would be the creation of an encryption context or a signature or key export or import operation). CRYPT_NOTINITED The encryption context you have tried to use hasn't been initialised yet. CRYPT_INITED The encryption context you have tried to initialise has already been initialised previously. CRYPT_NOALGO The requested encryption algorithm or mode is unavailable. CRYPT_NOMODE CRYPT_NOKEY The encryption key or IV hasn't been loaded into an CRYPT_NOIV encryption context yet. CRYPT_NOLOCK The operating system cannot lock pages containing sensitive information in memory to prevent them from being swapped to disk. CRYPT_NORANDOM Not enough randomness information is available to the library to perform the requested operation. CRYPT_NOTAVAIL The requested operation is not available for this algorithm (for example an attempt to load an encryption key into a hash algorithm context, or to decrypt a Diffie-Hellman shared integer with an RSA key). CRYPT_KEYPERM The key used does not have the permission to perform this type of operation (for example an encrypt-only key being used for a decrypt operation). CRYPT_WRONGKEY The key being used to decrypt a piece of data is incorrect. CRYPT_INCOMPLETE An operation which consists of multiple steps (such as a message hash) is still in progress and requires further steps before it can be regarded as having completed. CRYPT_COMPLETE An operation which consists of multiple steps (such as a message hash) is complete and cannot be continued. CRYPT_ORPHAN There were were still encryption contexts allocated when cryptEnd() was called. This error type is not serious since the library performs automatic garbage collection of any orphaned encryption contexts, but may serve as a warning that something is wrong in a user program. CRYPT_DATASIZE There is too much data for this function to work with. For a public-key encryption or signature function this means there is too much data for this public/private key to encrypt/sign. You should either use a larger public/private key (in general a 1024-bit or larger key should be sufficient for most purposes) or less data (for example by reducing the key size in the encryption context passed to cryptExportKey()). For a key certificate function this means the amount of data you have supplied is more than what is allowed for the field you are trying to store it in. CRYPT_PKCCRYPT The public/private key encryption/decryption failed, probably due to incorrect public or private key parameters. CRYPT_BADDATA The object (typically encrypted or signed data, or a key certificate) was corrupt and can't be processed. CRYPT_BADSIG The signature did not match the data. The macros cryptStatusError() and cryptStatusOK() can be used to determine whether a return value denotes an error condition, for example: CRYPT_CONTEXT cryptContext; int status; status = cryptCreateContext( &cryptContext, CRYPT_ALGO_IDEA, CRYPT_MODE_CFB ); if( cryptStatusError( status ) ) /* Perform error processing */ The macros cryptIsParamError(), cryptIsResourceError(), cryptIsSecurityError(), cryptIsHighlevelError(), cryptIsKeysetError(), and cryptIsCertError() can be used to determine whether a return value denotes a function parameter error, an error due to insufficient resources, a cryptlib API security violation, a high-level function related error, a key collection related error, or a certificate management error. Note that high-level errors also cover key collection and certificate operations (for example checking a certificate might return CRYPT_BADSIG), so you should check for these as well when you check for classes of key collection or certificate errors. Examples -------- The following examples have had error checking removed for readability. See the test code in TEST.C for an example with full error checking. To encrypt a buffer using DES in CFB mode with the password 0x0123456789ABCDEF and an IV generated automatically by the library: CRYPT_CONTEXT cryptContext; unsigned char key[] = { 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF }; /* Load the key, encrypt the buffer (the IV is automatically generated if we don't specify one, it can be obtained with cryptRetrieveIV()), and destroy the encryption context */ cryptCreateContext( &cryptContext, CRYPT_ALGO_DES, CRYPT_MODE_CFB ); cryptLoadContext( cryptContext, key, sizeof( key ) ); cryptEncrypt( cryptContext, buffer, length ); cryptDestroyContext( cryptContext ); To hash an arbitrary-size passphrase down to the one used by a particular cryptosystem (in this case triple DES) using MD5: CRYPT_QUERY_INFO cryptQueryInfo; CRYPT_CONTEXT cryptContext; unsigned char key[ CRYPT_MAX_KEYSIZE ]; int keySize; /* Find out how long we can make the key */ cryptQueryAlgoMode( CRYPT_ALGO_3DES, CRYPT_MODE_CBC, &cryptQueryInfo ); keySize = cryptQueryInfo->maxKeySize; /* Use all we can */ /* Hash the user key with MD5 and query the encryption context to get the final hash value */ cryptCreateContext( &cryptContext, CRYPT_ALGO_MD5, CRYPT_MODE_NONE ); cryptEncrypt( cryptContext, passphrase, strlen( passphrase ) ); cryptEncrypt( cryptContext, passphrase, 0 ); cryptQueryContext( cryptContext, &cryptQueryInfo ); cryptDestroyContext( cryptContext ); /* Use the hashed value of the passphrase as the key */ memcpy( key, cryptQueryInfo.hashValue, keySize ); Note that this code uses the encryption library querying functions, which will be explained in the next section of the documentation. To encrypt a file using triple DES in CBC mode with the key generated in the previous example: int firstTime = 1; /* Load the previously-generated key */ cryptCreateContext( &cryptContext, CRYPT_ALGO_3DES, CRYPT_MODE_CBC ); cryptLoadContext( cryptContext, key, keySize ); /* Copy the data across, encrypting as we go */ while( ( length = fread( buffer, 1, BUFSIZE, inFile ) ) != 0 ) { /* Encrypt the data in the buffer */ cryptEncrypt( cryptContext, buffer, length ); /* If it's the first block, retrieve the IV and prepend it to the output data. Note the since we've let the library generate the IV for us automatically, we can't retrieve it until after the first cryptEncrypt() call */ if( firstTime ) { CRYPT_QUERY_INFO cryptQueryInfo; unsigned char iv[ CRYPT_MAX_IVSIZE ]; int ivSize; /* Find out how long the IV we're using is */ cryptQueryContext( cryptContext, &cryptQueryInfo ); ivSize = cryptQueryInfo->ivSize; /* Retrieve the IV and write it to the output file */ cryptRetrieveIV( cryptContext, iv ); fwrite( iv, 1, ivSize, outFile ); firstTime = 0; } /* Write the encrypted data to the output file */ fwrite( buffer, 1, length, outFile ); } /* Since CBC is a block cipher, we perform a courtesy close call to let the encryption routines handle the last block */ cryptEncrypt( cryptContext, buffer, 0 ); cryptDestroyContext( cryptContext ); To decrypt the previously encrypted file with the key generated in the previous example: CRYPT_QUERY_INFO cryptQueryInfo; unsigned char iv[ CRYPT_MAX_IVSIZE ]; int ivSize; /* Load the previously-generated key */ cryptCreateContext( &cryptContext, CRYPT_ALGO_3DES, CRYPT_MODE_CBC ); cryptLoadContext( cryptContext, key, keySize ); /* Find out how long the IV we're using is */ cryptQueryContext( cryptContext, &cryptQueryInfo ); ivSize = cryptQueryInfo.ivSize; /* Read the IV from the input file and load it into the encryption context */ fread( iv, 1, ivSize, inFile ); cryptLoadIV( cryptContext, iv, ivSize ); /* Copy the data across, decrypting as we go */ while( ( length = fread( buffer, 1, BUFSIZE, inFile ) ) != 0 ) { /* Encrypt the data in the buffer */ cryptDecrypt( cryptContext, buffer, length ); /* Write the decrypted data to the output file */ fwrite( buffer, 1, length, outFile ); } /* Since CBC mode implements a block cipher, we perform a courtesy close call to let the encryption routines handle the last block */ cryptDecrypt( cryptContext, buffer, 0 ); cryptDestroyContext( cryptContext ); To export an RSA-encrypted triple DES session key contained in the sessionKeyContext encryption context for an encrypted message exchange: CRYPT_CONTEXT cryptContext, sessionKeyContext; CRYPT_PKCINFO_RSA rsaKey; BYTE *encryptedKey; int encryptedKeyLength; /* Create an RSA encryption context */ cryptCreateContext( cryptContext, CRYPT_ALGO_RSA, CRYPT_MODE_PKC ); cryptInitComponents( rsaKey, CRYPT_COMPONENTS_BIGENDIAN, CRYPT_KEYTYPE_PUBLIC ); cryptSetComponent( rsaKey.n, ... ); cryptSetComponent( rsaKey.e, ... ); cryptLoadContext( cryptContext, &rsaKey, CRYPT_UNUSED ); cryptDestroyComponents( rsaKey ); /* Create and export the session key */ cryptCreateContext( sessionKeyContext, CRYPT_ALGO_3DES, CRYPT_MODE_CFB ); cryptGenerateContext( sessionKeyContext, CRYPT_USE_DEFAULT ); cryptExportKey( cryptContext, sessionKeyContext, NULL, &encryptedKeyLength ); encryptedKey = malloc( encryptedKeyLength ); cryptExportKey( cryptContext, sessionKeyContext, encryptedKey, &encryptedKeyLength ); cryptDestroyContext( cryptContext ); You can now encrypt the data using the session key context, and send both the encrypted key and encrypted data to the recipient (remember to destroy the session key context when you've finished). To import the RSA-encrypted triple DES session key at the other end of the communications channel: CRYPT_CONTEXT cryptContext, sessionKeyContext; /* Create an RSA decryption context */ cryptCreateContext( decryptContext, CRYPT_ALGO_RSA, CRYPT_MODE_PKC ); cryptInitComponents( rsaKey, CRYPT_COMPONENTS_BIGENDIAN, CRYPT_KEYTYPE_PRIVATE ); cryptSetComponent( rsaKey.n, ... ); cryptSetComponent( rsaKey.e, ... ); cryptSetComponent( rsaKey.d, ... ); cryptSetComponent( rsaKey.p, ... ); cryptSetComponent( rsaKey.q, ... ); cryptSetComponent( rsaKey.u, ... ); cryptSetComponent( rsaKey.e1, ... ); cryptSetComponent( rsaKey.e2, ... ); cryptLoadContext( decryptContext, &rsaKey, CRYPT_UNUSED ); cryptDestroyComponents( rsaKey ); /* Import the session key */ cryptImportKey( decryptContext, &sessionKeyContext, encryptedKey ); cryptDestroyContext( decryptContext ); You can now decrypt the encrypted data using the session key context (remember to destroy the session key context when you've finished). To sign a message using the message hash contained in the hashContext encryption context: CRYPT_CONTEXT signContext; BYTE signature; int signatureSize; /* Create an RSA signature context */ cryptCreateContext( signContext, CRYPT_ALGO_RSA, CRYPT_MODE_PKC ); cryptInitComponents( rsaKey, CRYPT_COMPONENTS_BIGENDIAN, CRYPT_KEYTYPE_PRIVATE ); cryptSetComponent( rsaKey.n, ... ); cryptSetComponent( rsaKey.e, ... ); cryptSetComponent( rsaKey.d, ... ); cryptSetComponent( rsaKey.p, ... ); cryptSetComponent( rsaKey.q, ... ); cryptSetComponent( rsaKey.u, ... ); cryptSetComponent( rsaKey.e1, ... ); cryptSetComponent( rsaKey.e2, ... ); cryptLoadContext( signContext, &rsaKey, CRYPT_UNUSED ); cryptDestroyComponents( rsaKey ); /* Sign the hashed data */ cryptCreateSignature( signContext, hashContext, NULL, &signatureSize ); signature = malloc( &signatureSize ); cryptCreateSignature( signContext, hashContext, buffer, &signatureSize ); cryptDestroyContext( signContext ); You can now send the signature and the data it pertains to to the recipient. To check the signature at the other end of the communications channel against a hash of the data contained in the hashContext encryption context: CRYPT_CONTEXT checkContext; /* Create an RSA signature check context */ cryptCreateContext( checkContext, CRYPT_ALGO_RSA, CRYPT_MODE_PKC ); cryptInitComponents( rsaKey, CRYPT_COMPONENTS_BIGENDIAN, CRYPT_KEYTYPE_PUBLIC ); cryptSetComponent( rsaKey.n, ... ); cryptSetComponent( rsaKey.e, ... ); cryptLoadContext( checkContext, &rsaKey, CRYPT_UNUSED ); cryptDestroyComponents( rsaKey ); /* Check the signature on the hash */ if( cryptCheckSignature( checkContext, hashContext, signature ) == CRYPT_BADSIG ) /* Signature check failed */ A longer usage example including proper error checking and with various other test routines can be found in the file test.c included with the code. Querying the Encryption Library Capabilities (L) ------------------------------------------------ The previous examples showed the use of the cryptQueryAlgoMode() and cryptQueryContext() calls to retrieve information about the characteristics of a particular algorithm as implemented by the encryption library. There are four such calls in total, of which two query the existence of an implementation of an algorithm or an algorithm/mode combination, and two return information about the implementation itself. The library can be interrogated about the existence of a particular encryption algorithm or algorithm and encryption mode combination with: status = cryptAlgoAvailabile( algorithm ); or: status = cryptModeAvailabile( algorithm, mode ); In addition to requesting information on the availability of an encryption algorithm and mode, you can request extra information to be returned in a CRYPT_QUERY_INFO structure with: status = cryptQueryAlgoMode( algorithm, mode, &cryptQueryInfo ); or status = cryptQueryContext( cryptContext, &cryptQueryInfo ); with the former being used to request information on a given algorithm/mode combination and the latter being used to request information about the current encryption context being used. The CRYPT_QUERY_INFO structure contains the following fields: /* The encryption algorithm, encryption mode, and algorithm and mode names ASCII strings */ CRYPT_ALGO cryptAlgo; CRYPT_MODE cryptMode; char *algoName; char *modeName; /* The algorithm block size in bytes, the minimum, recommended, and maximum key size in bytes (if the algorithm uses a key), and the minimum, recommended, and maximum IV size in bytes (if the algorithm uses an IV) */ int blockSize; int minKeySize; int keySize; int maxKeySize; int minIVsize; int ivSize; int maxIVsize; /* The algorithm speed relative to a block copy operation. This value ranges from 1 ... CRYPT_MAX_SPEED, or CRYPT_ERROR if no speed rating is available */ int speed; /* The hash value (set to 0 if the encryption context isn't for a hash algorithm) */ unsigned char hashValue[ CRYPT_MAX_HASHSIZE ]; /* The key ID information (set to 0 if the encryption context isn't for a public-key algorithm */ CRYPT_ALGO keyIDalgo; unsigned char *keyID; unsigned char keyIDlength; /* The control vector (set to 0 if the encryption context isn't for a conventional encryption algorithm */ long controlVector; Algorithm-Specific Notes ------------------------ DES: Triple DES: Although the library routines use 64, 128, or 192-bit DES keys (depending on whether one, two, or three-key DES is being used), only 56, 112, or 168 bits of the key are actually used. The high bit in each byte is used as a parity bit (the library will set the correct parity values for you, so you don't have to worry about this). You can treat the algorithms as having 64, 128, or 192-bit keys, but bear in mind that only 7 of the 8 bits in each byte are actually used as keying material. cryptLoadContext() will return a bad parameter code if the key is a weak key (CRYPT_BADPARM2 in this case). cryptEncrypt() and cryptDecrypt() will return a bad parameter code if the encryption mode is ECB, CBC, or PCBC and the encrypted data length is not a multiple of the block size (CRYPT_BADPARM3 in this case). cryptExportKey() will export the correct parity-adjusted version of the key, not the raw version as passed to cryptLoadContext(). Diffie-Hellman DSA These algorithms are covered by patents, see the section "Patents" above. IDEA: cryptEncrypt() and cryptDecrypt() will return a bad parameter code if the encryption mode is ECB, CBC, or PCBC and the encrypted data length is not a multiple of the block size (CRYPT_BADPARM3 in this case). This algorithm is covered by patents, see the section "Patents" above. MDCSHS: None Blowfish: Blowfish-SK: RC2: SAFER: SAFER-SK: cryptEncrypt() and cryptDecrypt() will return a bad parameter code if the encryption mode is ECB, CBC, or PCBC and the encrypted data length is not a multiple of the block size (CRYPT_BADPARM3 in this case). RC4: None RC5: cryptEncrypt() and cryptDecrypt() will return a bad parameter code if the encryption mode is ECB, CBC, or PCBC and the encrypted data length is not a multiple of the block size (CRYPT_BADPARM3 in this case). This algorithm is covered by patents, see the section "Patents" above. RSA: cryptEncrypt() and cryptDecrypt() will return a bad parameter code if the length parameter is not CRYPT_USE_DEFAULT (CRYPT_BADPARM3 in this case). This algorithm is covered by patents, see the section "Patents" above. Plug-In Module Implementation ----------------------------- These notes are rather brief since there is a lot of ground to cover, and the easiest way to see what is required is to look at the crypt.c code and the way it interfaces to the lib_XXX.c code, which may be regarded as standard implementations of a plug-in module (the triple DES module is a good example). Note that internally the library uses pointers to CRYPT_INFO structures rather than the opaque CRYPT_CONTEXT cookies which exist outside the libraries' security perimeter. Implementors must provide the following services in their plug-in modules (if the algorithm doesn't require this service, the pointer to it should be set to NULL in the capabilityInfo table): int selfTestFunction( void ); Perform a self-test on the encryption hardware/software module. This function is called once before any other function is called. int initFunction( CRYPT_INFO *cryptInfo ); Initialise the appropriate private CRYPT_INFO fields. int initExFunction( CRYPT_INFO *cryptInfo, void *cryptInfoEx ); Initialiase the appropriate private CRYPT_INFO fields based on the extended information given in the second parameter. int endFunction( CRYPT_INFO *cryptInfo ); Clear the appropriate private CRYPT_INFO fields. int initKeyFunction( CRYPT_INFO *cryptInfo ); Initialise the internal key based on the user key. Typically this might perform one or more of the following: Hash the user key down to the size of the key used by the algorithm Perform some type of key schedule operation Perform endianness conversion for the current operating environment Load the key into external crypto hardware. Perform a key selection in external hardware based on the specified key selector. For algorithms with asymmetric internal keys, both an encryption and a decryption key will have to be generated, as a crypt context may be used for encryption or decryption. int initIVFunction( CRYPT_INFO *cryptInfo ); Initialise the IV. Typically this might perform one or more of the following: Perform endianness conversion for the current operating environment Load the IV into external crypto hardware. int getKeysizeFunction( CRYPT_INFO *cryptInfo ); Return the maximum key size for this encryption context in bits if the algorithm can have multiple maximum key sizes (for example triple DES can have 112 or 168 bit maximum key sizes). int getDataFunction( CRYPT_INFO *cryptInfo ); Returns any user-visible data created by the algorithm for this encryption context. Currently only used to get the result of a hash operation for hash encryption contexts. int encryptFunction( CRYPT_INFO *cryptInfo, void *buffer, int length ); int decryptFunction( CRYPT_INFO *cryptInfo, void *buffer, int length ); Encrypt/decrypt a block of data based on the algorithm and keying information in CRYPT_INFO. Some algorithms may require special handling for the last block (eg block truncation in CBC) so both routines should recognise a call with length = 0 to indicate the end of the encrypted data block. Standards Conformance --------------------- Blowfish/Blowfish-SK: Blowfish has been implemented as per: "Description of a New Varible-Length Key, 64-bit Block Cipher (Blowfish)", Bruce Schneier, "Fast Software Encryption", Lecture Notes in Computer Science No. 809, Springer-Verlag 1994. The Blowfish modes of operation are given in: ISO/IEC 8372, "Information Technology - Modes of Operation for a 64-bit Block Cipher Algorithm". The Blowfish code has been validated against the Blowfish reference implementation test vectors. DES: DES has been implemented as per: ANSI X3.92, "American National Standard, Data Encryption Algorithm", 1981. FIPS PUB 46-2, "Data Encryption Standard", 1994. FIPS PUB 74, "Guidelines for Implementing and Using the NBS Data Encryption Standard", 1981. ISO/IEC 8731:1987, "Banking - Approved Algorithms for Message Authentication - Part 1: Data Encryption Algorithm (DEA)". The DES modes of operation are given in: ANSI X3.106, "American National Standard, Information Systems - Data Encryption Algorithm - Modes of Operation", 1983. FIPS PUB 81, "DES Modes of Operation", 1980. ISO/IEC 8372, "Information Technology - Modes of Operation for a 64-bit Block Cipher Algorithm". The DES code has been validated against the test vectors given in: NIST Special Publication 500-20, "Validating the Correctness of Hardware Implementations of the NBS Data Encryption Standard". Triple DES: Triple DES has been implemented as per: ANSI X9.17, "American National Standard, Financial Institution Key Management (Wholesale)", 1985. ISO/IEC 8732:1987, "Banking - Key Management (Wholesale)". The triple DES modes of operation are given in: ISO/IEC 8372, "Information Technology - Modes of Operation for a 64-bit Block Cipher Algorithm". Diffie-Hellman: DH has been implemented as per: PKCS #3, "Diffie-Hellman Key Agreement Standard", 1991. DSA: DSA has been implemented as per: ANSI X9.30-1, "American National Standard, Public-Key Cryptography Using Irreversible Algorithms for the Financial Services Industry", 1993. FIPS PUB 186, "Digital Signature Standard", 1994. IDEA: IDEA has been implemented as per: "Device for the Conversion of a Digital Block and the Use Thereof", James Massey and Xuejia Lai, International Patent PCT/CH91/00117, 1991. "Device for the Conversion of a Digital Block and Use of Same", James Massey and Xuejia Lai, US Patent #5,214,703, 1993. "On the Design and Security of Block Ciphers", Xuejia Lai, ETH Series in Information Processing, Vol.1, Hartung-Gorre Verlag, 1992. ISO/IEC 9979, "Data Cryptographic Tecniques - Procedures for the Registration of Cryptographic Algorithms". The IDEA modes of operation are given in: ISO/IEC 8372, "Information Technology - Modes of Operation for a 64-bit Block Cipher Algorithm". The IDEA code has been validated against the ETH reference implementation test vectors. MD2: MD2 has been implemented as per: RFC 1319, "The MD2 Message Digest Algorithm", Burt Kaliski, 1992. The MD2 code has been validated against the RFC 1319 reference implementation test vectors. MD4: MD4 has been implemented as per: RFC 1320, "The MD4 Message Digest Algorithm", Ronald Rivest, 1992. The MD4 code has been validated against the RFC 1320 reference implementation test vectors. MD5: MD5 has been implemented as per: RFC 1321, "The MD5 Message Digest Algorithm", Ronald Rivest, 1992. The MD5 code has been validated against the RFC 1321 reference implementation test vectors. MDC/SHS: The SHS code in MDC/SHS has been implemented as per: ANSI X9.30-2, ""American National Standard, Public-Key Cryptography Using Irreversible Algorithms for the Financial Services Industry", 1993. FIPS PUB 180, "Secure Hash Standard", 1993 The MDC/SHS mode of operation is given in: ISO/IEC 10116, "Information Technology - Security Techniques - Modes of Operation for an n-bit Block Cipher Algorithm". The SHS code has been validated against the test vectors given in: FIPS PUB 180, "Secure Hash Standard", 1993 RSA: The RSA code is implemented as per: ANSI X9.31-1, "American National Standard, Public-Key Cryptography Using Reversible Algorithms for the Financial Services Industry", 1993. PKCS #1, "RSA Encryption Standard", 1991. ISO IEC 9594-8/ITU-T X.509, "Information Technology - Open Systems Interconnection - The Directory: Authentication Framework". RC2: The RC2 code is implemented as per: "The RC2 Encryption Algorithm", Ronald Rivest, RSA Data Security Inc, 1992. The RC2 modes of operation are given in: ISO/IEC 8372, "Information Technology - Modes of Operation for a 64-bit Block Cipher Algorithm". The RC2 code has been validated against RSADSI BSAFE test vectors. RC4: The RC4 code is implemented as per: "The RC4 Encryption Algorithm", Ronald Rivest, RSA Data Security Inc, 1992. The RC4 code has been validated against RSADSI BSAFE test vectors. RC5: The RC5 code is implemented as per: "The RC5 Encrypion Algorithm", Ronald Rivest, "Fast Software Encryption II", Lecture Notes in Computer Science No.1008, Springer-Verlag 1995. The RC5 modes of operation are given in: ISO/IEC 8372, "Information Technology - Modes of Operation for a 64-bit Block Cipher Algorithm". The RC5 code has been validated against the RC5 reference implementation test vectors. SHA/SHA1: The SHA code has been implemented as per: ANSI X9.30-2, "American National Standard, Public-Key Cryptography Using Irreversible Algorithms for the Financial Services Industry", 1993. FIPS PUB 180, "Secure Hash Standard", 1993 FIPS PUB 180-1, "Secure Hash Standard", 1994 The SHA code has been validated against the test vectors given in: FIPS PUB 180, "Secure Hash Standard", 1993 The SHA1 code has been validated against the test vectors given in: FIPS PUB 180-1, "Secure Hash Standard", 1994 Safer/Safer-SK: The Safer code has been implemented as per: "SAFER K-64: A Byte-Oriented Block-Ciphering Algorithm", James L.Massey, "Fast Software Encryption", Lecture Notes in Computer Science No. 809, Springer-Verlag 1994. The Safer-SK code has been implemented as per: "SAFER K-64: One Year Later", James L.Massey, "Fast Software Encryption II", Lecture Notes in Computer Science No.1008, Springer-Verlag 1995. The Safer/Safer-SK modes of operation are given in: ISO/IEC 8372, "Information Technology - Modes of Operation for a 64-bit Block Cipher Algorithm". The Safer/Safer-SK code has been validated against the ETH reference implementation test vectors. Data Structures: All message exchange data structures are specified as per: ISO/IEC 8824-1:1993/ITU-T X.680, "Information Technology - Open Systems Interconnection - Abstract Syntax Notation One (ASN.1) - Part 1: Specification of Basic Notation". The encoding is as per: ISO/IEC 8825-3:1993/ITU-T X.692, "Information Technology - Open Systems Interconnection - Specification of ASN.1 Encoding Rules - Part 3: Distinguished Canonical Encoding Rules". The ASN.1 specifications for the message structures are given in the file cryptlib.asn. General: The encryption subsystem has been implemented at a level equivalent to level 1 of the standard given in: FIPS PUB 140-1, "Security Requirements for Cryptographic Modules", 1993. The random-data acquisition routines follow the guidelines laid out in: "Randomness Recommendations for Security", RFC 1750, Donald Eastlake, Stephen Crocker, and Jeffrey Schiller, December 1994. "Cryptographic Random Numbers", IEEE P1363 Appendix E, Draft version 1.0, 11 November 1995. Revisions --------- Version 0.99 First public release. Version 1.10 Added RC2, RC5, and Blowfish/Blowfish-SK encryption algorithms. Added MD4, MD5, and SHA hash algorithms. Moved encryption context information into the library, making the information contained within it opaque to end users. Version 1.20 Added MD2 hash algorithm. Added Diffie-Hellman and RSA public-key encryption algorithm. Cleaned up the name space. Added locking of memory objects to stop them being paged to disk. Added random number management routines Added high-level encrypted object management routines Acknowledgements ---------------- The DES and 3DES encryption code was contributed by Eric Young and is part of his libdes package. The primary ftp site for the full libdes is ftp://ftp.psy.uq.oz.au/pub/Crypto/DES/libdes-x.xx.tar.gz. libdes is now also shipped with SSLeay. The primary site for this is ftp://ftp.psy.uq.oz.au/pub/Crypto/SSL/SSLeay-x.xx.tar.gz. The IDEA code was written by Colin Plumb, and scanned from the book "PGP Source Code and Internals", MIT Press, 1995 (see the comments in the code for more information). The bignum maths library used for the public-key encryption was contributed by Colin Plumb. Matt Thomlinson and Blake Coverett helped fix up and debug the Win32 random data gathering routines. Chris Wedgwood and Paul Kendall wrote the Unix random data gathering routines. Scott Morris did the VMS port. Cylink Corporation very generously granted permission for the use of their patents for non-commercial purposes.