CryptoSysTM PKI Manual

Function List   Index

Contents

Introduction to CryptoSys PKI

CryptoSys PKI is a programmer's toolkit for Win32 systems that enables the user to create and read secure cryptographic messages encrypted or signed using RSA public key encryption.

You can create and read both enveloped-data (encrypted) and signed-data Cryptographic Message Syntax (CMS, PKCS#7) objects, which you can use in S/MIME email messages. You can verify the digital signature in a signed-data CMS object; generate and manage RSA public and private keys; carry out "raw" RSA encryption and decryption, and create and read X.509 certificate files.

Other utilities included in the toolkit are the ability to generate message digest hash values using SHA-1, MD5, MD2, SHA-256, SHA-384, SHA-512 and [new in version 3.2] SHA-224; generate HMAC keyed-hash message authentication values, wipe files using 7-pass DOD standards, generate cryptographically-secure random numbers to the strict NIST SP800-90 standard, prompt for a password, and convert to and from base64- and hexadecimal-encoded formats.

Public Key Infrastructure (PKI) is defined as

The set of hardware, software, people, policies and procedures needed to create, manage, store, distribute, and revoke Public Key Certificates based on public-key cryptography. -[PKIX-MAP]
The CryptoSys PKI toolkit provides programmers and developers with some of the more useful algorithms you need to create the software for a true PKI. We have appropriated a well-known three-letter-acronym. It's up to you to manage the hardware, people, policies, procedures and the overall software security you require.

We have used S/MIME Version 3 Message Specification [SMIME-MSG], and Cryptographic Message Syntax (CMS) [CMS] together with the relevant PKCS documents as our primary reference documents. CMS is a stricter subset of PKCS#7 [PKCS7] and is compatible with it.

Our goal has been to provide developers and programmers with a useful set of functions, not necessarily an exhaustive set (see Design Philosophy). We have excluded a few of the required ("MUST") algorithms. For example, this release of the toolkit only includes the RSA signature algorithm not DSA and does not support CRLs. UTF-16 Unicode and MBCS character sets are not explicitly supported, but usually can be managed by suitable encoding into a byte array before using the toolkit functions. However, it can verify X.509 certificates certain signedData objects signed using DSA and UTF-8 support is provided for X.509 distinguished name strings.

The CMS (PKCS#7) objects produced by this toolkit should be readable by S/MIME-compatible email clients like Microsoft Outlook Express if they are wrapped in MIME-conformant email messages. No MIME or email facilities are provided with the toolkit. You need to use a separate program to create, send and read MIME email messages. The X.509 certificate tools should be compatible with typical certificates issued by Verisign and Thawte. The certificate requests it creates are accepted by Verisign's test facility, provided you include the distinguished name attributes they require. However, there is only limited support for Unicode character sets like UTF8String and BMPString. And, sorry, there are no facilities to add an MPEG video of you playing with your cat into an X.509 certificate.

To get started, read the sections on Installation and General Programming Issues and the section on your programming language:

[Contents] [Index]

Features

CryptoSys PKI uses a straightforward Win32 DLL which is compatible with all versions of 32-bit Windows® (95/98/Me/NT/2K/XP/2003/Vista). There is no "COM", no "Active-X", and no requirement to "register" it with Windows to use it. The installed executable has a small footprint of about 350 KB. Developers can easily distribute it with their projects made in Visual Basic, VBA, C, C++, VB.NET or C# (in fact, in any other programming language that will let you call Win32 API functions). For more information, see Technical Details.

[Contents] [Index]

Changes in the latest versions

Changes in version 3.2:

Changes in earlier versions

Changes in version 3.1:

Changes in version 3.0:

Changes in version 2.9:

Changes in version 2.8:

Changes in version 2.7:

[Contents] [Index]

Copyright Notice

Except where otherwise noted, the CryptoSys PKI toolkit program, its executables, sample source code and this document were written by David Ireland and are copyright (c) 2002-8 by DI Management Services Pty Limited, all rights reserved. They may not be distributed or reproduced separately by any means whatsoever without express permission in writing. Users holding a valid develeoper's licence are permitted to distribute the executables as part of a value-added application according to the terms of their licence.

The latest version of CryptoSys PKI may be obtained from <http://www.cryptosys.net/pki/>.

[Contents] [Index]

Theory

There is no theory explained here. We assume you know what you are doing and have read and understood at least the relevant reference documents. For a good introduction to the concepts, try William Stallings, Cryptography and Network Security: Principles and Practice [STAL02]. For a more detailed work, see Some Examples of the PKCS Standards by RSA Laboratories [PKCS-EX] and the primary references we used.

Assumptions

You are assumed to have a reasonable knowledge of the basics of cryptography, public key encryption, and Secure/MIME electronic messaging, and that you can program in the language of your choice to an advanced standard. If you don't understand what this is trying to do, you really shouldn't be using it.

You must understand the difference between binary data and base64-encoded data and know how to read them, and you must understand the difference between a CMS object and a MIME body.

It will also help that you:

[Contents] [Index]

Supported Algorithms

Public key encryption and signature algorithms

Symmetric block cipher algorithms for content encryption

Block cipher algorithms for key wrapping

Message digest hash algorithms

We keep MD2 here so we can reproduce the examples from RSA Laboratories' 1993 paper [PKCS-EX] and because we still find the odd X.509 certificate using it. You are recommended to use at least SHA-1 in new applications.

HMAC keyed-hash algorithms

For generating HMAC message authentication codes with the HMAC_ functions.

Password-based encryption algorithms

These algorithms from PKCS#5 and PKCS#12 can be used to create PKCS#8 encrypted private key files by the RSA_SaveEncPrivateKey and RSA_MakeKeys functions: In addition to those above, the following algorithms can be read by the RSA_ReadEncPrivateKey function:

RSA Key Formats

Supported formats for RSA keys are as per PKCS#1 and PKCS#8. XML format to XKMS 2.0 is also supported.

CMS Content Types

Only CMS objects with an id-data inner content type are supported. The RecipientIdentifier must be issuerAndSerialNumber.

X.509 Certificates

Unsupported algorithms

These algorithms from [PKIXALG] are not currently supported:

Key Storage Format

[Contents] [Index]

Conventions in this document

Visual basic code is shown shaded as follows (if your browser supports shading and colours):-
Dim strData As String
Dim nRet As Long
strData = "Hello world"
Debug.Print strData
' ... etc
Code in C/C++ is shown as:
char *str = "Hello world";
printf("%s\n", str);
Code in VB.NET is shown as:
Dim nDataLen As Integer
Dim abData() As Byte
If strData.Length = 0 Then Exit Function
abData = System.Text.Encoding.Default.GetBytes(strData)
nDataLen = abData.Length
Code in C# is shown as:
public static string ToHex(byte[] binaryData)
{
    int nBytes = binaryData.Length;
    int nChars = 2 * nBytes;
    if (nBytes == 0) return String.Empty;
    StringBuilder sb = new StringBuilder(nChars);
    nChars = CNV_HexStrFromBytes(sb, nChars, binaryData, nBytes);
    return sb.ToString(0, nChars);
}
Output from code samples is shown as:
Result=OK
All functions called directly in the CryptoSys PKI toolkit begin with 3 or 4 capital letters followed by an underscore "_", e.g.
nRet = RSA_ReadPublicKey(rsaReadPublicKey, nKeyLen, strKeyFile, 0)
For VB users, there are some wrapper functions provided in basCrPKI which avoid the complications of having to pre-dimension strings, etc. These begin with lowercase letters and no underscore. They are shown in our examples as follows:
strPublicKey = rsaReadPublicKey(strKeyFile)

[Contents] [Index]

Installation

To install on a test or developer system, use the setup.exe program provided with the distribution. This installs the core Win32 DLL in your main windows system directory and creates copies of all other required files, including this manual, in the directory C:\Program Files\CryptoSysPKI.

To distribute a Licensed Version to your end users, please read the instructions in the file distrib.txt. You do not use the setup.exe program to distribute to end users. Note that the core DLL file is completely different for the Trial and Developer versions.

Important: You must use the setup.exe program to install the Trial version on your system and you must have administrator rights when installing or uninstalling.

To uninstall, use Start > Settings > Control Panel > Add/Remove Programs > CryptoSys PKI Toolkit to remove the application and all associated files.

Core executable DLL

The core executable file is diCrPKI.dll which is a Win32 DLL. This file must exist in the library search path on the user's system for all programming language interfaces. The executable diCrPKI.dll is not registered with Regsvr32 (it can't be). The VB6/VBA and C/C++ interfaces access this core executable directly.

Wrapper DLLs

A wrapper executable is provided to allow .NET programming access to the core executable. This requires the core Win32 DLL to exist in the library search path. The same wrapper DLL can be used with all versions. For more information on the executables, see Technical Details.

[Contents] [Index]

General Programming Issues

Return Values

All the core VB6/C functions in this toolkit return a 32-bit signed integer value, that is a Long in VB6/VBA and a long in C/C++, but an Integer in VB.NET and an int in C#. The wrapper functions provided in the .NET interface behave differently (and more conveniently) - please refer to the detailed documentation on that interface.

Functions either

  1. return zero to indicate success or a non-zero error code on failure; or
  2. return a positive value to indicate, say, a required length or other value, or a negative error code on failure.

[Contents] [Index]

"Hello world" programs

The equivalent of the "Hello world" program for CryptoSys PKI is to call the PKI_Version function. The function will demonstrate that the Toolkit is properly installed. Here is some example source code in VB6, C, C# and VB.NET, respectively.
Public Sub ShowVersion()
   Dim nRet As Long
   nRet = PKI_Version(0, 0)
   Debug.Print "Version=" & nRet
End Sub
#include <stdio.h>
#include "diCrPKI.h"
int main(void) 
{
   long version;
   version = PKI_Version(NULL, NULL);
   printf("Version=%ld\n", version);
   return 0;
}
using CryptoSysPKI;
static void ShowVersion()
{
   int ver;
   ver = General.Version();
   Console.WriteLine("Version={0}", ver);
}
Imports CryptoSysPKI	
Shared Sub ShowVersion()
   Dim ver As Integer
   ver = General.Version()
   Console.WriteLine("Version={0}", ver)
End Sub

[Contents] [Index]

Converting strings to bytes and vice versa

Use these functions to convert a string of text to an unambiguous array of bytes and vice versa.

In VB6/VBA, use the StrConv function.

Dim abData() As Byte
Dim Str As String
Dim i As Long
Str = "Hello world!"
' Convert string to bytes
abData = StrConv(Str, vbFromUnicode)
For i = 0 To UBound(abData)
    Debug.Print Hex(abData(i)); "='" & Chr(abData(i)) & "'"
Next
' Convert bytes to string
Str = StrConv(abData, vbUnicode)
Debug.Print "'" & Str & "'"
48='H'
65='e'
6C='l'
6C='l'
6F='o'
20=' '
77='w'
6F='o'
72='r'
6C='l'
64='d'
21='!'
'Hello world!'

In VB.NET use System.Text.Encoding.

Dim abData() As Byte
Dim Str As String
Dim i As Long
Str = "Hello world!"
' Convert string to bytes
abData = System.Text.Encoding.Default.GetBytes(Str)
For i = 0 To UBound(abData)
    Console.WriteLine(Hex(abData(i)) & "='" & Chr(abData(i)) & "'")
Next
' Convert bytes to string
Str = System.Text.Encoding.Default.GetString(abData)
Console.WriteLine("'" & Str & "'")
You could be more explicit by replacing .Default with .GetEncoding(1252), and then use the appropriate code page for your character set (1252 is Western European).

In C#, use System.Text.Encoding, which has identical behaviour to the function in VB.NET.

byte[] abData;
string Str;
int i;
Str = "Hello world!";
// Convert string to bytes
abData = System.Text.Encoding.Default.GetBytes(Str);
for (i = 0; i < abData.Length; i++)
{
	Console.WriteLine("{0:X}", abData[i]);
}
// Convert bytes to string
Str = System.Text.Encoding.Default.GetString(abData);
Console.WriteLine("'{0}'", Str);
In C and C++, the distinction between a string and an array of bytes is often blurred. A string is a zero-terminated sequence of char types and bytes are stored in the unsigned char type. A string needs an extra character for the null terminating character; a byte array does not, but it needs its length to be stored in a separate variable A byte array can can contain a zero (NUL) value but a string cannot.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

static void pr_hexbytes(const unsigned char *bytes, int nbytes)
/* Print bytes in hex format + newline */
{
   int i;
   for (i = 0; i < nbytes; i++)
   	printf("%02X ", bytes[i]);
   printf("\n");
}

int main()
{
   char szStr[] = "Hello world!";
   unsigned char *lpData;
   long nbytes;
   char *lpszCopy;

   /* Convert string to bytes */
   /* (a) simply re-cast */
   lpData = (unsigned char*)szStr;
   nbytes = strlen(szStr);
   pr_hexbytes(lpData, nbytes);

   /* (b) make a copy */
   lpData = malloc(nbytes);
   memcpy(lpData, (unsigned char*)szStr, nbytes);
   pr_hexbytes(lpData, nbytes);

   /* Convert bytes to a zero-terminated string */
   lpszCopy = malloc(nbytes + 1);
   memcpy(lpszCopy, lpData, nbytes);
   lpszCopy[nbytes] = '\0';
   printf("'%s'\n", lpszCopy);

   free(lpData);
   free(lpszCopy);

   return 0;
}
48 65 6C 6C 6F 20 77 6F 72 6C 64 21
48 65 6C 6C 6F 20 77 6F 72 6C 64 21
'Hello world!'

The types char and unsigned char might be identical on your system, or they might not be. We strongly recommend that you explictly distinguish between strings and byte arrays in your code by using the correct type and consistently treating them differently.

If your string is a Unicode string, then it consists of a sequence of wchar_t types. Converting wide-character strings to a sequence of bytes in C is more problematic. You can either convert the Unicode string directly to a string of bytes (in which case every second byte will be zero for US-ASCII characters), or use the stdlib wcstombs function or the Windows WideCharToMultiByte function to convert to a sequence of multi-byte characters (some will be one byte long, some two) and then convert the multi-byte string to bytes (you can do this with a simple cast). Each party encrypting and decrypting must agree on which way to do it.

[Contents] [Index]

Upgrading VB6 to VB.NET

If you are writing VB.NET code from scratch, we recommend you use the classes and methods in the .NET Class Library. If you need to upgrade old VB6 code to VB.NET, please note the following:
  1. Change all variables dimensioned as Long to Integer. CryptoSys PKI always uses 32-bit integers.
  2. All strings in CryptoSys PKI are ANSI strings. Change statements of the form
    Declare Function Foobar
    to
    Declare Ansi Function Foobar
  3. Change statements of the form
    strBuffer = String(n, " ")
    to
    strBuffer = New String(" "c, n)
    Note the 'c' after the " ". This is required for the Strict On option.
  4. Use System.Text.Encoding.Default.GetBytes(Str) and System.Text.Encoding.Default.GetString(abData) to convert between text strings and bytes instead of StrConv(Str, vbFromUnicode) and StrConv(abData, vbUnicode).

[Contents] [Index]

Using with Classic Visual Basic and VBA

To call the CryptoSys PKI functions from a classic Visual Basic project or VBA application, just add the module basCrPKI.bas to your project (VBA users should delete the first line Attribute VB_Name = "basCrPKI").

The VB functions work in the same way as you would call the Win32 API functions from VB. You must use the correct variable types and must pre-dimension strings and byte arrays that are to receive output or you will suffer the wrath of the great god Gee-pee-eff.

For examples, see the test code provided in the distribution in the folder C:\Program Files\CryptoSysPKI\VB6.

Pre-dimensioning for VB

To create a string of, say, length 40 characters, do either:
Dim sData As String * 40
or
Dim sData As String
sData = String(40, " ")
If you know the output string needs to be the same size as the input, do this:
Dim strInput As String
Dim strOutput As String
strInput = "......"
strOutput = String(Len(strInput), " ")
To create a byte array of a given length:
Dim abData() As Byte
Dim nLen As Long
nLen = 40
ReDim abData(nLen - 1)
Note that byte arrays in VB are indexed from 0 to nLen - 1.

To create a byte array of the same length as an existing array, do this:

Dim abOutput() As Byte
ReDim abOutput(Ubound(abInput))

To find the length of an existing byte array:

nLen = UBound(abData) - LBound(abData) + 1
Be careful, as this will cause a run-time error if abData() has not been ReDim'd. Look at the code for the function cnvHexStrFromBytes in basCrPKI.bas to see how this can be handled.

Other Issues For VB6/VBA Users

[Contents] [Index]

Using with C and C++

To use with a C or C++ program, include the diCrPKI.h file with your source code and link with the diCrPKI.lib library. The only non-ANSI C requirement is that your complier supports the __stdcall calling convention to call Win32 API functions from a Win32 DLL. The sample C code PKI_Examples.c provided in the distribution carries out a variety of tests using known test vectors. There are examples given for each function.

You'll also realise that this manual is written primarily for Visual Basic programmers. Apologies, sort of, because we know you can easily work what to do from the examples given in the sample C programs and the rest of this manual.

The VB6/VBA functions and C/C++ functions are identical in form. The function names are identical, the arguments are the same (even though we may have used different parameter names in the VB and C syntaxes), and the same warnings given in the Remarks section apply to both. Just be careful to add an extra character for output string types in C.

Type Conversions

The following type conversions apply between VB6/VBA and C/C++:-

VB6/VBAC/C++
ByVal x As Stringchar *x
ByRef x As Byteunsigned char *x
ByVal x As Longlong x

You can still use Boolean type for the fEncrypt variable in VB6/VBA, which is equivalent to a 32-bit integer when passed to the core DLL. We recommend that you use the defined constants ENCRYPT and DECRYPT in your VB6 and C code.

Compiling with C

The Toolkit has been tested with Microsoft Visual C++ (versions 5, 6, 7 and 8=Express 2005) and Borland C++Builder version 5.5.

Here is some minimal code:-

/* mysource.c */
#include <stdio.h>
#include "diCrPKI.h"
int main(void)
{
    long version;
    version = PKI_Version(NULL, NULL);
    printf("Version=%ld\n", version);
    return 0;
}
To create mysource.exe with MSVC++:
cl mysource.c diCrPKI.lib
Running this program should result in
Version=298
where the actual number displayed depends on which version you have installed.

Using With Borland C++

The lib file distributed with the program is made using MSVC. With Borland you need to generate a new .lib file directly from the DLL using the IMPLIB utility:
implib diCrPKI.lib diCrPKI.dll
To compile with Borland C++:
bcc32 mysource.c diCrPKI.lib

Cautions for C/C++ Users

We recommend using the defined constants like PKI_MAX_SHA1_CHARS and PKI_MAX_SHA1_BYTES rather than hard-coded numbers, e.g.
char sDigest_sha1[PKI_MAX_SHA1_CHARS + 1]; 
See the constants in diCrPKI.h.

For examples, see the test code PKI_Examples.c and PKICheck.c provided in the distribution.

[Contents] [Index]

Using with .NET: C# and VB.NET/VB2005/VB200x

To use the .NET interface with C# and VB.NET/VB2005/VB200x:
  1. Copy the dotnet diCrSysPKINet.dll library file into a convenient folder (the setup program puts this file by default in C:\Program Files\CryptoSysPKI\DotNet).
  2. In your application, add a reference to the library:
    1. Project > Add Reference.
    2. In the .NET tab, click on the Browse button to find and select the library file diCrSysPKINet.dll.
    3. Click on OK and the wizard will add the reference of the class library to your project.
  3. Add the line (for C#)
    using CryptoSysPKI;
    
    or (for VB.NET)
    Imports CryptoSysPKI
    
    to your code.
  4. Call the methods in the various classes. Note that the methods in the .NET interface may have different parameters and return values to the functions in the core VB6/C DLL. Refer to the separate .NET Help File for the details.

Alternatively, with C#, you can just include the source code module CryptoSysPKI.cs in your project and there is no need to reference the class library DLL.

All methods in the CryptoSysPKI .NET Class Library are static methods. You do not need to instantiate or dispose of any objects.

For examples, see the test code TestPKIcsharp.cs and TestPKIvbnet.vb provided in the distribution.

In older versions we suggested using direct upgrades of the VB6 code in VB.NET with all the pre-dimensioning and other unsafe practices. The .NET Class Library interface is cleaner, safer and more convenient. If you are writing VB.NET code from scratch, please use the .NET Class Library interface. If you need to upgrade old VB6 code, see Upgrading VB6 to VB.NET.

.NET Help File

The .NET classes and methods have different parameters and return values to the VB/C functions described in this manual. Full details are provided in the separate help file CryptoSysPKI.chm supplied with the distribution (Hint: it should be in the folder C: rogram Files\CryptoSysPKI\DotNet).

[Contents] [Index]

Security Issues

  1. The functions and methods in CryptoSys PKI provide cryptographic primitives intended to be used as part of a security-related application. It is up to you the programmer to ensure that keys, passwords and other private data are kept secret, and to ensure that appropriate security policies and procedures are followed by end users.
  2. CryptoSys PKI is a 32-bit dynamically linked library (DLL) that provides encryption services to applications running on Microsoft Windows® operating systems. On Windows, it is designed for and supports multi-threaded operation.
  3. In FIPS 140-2 terms, CryptoSys PKI is a multi-chip standalone module, consisting of the file diCrPKI.dll. It is intended to meet FIPS 140-2 security level 1. The cryptographic boundary for CryptoSys PKI is defined as the enclosure of the computer on which the cryptographic module is installed. As a pure software product, CryptoSys PKI provides no physical security by itself. The computer itself must be appropriately physically secured.
  4. Functions that advertise that they create files will overwrite any existing file of the same name without warning.
  5. No other files, temporary or permanent, are ever created by the toolkit.
  6. There is no communications functionality whatsoever in the toolkit DLL. The toolkit will never attempt to "dial home" or make any attempt to create a communications socket. If your firewall tells you there is an attempt to create an internet connection, you have a virus or trojan of some sort unrelated to the CryptoSys PKI toolkit.
  7. The Developer version of the toolkit makes no attempts to read or write to the Windows registry whatsoever under normal operations. However, optional registry settings may be made and, if they exist, will be read and acted on by the module if a critical error occurs.
  8. The Trial version creates and updates registry entries in HKCU\Software\DI Management and reads entries created by the setup utility in HKLM\Software\DI Management. Do not attempt to change these entries.
  9. Developer versions distributed to end users do not require any registry entries on the user's machine (but see optional registry settings).
  10. To check you have a valid version of the CryptoSys PKI executable, please check the CRC and hash digests published on our web site.
  11. It is your responsibility to take care of unencrypted private keys and password strings.
  12. Some functions require the input to be provided as a file or create a file of the output. It is your responsibility to clean up these files after use.
  13. VB6/C functions that write output to a string require the string to be "pre-dimensioned" first. All such functions require the length to be specified. Make sure the specified length matches the size of the string or a GPF error will result.
  14. Except for the X509_CertIsValidNow function, no checks are made on the validity period of X.509 certificates by any other functions in this toolkit. It's up to you to make your own checks.
  15. Read the section on Key Security below.

[Contents] [Index]

Key Security

The primitives in this toolkit allow you to do a lot of low-level operations with RSA keys. The original design only permitted private keys to be stored as a file in encrypted format. In response to many requests from users, we've added various functions that allow you to import and save private keys in a variety of unencrypted formats, including XML and OpenSSL-compatible PEM formats.

Use these functions in your tests by all means, but if you are using this toolkit to make an application to be used by less-experienced end users (and this is almost always the case), follow the following guidelines:

Internal key strings

[New in Version 3.0]

As of version 3.0, the "internal" RSA key strings are stored in encrypted form. A fresh, random key-encryption key (KEK) is generated for each new process. That means You should always read in the key data from the private and public key files, PFX files, or X.509 certificates at the time they are required.

Internal key string format

The old-style internal key strings would have been a string of base64 characters starting with an "M", e.g.
MIICXAIBAAKBgQDOMfnp2hxoUuapC0oAits0T0cs0uQb7mRa+...
The new encrypted internal key strings are still in base64 format, but will begin with either "PVT" or "PUB" to denote a private or public key respectively.
PVTRujwuI4e5gQP1SRLmc0n9viayF37krSHbHE...
PUBRg1UO9GCVDFLoxE1ihi5p0PoR3Bdrx1U7ky...

Encryption mechanism

The key strings are encrypted using a slightly-modified version of the HMAC key wrapping method described in [RFC3537]. Each process has its own 192-bit key-encryption key (KEK) generated at random when first needed. The same KEK is used for all subsequent key wrapping in that process and for all its threads.

Why?

The original design of the Tookit envisaged the internal key strings only being used with great care and attention by thoughtful programmers who would never compromise the security of the unencrypted data in the strings by saving them or printing them or letting the operating system make unauthorised copies. Yes, indeed!

We always said we might change the format in future versions. Now we have. We may do it again.

[Contents] [Index]

Security options for encrypted private keys

Private keys are stored by default in a PKCS-8 encrypted format, protected by a password. The default algorithm is "pbeWithSHAAnd3-KeyTripleDES-CBC" from PKCS-5.

The approximate ranking for the encryption schemes in increasing order of security is

  1. "pbeWithMD2AndDES-CBC"
  2. "pbeWithMD5AndDES-CBC"
  3. "pbeWithSHA1AndDES-CBC"
  4. "pbeWithSHAAnd3-KeyTripleDES-CBC" (default)
  5. "pkcs5PBES2" + "des-EDE3-CBC"
  6. "pkcs5PBES2" + "AES128-CBC"
  7. "pkcs5PBES2" + "AES192-CBC"
  8. "pkcs5PBES2" + "AES256-CBC"

The less-secure algorithms pbeWithXAndDES-CBC with single DES are provided just in case you need compatibility with an older system. Do not use them unless you have to.

The PBES2 scheme uses the PKCS-5 PBKDF2 key derivation function with hmacWithSHA1 as the default psuedo-random function (PRF). To use a stronger message digest function from the SHA-2 family in the PRF, add one of the following options

  1. PKI_HASH_SHA224 for hmacWithSHA224
  2. PKI_HASH_SHA256 for hmacWithSHA256
  3. PKI_HASH_SHA384 for hmacWithSHA384
  4. PKI_HASH_SHA512 for hmacWithSHA512
So, for example, to specify using PBES2 with AES256-CBC-Pad as the block cipher and HMAC-with-SHA256 as the PRF, use
	
Dim nOptions As Long
nOptions = PKI_PBE_PBES2 + PKI_BC_AES256 + PKI_HASH_SHA512
Remember that the security of all these schemes is limited by the strength of the password used. Also, other systems may not necessarily support all the alternatives provided here.

[Contents] [Index]

New security algorithms in Version 3.2

We have introduced a whole new set of combinations of new encryption algorithms in this version.

Increased combinations of options for algorithms:

FunctionCombinations BeforeCombinations Now
CMS_MakeEnvData124
CMS_MakeSigData26
X509_MakeCert37
RSA_SaveEncrPrivateKey
RSA_MakeKeys
524

We must add that most of these new additions are overkill for the average user. It's convenient for us to add all the combinations at once, but expect the standard CMS algorithms of rsaEncryption with SHA-1 and Triple DES for encryption, and sha1WithRSAEncryption for signatures to stay as a standard for several years to come. Most other applications will not accept the new AES/SHA-2 algorithms yet, so check with your recipients whether they support them. We note that signatures using SHA-256 are starting to be required and we expect AES-128 will become a commonplace requirement instead of Triple DES soon. Otherwise, most other options (AES-192/256 and SHA-384/512) should be kept in reserve. Please consult your security adviser for the latest recommendations.

Remember that it's the overall security of your entire process that matters, not that you've decided to use AES-256 and SHA-512 just because they are the strongest items on the menu. A security level of 128 bits can be satisfied with AES-128 and SHA-256 and an RSA key of 3072 bits. Any keys and random numbers used should be to the same security level (which is harder to do than you might think). And a password of the same strength needs to be approximately 98 characters long! See NIST SP800-57 [SP80057] for more details on consistent security levels.

RSA-KEM

RSA-KEM is a key transport algorithm for transporting keying data to a recipient using the recipient's RSA public key. It is also known as "Simple RSA". It is meant to be stronger than the PKCS#1 v1.5 rsaEncryption algorithm and is an alternative to the hardly-implemented-by-anybody RSAES-OAEP from PKCS#1 v2.

RSA-KEM is described in ISO 18033-2 [ISO18033-2] and in an Internet draft document [CMSRSAKEM]. It involves using RSA to encrypt a randomly generated number and uses both key wrapping with a symmetric block cipher and a message-digest-based key derivation function (KDF). There are three parameters required to define it (block cipher, KDF, message digest).

The default parameters in this Toolkit are AES128-Wrap and KDF2 with SHA-1. Note that, in this case, Triple DES is not the default block cipher, although it is available as an option.

When using the CMS_QueryEnvData function to find the keyEncryptionAlgorithm, it will be described as "ac-generic-hybrid", not RSA-KEM, which is strictly the "KeyEncapsulationMechanism" or KEM. The KEM is a parameter of the overarching key encryption algorithm, together with the "DataEncapsulationMechanism", DEM, the symmetric key wrap algorithm. To add to the fun, the official name for the OID is the other way around, as in "kem-rsa".

In an ASN.1-style format, the KeyEncryptionAlgorithm element for PKCS#1 rsaEncryption

SEQUENCE {
  rsaEncryption,
  NULL
}
is replaced by a typical element like this for RSA-KEM
SEQUENCE {
  id-ac-generic-hybrid,                         -- generic cipher
  SEQUENCE {                           -- GenericHybridParameters
    SEQUENCE {                     -- key encapsulation mechanism
      id-kem-rsa,                                      -- RSA-KEM
      SEQUENCE {                              -- RsaKemParameters
        SEQUENCE {                     -- key derivation function
          id-kdf-kdf2,                                    -- KDF2
          SEQUENCE {                         -- KDF2-HashFunction
            id-sha256       -- SHA-256; no parameters (preferred)
          }
        },
        16                                 -- KEK length in bytes
      }
    }   
    SEQUENCE {                    -- data encapsulation mechanism
      id-aes128-Wrap               -- AES-128 Wrap; no parameters
    }
  }
}

This is a provisional version of the algorithm in this release [v3.2]. We reserve the right to change the related function and method parameters and options in a future release, and to fix any errors once proper test vectors are available.

[Contents] [Index]

Design Philosophy

Why we did what we did

This toolkit has been several years in the making. We have had a early version in operation on our web site dealing with secure messages since the year 2000 and version 1 has been used since 2002 by a client of ours to pass S/MIME messages between client computers using a program written in C and a program running on a mail server written in Visual Basic.

We have tried hard to keep this interface simple, but it's not a simple topic. There are thousands of options and alternative ways of doing things. The basic user requires an interface that is straightforward to use and the advanced user expects all the frills and expert options.

CMS objects, X.509 certificates and public key encryption all have lots of alternatives and we've tried to provide a set of the most commonly used variations. Our original design intention was to create a reasonably small set of functions that are simple to use to carry out the basic functions while providing the advanced user with the options they expect.

We have tried to strike a balance between dozens of functions all doing minor variations on a theme with long Java-like names and a smaller set that uses option flags where the developer has to master the addition of complicated bit values. On testing, we found that the over use of options generally made it more confusing, so we've added more functions than we initially planned. We do think some of the functions names are getting a bit long and we do admit that some functions have too many parameters, but, overall, we hope we have been successful in our original intentions.

As a general guide, choosing a zero value for the option flag will probably do what you expect.

Functions you would expect to be used repeatedly to decrypt messages require the private key to be stored in a string. This is messy to do for one-offs, and clutters up our simple examples, but is more efficient on a server handling large quantities of incoming messages. Remember that there is a deliberate delay when trying to decrypt private keys. See also Internal key strings.

We have avoided COM with all its potential problems and have kept the old-fashioned Windows API format where strings need to be pre-dimensioned before use. This makes it trickier to use but easier to use the toolkit with different programming languages. It is a simple matter to write wrapper functions to manage the tricky bits and examples are given.

We always strive to improve our products and will be grateful for any feedback. If you think we've got it wrong, please tell us.

[Contents] [Index]

Technical Details

The core executable diCrPKI.dll is a Win32 DLL compiled with Microsoft Visual C++ Version 5.0. All programming language interfaces require this DLL to exist in the library search path of the user's system. The core cryptographic functions are written in pure ANSI C with extensive internal checks for memory leaks and overflow issues. The executable diCrPKI.dll is compatible with all versions of 32-bit Windows (95/98/Me/NT4/2K/XP/2003/Vista). It does not require any other special libraries to work apart from the standard Win32 libraries available in all versions of Windows. It is totally independent of the Microsoft Cryptographic API.

The wrapper executable diCrSysPKINet.dll is a .NET Class Library created with Microsoft .NET Framework 1.0 Version 1.0.3705 and Microsoft Visual C# .NET 55524-652-0000007-18869. This should be upwardly compatible with all later versions, including Visual Studio 2005 Express. It calls the functions in the core Win32 DLL using System.Runtime.InteropServices. The source code for this wrapper DLL is provided in the distribution. For more details on its use, see Using with .NET.

[Contents] [Index]

Self-Tests

The module performs power-up self-tests and conditional self-tests to ensure that it is functioning properly. Power-up self-tests are performed when the module is powered up, i.e. when the DLL is first attached to the parent Windows process. Conditional self-tests are performed when certain security functions are invoked.

Power-up Self-Tests

The following power-up self-tests are performed:-

Cryptographic algorithm test:

RNG health test:

The random number generator implementation performs the following self-tests to obtain assurance that the Deterministic Random Bit Generator (DRBG) continues to operate as designed and implemented (as per section 11.3, [SP80090]).

Software integrity test:

The integrity of the software module is tested using a 32-bit error detection code (EDC). The value of this EDC is set and stored when the module is created. On testing, the EDC is re-computed for the DLL module file being used and compared with the stored value. If the values do not match, the test fails.

In addition to this automatic software integrity test, the integrity of the entire DLL file can be independently verified by the user using published message digest and checksum values before and after installation - see CryptoSys PKI Toolkit Integrity Checks.

Conditional Tests

The following conditional tests are performed:-

Pair-wise consistency test:

When the module generates a public and private key pair, it carries out pair-wise consistency tests for both encryption and digital signing. The test involves encrypting a randomly-generated message with the public key. If the output is equal to the input message, the test fails. The encrypted message is then decrypted using the private key and if the output is not equal to the original message, the test fails. The same random message is then signed using the private key and then verified with the public key. If the verification fails, the test fails.

Continuous random number generator test:

When the module is first loaded or instantiated in a new thread, the RNG generates a 64-bit block which not used but is saved in thread-safe memory for comparison with the next 64-bit block to be generated. Each subsequent generation of a 64-bit block is compared with the previously generated block. The test fails if any two compared 64-bit blocks are equal. No blocks are saved that have actually been previously output by the generator.

Action if a self-test fails

Any failure of a power-up test or conditional test will cause the following actions to take place:
  1. An error message will be logged to the event log (NT+ systems only).
  2. The system will (try to) save the error message in a log file* in the same directory as the calling process executable.
  3. A message box will display on the screen.
  4. The DLL will terminate its own process** to prevent further use of cryptographic functions.
Note that you should never, ever see such a failure unless someone has interfered with the DLL file or some serious problem has occurred on your system.

* The error log file will be given a filename "pkierr.log". If the process does not have permissions to write to that directory, no log file be created.

** By terminate its own process, we mean that the CryptoSys DllMain function will return false. This will cause statically-linked applications to terminate, and applications that use LoadLibrary, like Visual Basic, to return an error message saying it cannot find the DLL file.

You can make settings in the machine's registry to prevent the message box displaying and to change the destination directory of the log file. See Optional Registry Settings. It is not possible to prevent the DLL from exiting if a critical error happens.

The user may call the power-up self-tests on demand with the PKI_PowerUpTests function. In the event that such an "on demand" test fails, the module will log the error event and return an error code but will not terminate the process.

Be aware that the automatic self-tests fail only in exceptional circumstances. You should never see one in practice unless the software module has been tampered with.

[Contents] [Index]

Critical Errors

A Critical Error occurs if and only if one of the following events occur: You should never see a critical error in practice unless the software module has been tampered with.

[Contents] [Index]

Optional Registry Settings

The following optional registry settings may be made to change the behaviour of the module if a critical error occurs.

Disclaimer Modifying the registry can cause serious problems that may require you to reinstall your operating system. We cannot guarantee that problems resulting from the incorrect use of the registry can be solved. Use the information provided at your own risk.

Disable message boxes if a critical error occurs

The default behaviour is to display a pop-up MessageBox if a critical error occurs. Users running the toolkit on an unattended server or within an IIS application can disable pop-ups to prevent problems (IIS gets a bit upset if an application displays a pop-up). Set the value to '1' to disable pop-up messages from appearing. Note that this will not prevent the 'first user' or expiry dialogs appearing with the Test Version.
Key: [HKEY_LOCAL_MACHINE\Software\DI Management\CryptoSysPKI\Options]
Value Name: NoMessageBox
Data Type: REG_DWORD
Data: 1 = MessageBoxes disabled, 0 = MessageBoxes enabled (default)

Set directory to write error log file after a critical error

The default behaviour if a critical error occurs is to try to write to an error log file in the same directory as the parent executable file that called the DLL. To change this directory create a REG_SZ value of 'ErrorLogDir' in the key below and set the value to the directory you want, e.g. "C:\myfolder\subdir". The directory name should not have a trailing slash character.
Key: [HKEY_LOCAL_MACHINE\Software\DI Management\CryptoSysPKI\Options]
Value Name: ErrorLogDir
Data Type: REG_SZ

Disable creation of error log file if a critical error occurs

To disable the creation of an error log file altogether, create a REG_DWORD value of 'NoErrorLog' in the key below. Set the value to '1' to disable.
Key: [HKEY_LOCAL_MACHINE\Software\DI Management\CryptoSysPKI\Options]
Value Name: NoErrorLog
Data Type: REG_DWORD
Data: 1 = Error log file disabled, 0 = Error log file enabled (default)

Record event log messages properly

The following registry entry is required for the Event Log messages to be recorded properly. If this entry is not present, or the path to the DLL is wrong, the event log entries will be of the form:
The description for Event ID ( 8xxx ) in Source ( diCrPKI ) cannot be found. The local computer may not have the necessary registry information or message DLL files to display messages from a remote computer.
For correct formatting of the message, create the REG_SZ and REG_DWORD values in the key below. The message will still be recorded even if this entry is not present.
Key: [HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Eventlog\Application\diCrPKI]
Value Name: EventMessageFile
Data Type: REG_SZ
Data: Full path to DLL file, e.g. "C:\WINNT\system32\diCrPKI.dll"
 
Value Name: TypesSupported
Data Type: REG_DWORD
Data: 7 (0x7)

Note that event log messages are only written in exceptional circumstances. You should never see one in practice unless the software module has been tampered with.

[Contents] [Index]

Random Number Generator

RNG Mechanisms

The random number generator used in the CryptoSys PKI toolkit has been redesigned as of Version 3.0 to conform to the more conservative NIST Special Publication 800-90 Recommendation for Random Number Generation Using Deterministic Random Bit Generators [SP80090], first published June 2006. Entropy is accumulated in "Fortuna" pools as described in Ferguson and Schneier, Practical Cryptography, [FERG03]. The full technical details are published on our web site.

The underlying RNG functions use the algorithms recommended in NIST SP 800-90 [SP80090] (the "DRBG Standard") to provide a Deterministic Random Bit Generator (DRBG). The HMAC_DRBG mechanism is used with SHA-1 as the underlying hash function. This outputs a sequence of binary bits that appears to be statistically independent and unbiased. The output is effectively random so long as internal actions of the process are hidden from observation. In particular the algorithm provides good Backtracking Resistance and, depending how it is used, good Prediction Resistance.

Entropy is accumulated at startup and whenever any function in the library is called. Only inobtrusive methods of collecting entropy are used, so you can use the toolkit safely in any application. The "Fortuna" method of pooling is used to prevent certain attacks from someone who controls some but not all of the entropy sources (see chapter 10 of [FERG03]). The more times your application calls the functions in the library before needing some random data, the more entropy will be accumulated. The user cannot control how or when the Fortuna entropy is added to the RNG process - this is by design. The advantage of the Fortuna system is that the level of entropy does not need to be measured. There is, however, a period of vulnerability just after start up when there may not be sufficient entropy in the pools. This can be overcome by initializing with a seed file.

We strongly recommend that you use and initialize with a seed file wherever possible.

Techniques to add known security strength to the RNG process

1. Use a seed file
Use the RNG_Initialize function to specify a seedfile with a known minimum amount of entropy to initialise the PRNG. This seed file is updated automatically when used. You can optionally call the RNG_UpdateSeedFile from time to time in your application, and use RNG_MakeSeedFile to create a new one. The security of this method is as good as the security you have over the seed file. If an attacker controls the seed file, it does not mean they control the random output data; it just means that using a seedfile does not increase the security strength of the PRNG.
2. Make the user enter random keystrokes
Use the RNG_BytesWithPrompt function when generating random data to force the user to generate entropy using random keystrokes and mouse movements. RNG_MakeSeedFile also uses such a prompt. This works provided you know the user's keyboard strokes and mouse movements are secure (e.g. are not being transmitted over a network).
3. Add your own entropy
If you have your own independent source of entropy, add this "additional input" to the RNG process as a "seed" when using the RNG_Bytes function. If you assume zero security strength for the internally-generated entropy and you add input with, say, 128 bits of security strength, then the output from the RNG will have at least 128 bits of security strength.

User-supplied entropy (seeds)

User-supplied entropy (a.k.a. a "seed") is added as "additional input" to the generation process. It does not affect the accumulation pools and cannot be used by an attacker to control the output.

Remember it's not how "random" your user-supplied entropy is, but how little an attacker knows about it. Using the current time is no use. If you can provide 32 bytes* of data of which an attacker knows nothing and cannot later discover, then you have added 128 bits of security strength.
* The bytes must have been selected randomly from the range 0 to 255.

Here is an example in VB6 of how you could use the RNG to generate user-supplied entropy when creating a new pair of RSA keys. (The password should be entered separately, not hard-coded like this!)

Dim nRet As Long
Dim nBits As Long
Dim strPublicKeyFile As String
Dim strPrivateKeyFile As String
Dim strPassword As String
Dim strSeed As String

nBits = 512
strPublicKeyFile = "mykeypub.bin"
strPrivateKeyFile = "mykeypri.bin"
strPassword = "password"

' 1. Generate some user-derived entropy using the keyboard
strSeed = String(64, " ")
nRet = RNG_StringWithPrompt(strSeed, Len(strSeed), "", 0)

' 2. Create a new pair of RSA key files, adding this seed to the process
Debug.Print "About to create a new RSA key pair..."
nRet = RSA_MakeKeys(strPublicKeyFile, strPrivateKeyFile, nBits, _
    PKI_RSAEXP_EQ_65537, 50, 1000, strPassword, strSeed, Len(strSeed), 0)
Debug.Print "RSA_MakeKeys returns " & nRet & " (expected 0)"

' 3. Immediately wipe the sensitive data
Call WIPE_String(strSeed, Len(strSeed))
Call WIPE_String(strPassword, Len(strPassword))

And the same example in C# (VB.NET is very similar)

int r;
byte[] seed;
int nbits = 512;
string publicKeyFile = @"C:\Test\mykeypub.bin";
string privateKeyFile = @"C:\Test\mykeypri.bin";
StringBuilder sbPassword = new StringBuilder("password");

// 1. Generate some user-derived entropy using the keyboard
seed = Rng.BytesWithPrompt(64,"",Rng.Strength.Default);
Debug.Assert(seed.Length > 0, "Failed to create a seed");

// 2. Create a new pair of RSA key files, adding this seed to the process
r = Rsa.MakeKeys(publicKeyFile, privateKeyFile, nbits, 
	Rsa.PublicExponent.Exp_EQ_65537, 1000, sbPassword.ToString(),
	Rsa.PbeOptions.Default, false, seed);
Console.WriteLine("Rsa.MakeKeys returns {0} (expected 0)", r);

// 3. Immediately wipe the sensitive data
Wipe.Data(seed);
Wipe.String(sbPassword);

For more details on the security aspects of the random number generator, see the technical details published on our web site.

[Contents] [Index]

Specifying Distinguished Names

To specify a distinguished name for an X.509 certificate or certificate request, we use a string of attribute key=value pairs separated by semicolons (";"). The format is
key=value(;key=value)*
Supported keys are: The value can contain any valid character except the semicolon ";" (ASCII character 0x3B, Unicode U+003B).

Examples of distinguished name specifications

"C=US;O=Example Organisation;CN=Test User 1"
"CN=Carol"
"CN=My User;O=My Org;OU=Unit;C=AU;L=My Town;S=NSW;E=myuser@my.org"

At least one attribute must be specified. Spaces are significant between the "=" and the ";". Only a semicolon can be used as a separator - commas are treated as normal characters. Semicolons are not permitted in attribute values. The distinguished name attributes are written to the certificate name in the order they are found. Keys may be repeated. Note that the Windows Certificate Manager displays the attributes in reverse order.

We keep the deprecated emailAddress attribute here because it seems so popular. Note that the emailAddress attribute of the distinguished name is independent of the RFC822 address in a subjectAltName extension, which can be specified separately.

Default encoding

The default encoding is IA5String for the emailAddress attribute and PrintableString for all other attributes. If the input string includes characters that are not valid for these encodings, then a T61String (TeletexString) will be used instead as a fudge. Certificates created with a T61String may not be accepted as valid by some profiles. To force UTF-8 encoding, specify the PKI_X509_UTF8 flag.

UTF-8 encoding in distinguished names

If the PKI_X509_UTF8 flag is specified in nOptions, all new DN attribute strings will be encoded as UTF8String. (According to Section 4.1.2.4 of [PKIX], UTF-8 encoding is now mandatory for all new certificates issued under that profile after 31 December 2003.)

If the value contains only valid UTF-8 characters, then [New in Version 3.1] the string will be copied directly. Otherwise the input is assumed to be 8-bit Latin-1 and will be converted to UTF-8 accordingly, with each 8-bit character being converted to two UTF-8 bytes.

Entering hexadecimal-encoded values

[New in Version 3.1] As an alternative, you can enter the value as a hexadecimal-encoded string if the value is preceded by #x.

Examples:

Input stringDefault encodingWith PKI_X509_UTF8
OU=abcPrintableString "abc"UTF8String "abc"
OU=#x616263PrintableString "abc"UTF8String "abc"
C=MéxicoT61String "México"UTF8String "México"
C=#x4de97869636fT61String "México"UTF8String "México"
C=#x4dc3a97869636fT61String (garbage)UTF8String "México"
CN=#xE5A4A7E58DABT61String (garbage)UTF8String (U-5927,U-536B)

Note that the entire value must be preceded by the two characters "#" (number sign, hash sign) and "x" (lower-case letter X). The remainder of the value must consist only of valid hexadecimal characters [0-9A-Fa-f]. Note, too, that the hex digits are not case sensitive, but the "x" is. If you actually want to enter a value string that begins with "#x", then enter as "##x"; e.g. "OU=##xabc" will produce the OU value "#xabc".

[Contents] [Index]

Specifying the algorithm and mode for generic block cipher functions

[New in Version 3.2] The generic block cipher functions allow the block cipher algorithm and mode to specified either by a strAlgAndMode string or by using the nOptions flags, but not both. The algorithm-and-mode parameter string combines the name of the block cipher algorithm and the mode.

Valid algorithm names are:

ValueAlgorithmOption
tdeaTriple DES, a.k.a. 3DES, des-ede3PKI_BC_TDEA
3desAlternate for Triple DESPKI_BC_3DES
des-ede3Another alternate for Triple DESPKI_BC_DESEDE3
aes128AES-128PKI_BC_AES128
aes192AES-192PKI_BC_AES192
aes256AES-256PKI_BC_AES256

We have used "TDEA" consistently in CryptoSys products to refer to the Triple DES algorithm (as in its official name "Triple Data Encryption Algorithm"). In this case, we have given you the alternative ways of expressing the algorithm as any one of "tdea", "3des" or "des-ede3". These are all equivalent and all yield identical results.

Valid mode names are:

ValueModeOption
ecbElectronic Code Book mode (default)PKI_MODE_ECB
cbcCipher Block Chaining modePKI_MODE_CBC
ofbOutput Feedback mode PKI_MODE_OFB
cfb64-bit Cipher Feedback mode PKI_MODE_CFB
ctrCounter mode PKI_MODE_CTR

Some examples of valid string values for the strAlgAndMode parameter are:

strAlgAndModeDescriptionAlternative Option value
tdea-cbcTriple DES in CBC modePKI_BC_TDEA+PKI_MODE_CBC
3des-cbcditto (alternate name)PKI_BC_3DES+PKI_MODE_CBC
des-ede3-cbcditto (alternate name)PKI_BC_DESEDE3+PKI_MODE_CBC
tdea-ecbTriple DES in ECB modePKI_BC_TDEA+PKI_MODE_ECB
tdeaditto (ECB is default mode)PKI_BC_TDEA
aes128-cbcAES-128 in CBC modePKI_BC_AES128+PKI_MODE_CBC
aes256-ctrAES-256 in Counter modePKI_BC_AES2568+PKI_MODE_CTR

Punctuation and space characters and upper- and lower-case are ignored in the strAlgAndMode string, so "tdea-cbc", "TDeA---cBc", "tdea cbc", and "TDEACBC" are equivalent (as indeed is "t*D$e^A c@b!C"!!)

It is an error to use both the strAlgAndMode and nOptions parameters to specify the algorithm and mode. The algorithm must be explicitly specified. There is no default algorithm. The default cipher mode is ECB mode, which is not recommended because of security issues. It is recommended to use either CBC or CTR mode with a IV value that is unique each time it is used with a given key.

[Contents] [Index]

Valid key and block sizes for block cipher algorithms

AlgorithmKey size (bytes)Block size (bytes)IV size (bytes)Valid ECB/CBC data lengths
Triple DES24888, 16, 24, 32, ... bytes
AES-12816161616, 32, 48, 64, ... bytes
AES-19224161616, 32, 48, 64, ... bytes
AES-25632161616, 32, 48, 64, ... bytes

[Contents] [Index]

Base64 alternative for X.509 certificates

[New in Version 3.1] Those X.509 functions which require you to pass the filename of an X.509 certificate will now accept a base64 string representation of the certificate instead. This is the base64 string that can be obtained using the X509_ReadStringFromFile function. The first character in such a string should always be an "M".

[New in Version 3.2] You can also pass a string containing the certificate in PEM format. PEM format looks like

-----BEGIN CERTIFICATE-----
MIHgMIGaAgEBMA0GCSqG...
-----END CERTIFICATE-----

See PEM string alternative below for more details.

The example below shows how each of the filename, the base64 string, or the PEM-format string can be used in a typical X.509 function.

Dim nRet As Long
Dim strCertFileOrB64String As String
Dim strHexHash As String

' Compute the SHA-1 `thumbprint' of an X.509 certificate in two forms
strHexHash = String(PKI_SHA1_CHARS, " ")

' Refer to file itself...
strCertFileOrB64String = "smallca.cer"
nRet = X509_CertThumb(strCertFileOrB64String, strHexHash, Len(strHexHash), 0)
Debug.Print "X509_CertThumb returns " & nRet & " for '" & strCertFileOrB64String & "'"
Debug.Print "SHA-1 thumbprint=" & strHexHash

' Use base64 string representation directly...
strCertFileOrB64String = _
    "MIHgMIGaAgEBMA0GCSqGSIb3DQEBBQUAMAwxCjAIBgNVBAMTAUEwHhcNMDcwODAyMDIwMDAxWhc" _
    & "NMTEwODAyMDIwMDAxWjAMMQowCAYDVQQDEwFBMEowDQYJKoZIhvcNAQEBBQADOQAwNgIxA1KS" _
    & "JlPSmQAqQgDHUISaUsCrHbIZe249i6jFtfN3rA7czrP4CXS3mjvMFf0AsxV6BwIBAzANBgkqh" _
    & "kiG9w0BAQUFAAMyAACeT7GtgmBRKUN20cIyNEGneEvmNxaliuBEVkg2npbyEBgeHXOH6jqj9Ase348UN/Q="
nRet = X509_CertThumb(strCertFileOrB64String, strHexHash, Len(strHexHash), 0)
Debug.Print "X509_CertThumb returns " & nRet & " for '" & strCertFileOrB64String & "'"
Debug.Print "SHA-1 thumbprint=" & strHexHash

' Again using a PEM-style string...
strCertFileOrB64String = _
    "-----BEGIN CERTIFICATE-----" & vbCrLf _
    & "MIHgMIGaAgEBMA0GCSqGSIb3DQEBBQUAMAwxCjAIBgNVBAMTAUEwHhcNMDcwODAyMDIwMDAxWhc" & vbCrLf _
    & "NMTEwODAyMDIwMDAxWjAMMQowCAYDVQQDEwFBMEowDQYJKoZIhvcNAQEBBQADOQAwNgIxA1KS" & vbCrLf _
    & "JlPSmQAqQgDHUISaUsCrHbIZe249i6jFtfN3rA7czrP4CXS3mjvMFf0AsxV6BwIBAzANBgkqh" & vbCrLf _
    & "kiG9w0BAQUFAAMyAACeT7GtgmBRKUN20cIyNEGneEvmNxaliuBEVkg2npbyEBgeHXOH6jqj9Ase348UN/Q=" & vbCrLf _
    & "-----END CERTIFICATE-----"
nRet = X509_CertThumb(strCertFileOrB64String, strHexHash, Len(strHexHash), 0)
Debug.Print "X509_CertThumb returns " & nRet & " for '" & strCertFileOrB64String & "'"
Debug.Print "SHA-1 thumbprint=" & strHexHash
X509_CertThumb returns 40 for 'smallca.cer'
SHA-1 thumbprint=a36b1bfa0af41a2785066b2d5135b67011ac3b7f
X509_CertThumb returns 40 for 'MIHgMIGaAgEBMA0GCSq...(snip)...HXOH6jqj9Ase348UN/Q='
SHA-1 thumbprint=a36b1bfa0af41a2785066b2d5135b67011ac3b7f
X509_CertThumb returns 40 for '-----BEGIN CERTIFICATE-----
MIHgMIGaAgEBMA...(snip)...BgeHXOH6jqj9Ase348UN/Q=
-----END CERTIFICATE-----'
SHA-1 thumbprint=a36b1bfa0af41a2785066b2d5135b67011ac3b7f

[Contents] [Index]

PEM string alternative for X.509 certificates and RSA key files

[New in Version 3.2] In the same way you can pass a base64 string instead of an X.509 filename, you can now pass a string containing the certificate in PEM format. The PEM format looks like

-----BEGIN CERTIFICATE-----
MIHgMIGaAgEBMA0GCSqG...
-----END CERTIFICATE-----

Similarly, those RSA functions which require you to pass the filename of an RSA key file will now accept a string that contains the file contents in PEM format. An RSA key file in PEM format looks like

-----BEGIN ENCRYPTED PRIVATE KEY-----
MIICojAcBgoqhkiG9w0BDAEDMA4ECHPQz6NdAmoFAgIH0ASCAoBKn9KXr+dm
Vtc0ZhEog7t3Prs4rJazwUsXExU78ePLMquxLi/cPmqtyjb472r6XUOa...
-----END ENCRYPTED PRIVATE KEY-----

The functions will accept all strings that start with "-----BEGIN" and are of the form

-----BEGIN XXX-----
(base64-encoded data)
-----END XXX-----

provided there is a newline character (LF or CRLF) between the pre-encapsulation boundary "-----BEGIN XXX-----" and the start of the base64 data, and another before the post-encapsulation boundary "-----END XXX-----". So do not remove the newline characters from the PEM string. The exact word or words used for "XXX" do not matter. Any non-base64 characters found in the encoded data will be ignored.

This means, for example, that you can store your certificates and encrypted private keys as strings in a database. Note that an X.509 certificate can be passed either as a plain base64 string or in PEM format; that is, both with and without the "-----BEGIN CERTIFICATE-----" encapsulation; but RSA key data can only be passed in PEM format.

Examples

This first example shows how an encrypted private key can be read from a string instead of a file.
Dim strKeyPemData As String
' The vbCrLf after the first line is important and so is the one before the last line
strKeyPemData = _
    "-----BEGIN ENCRYPTED PRIVATE KEY-----" & vbCrLf & _
    "MIICojAcBgoqhkiG9w0BDAEDMA4ECHPQz6NdAmoFAgIH0ASCAoBKn9KXr+dm" & vbCrLf & _
    "Vtc0ZhEog7t3Prs4rJazwUsXExU78ePLMquxLi/cPmqtyjb472r6XUOa9J/v" & vbCrLf & _
    "g2gYHlJ7D7FfAdTdVbHmXWfZzdIqI+AKZmrMoIfSVSSrI8mLDXLDgJVm2Gxa" & vbCrLf & _
    "r/YJ154L4fwqWjj0b06v8nTrXTp7G3ZSxjmXc3auf8tS1RatpDuSn027jBGt" & vbCrLf & _
    "Pg2CGPjeSomOU7Efd89R+gryW3RfXaMEv1TtGmdS+szxN4TAzgFTzjzE7qJ2" & vbCrLf & _
    "+WL09hBRxSyi5JybbxblrO5zDbGJD8rq4kGawWUj4PYDpOkxQYQyK/cALEvv" & vbCrLf & _
    "EipLeWvk03CadKER3EcpL7wQT3N5wJGNx7GR3efkO7lO/VfGf6kYFsJ8Qt94" & vbCrLf & _
    "vBlgq84abgSD+rlRX03re/NLJQ00Qxl3bDrkSiRoXSfBiOeVzBVTsh03Sj4B" & vbCrLf & _
    "V0v2KLENsMXr40rMqTGfKD3V+FyYUehWEkEl3NrIVpBSJir+g4H3tl76SdNe" & vbCrLf & _
    "mq/cTtQP+EY8fpC3I46dyDXFat3wQfubw+E5nGfv7xp6vRVRRolpZx7DpuB/" & vbCrLf & _
    "z1tzO3uP0vJ0pjATriO/ZAVs6UrXx+DJ6XsfrAVt0jpW5Ngr8rm2EiD3/1T9" & vbCrLf & _
    "7q1dELJ7GzCY1dG99XVjt9ZXb7cI8zsPpT/gzQJLfeLe3U5Mdw0hKZLfPCex" & vbCrLf & _
    "0urs3ytK0XNu+jZAYeSaysG8/rHJaH74WOgJ8gnSPY4QtWsu6+3qBErS2jbq" & vbCrLf & _
    "7E2jRvBKWICVd1yiQCDq/c6s9LeYhNhZsmcWxuX9b4lG9f1LHZy0djhIYi4x" & vbCrLf & _
    "IpcEfjkTH+7zUOkMQ+fXZHtSEVFt9L2Ci49jB8YReqbfOuDFzzwsk3xxfL2h" & vbCrLf & _
    "ZoRK" & vbCrLf & _
    "-----END ENCRYPTED PRIVATE KEY-----"
    
Dim nLen As Long
Dim strPassword As String
Dim strPrivateKey As String

strPassword = "password"

' How long is PrivateKey string?
nLen = RSA_ReadEncPrivateKey("", 0, strKeyPemData, strPassword, 0)
If nLen <= 0 Then
    Debug.Print "ERROR: RSA_ReadEncPrivateKey returns " & nLen
    Exit Sub
End If
' Pre-dimension the string to receive data
strPrivateKey = String(nLen, " ")
' Read in the Private Key
nLen = RSA_ReadEncPrivateKey(strPrivateKey, Len(strPrivateKey), strKeyPemData, strPassword, 0)
If nLen <= 0 Then
    Debug.Print "ERROR: RSA_ReadEncPrivateKey returns " & nLen
    Exit Sub
End If
Debug.Print "Private key is " & RSA_KeyBits(strPrivateKey) & " bits long."

' ... do something with the private key...

' then make sure it is deleted
strPrivateKey = wipeString(strPrivateKey)

This should produce the output

Private key is 1024 bits long.

[Contents] [Index]

CMS Content Types

Cryptographic Message Syntax (CMS) is a syntax used to digitally sign, digest, authenticate, or encrypt arbitrary message content. CMS is a stricter subset of PKCS#7. There are several CMS content types but S/MIME currently only uses four of them, namely Data, SignedData, EnvelopedData, and CompressedData content types. This Toolkit provides the means to create both digitally-signed SignedData and encrypted EnvelopedData objects according to the CMS version 1 specifications.

CMS SignedData objects

A SignedData object is a digitally-signed container for arbitrary message content. You can create a SignedData object using one of the CMS_MakeSigData, CMS_MakeSigDataFromString, CMS_MakeSigDataFromSigValue, or CMS_MakeDetachedSig functions.

The original specification for a SignedData object is in RSA Lab's PKCS#7 Cryptographic Message Syntax Standard [PKCS7]. The last complete version of this document is version 1.5, which is also republished as RFC 2315. There is also a version 1.6 which is currently just an addendum note [PKCS7-EXT] and which extends the original specification. The CMS specification Cryptographic Message Syntax [CMS] is based on PKCS#7 version 1.5 and ties down some of its ambiguities. S/MIME [SMIME-MSG] uses the CMS specification. Between them, these various documents define five versions of a SignedData object. We support CMS version 1 only (but with a side order of PKCS#7 version 1.6 "naked" SignedData objects also thrown in) - see Supported Algorithms.

A CMS version 1 SignedData object has a variety of possible combinations in what it can contain:

By now we hope you have got the idea that there is whole host of combinations of how to deal with these objects. To verify that the message content was indeed signed by the signer requires the recipient to do the following:
  1. Obtain a copy of the signer's X.509 certificate, unless this is already included in the SignedData, and verify independently that this certificate is valid.
  2. Decrypt the signature in the SignedData using the public key inside the signer's certificate.
  3. Verify that the message digest of the eContent matches the message digest included in the SignedData.
The function CMS_VerifySigData carries out steps 2 and 3 directly with options for the user to pass the signer's certificate details if they are not already included and also to pass the message digest of the eContent for detached signatures.

The function CMS_GetSigDataDigest will extract the message digest, if possible, to enable the user to perform their own separate comparison with an independently-computed message digest. Note that being able to retrieve the message digest with this function implicitly verifies that the purported signer really did use their private key to sign the object. However, unlike the CMS_VerifySigData function, success with this function does not necessarily mean that the signer actually signed the eContent itself. Furthermore, if the signer used the DSA signature algorithm and did not include message attributes, then you cannot directly extract the message digest of the eContent. Confused so far? Try writing this manual.

To extract just the certificates themselves from a SignedData object, use the X509_GetCertFromP7Chain function. This will work for all types of SignedData objects, not just the "certs-only" type.

[Contents] [Index]

Using in MIME-conformant email messages

This toolkit produces enveloped-data and signed-data CMS objects which can be used in S/MIME v3 messages. To send the output as an email message you first need to wrap it in a MIME body, and to decrypt or verify an incoming message you need to extract the CMS object first. The toolkit does not do this wrapping or extraction for you (at least not in this release).

Sending an enveloped-data object

The output from the CMS_MakeEnvData or CMS_MakeEnvDataFromString function is a CMS object. This CMS object needs to be inserted into an application/pkcs7-mime MIME entity before being sent as an email message.

The CMS object is sent as an attachment to the email, usually with the name smime.p7m. If your email program allows you to tailor the headers, you should identify the Content-Type as application/pkcs7-mime; smime-type=enveloped-data. Most email programs convert the binary CMS object file into base64 encoding automatically. If not, you can use the PKI_CMS_FORMAT_BASE64 option to generate the output directly in base64 encoding.

Ann creates a message for Ben using the default option:

nRet = CMS_MakeEnvDataFromString("smime.p7m", _
    "Be in bar at 7 pm wearing a red rose", "ben.cer", "", 0, 0);
The output file smime.p7m will be in binary BER-encoded format. To send as an S/MIME email, you attach this file to your email message and you need to add the following headers:-
Date: Mon, 23 Feb 2004 12:00:52 +1100
From: alice@example.com
To: ben@example.com
Subject: Secret message
MIME-Version: 1.0
Content-Type: application/pkcs7-mime;
    smime-type=enveloped-data; name="smime.p7m"
Content-Transfer-Encoding: base64
Content-Description: attachment;filename=smime.p7m

MIAGCSqGSIb3DQEHA6CAMIACAQAxgZMwgZACAQAwOjA0MQswCQYDVQQGEwJBVTEV
...
When Bob receives the email, he can either:-
  1. Rely on his email program correctly identifying the enveloped-data pkcs7-mime message and using his certificate and private key he previously loaded into the Windows certificate store to decrypt it; or
  2. Save the attachment from the email to a file on his hard drive and decrypt it using the toolkit CMS_ReadEnvData function.
Identifying the Content-Type as application/pkcs7-mime and enveloped-data should ensure that the receiving email program correctly identifies the file as an encrypted email and treats it accordingly. This is optional, and you may find it more convenient to agree with your sending parties not to do this so you can just save the attached data file directly without getting involved in the "security" hoops that programs like Outlook Express will put you through.

If you can't save the attachment file directly from your email program, remember that email files are just simple text files (even though they may have a .EML extension) and can be edited using a simple text editor like NotePad. Open the email in your favorite text editor and do a cut-and-paste of the attachment data to another file. Use the PKI_CMS_FORMAT_BASE64 option when reading what you've saved.

Sending an signed-data object

There are two formats for signed messages for S/MIME:
  1. S/MIME application/pkcs7-mime signed message
  2. S/MIME multipart/signed message
The first format is simpler for our purposes. It consists of a signed-data CMS object which includes the signed content as created by the default CMS_MakeSigData or CMS_MakeSigDataFromString functions. The MIME headers are similar to the enveloped-data example above:-
MIME-Version: 1.0
To: ben@example.com
From: ann@example.com
Subject: Signed message
Date: Mon, 23 Feb 2004 12:00:52 +1100
Content-Type: application/pkcs7-mime; smime-type=signed-data;
    name=smime.p7m
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename=smime.p7m

MIIDmwYJKoZIhvcNAQcCoIIDjDCCA4gCAQExCTAHBgUrDgMCGjAtBgkqhkiG9w0BBwGgIA
QeDQpUaGlzIGlzIHNvbWUgc2FtcGxlIGNvbnRlbnQuoIIC4jCCAt4wggKdoAMCAQICAgDI
...
To read, just save the attachment to your hard drive and read the file with CMS_ReadSigData or CMS_ReadSigDataToString.

The second format, S/MIME multipart/signed, includes the actual content in the body of the email message and attaches a "detached signature" signed-data object identified as Content-Type: application/pkcs7-signature; name=smime.p7s in the MIME part header. This has the advantage that the signed content can be read directly in the email message, but you need a more sophisticated email program to create the final message. The detached signature CMS object can be created using the CMS_MakeDetachedSig function.

A typical multipart/signed message is:

MIME-Version: 1.0
To: ben@example.com
From: ann@example.com
Subject: Multi-part signed message
Date: Mon, 23 Feb 2004 12:00:52 +1100
Content-Type: multipart/signed;
    micalg=SHA1;
    boundary="----=_NextBoundry";
    protocol="application/pkcs7-signature"

This is a multi-part message in MIME format.

------=_NextBoundry

This is some sample content.
------=_NextBoundry
Content-Type: application/pkcs7-signature; name=smime.p7s
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename=smime.p7s

MIIDeQYJKoZIhvcNAQcCoIIDajCCA2YCAQExCTAHBgUrDgMCGjALBgkqhkiG9w0BBwGggg
LiMIIC3jCCAp2gAwIBAgICAMgwCQYHKoZIzjgEAzASMRAwDgYDVQQDEwdDYXJsRFNTMB4X
...

------=_NextBoundry--
You are strongly recommended to use quoted-printable encoding for the content part of the message to prevent the signed data being changed during transmission.

More information on S/MIME

For more details on how to insert your CMS objects into MIME bodies (honest, that's the term they use), please refer to S/MIME Version 3 Message Specification [SMIME-MSG] and Examples of S/MIME Messages [SMIME-EX]. Stallings also covers the topic well in Cryptography and Network Security [STAL02].

[Contents] [Index]

Raw RSA Techniques

The original intention of the toolkit was to provide a set of primitives to carry out S/MIME operations using relatively high-level functions. However, we get so many questions about using the "raw" RSA functions that we've added this section on techniques.

The functions RSA_RawPublic and RSA_RawPrivate just carry out the basic RSA encryption or decryption operation on a "raw" block of data. The block must be exactly the same length in bytes as the length of the RSA key modulus; it must obey certain mathematical properties (in practice, make sure the first byte is zero); and it should be "padded" in a certain way to improve security and make it easier to pass to other systems (the built-in cryptographic functions in .NET hide this part of the process from you).

Encrypting and signing with RSA

Encryption and signing use the same RSA operations:

Use the function RSA_EncodeMsg to encode or "pad" the message data you want to encrypt or sign. Remember that Encoding is Not Encryption.

Examples

Please check the latest examples on our web site at <http://www.cryptosys.net/pki/pkiexamples.html>.

More Techniques

For encryption in practice, except for very short messages, we generate a random session key, encrypt that using RSA, and then encrypt the plaintext using a faster, symmetric block cipher like Triple DES (hint to implementors: do this in CBC mode, it's much more secure than ECB). This session key is sometimes referred to as the Content Encryption Key (CEK). You would then need to transmit a message to your recipient in the form
+-------------------+----+------------------------------------------------+
| RSA-encrypted-CEK | IV | Data-encrypted-with-symmetric-cipher-using-CEK |
+-------------------+----+------------------------------------------------+
where IV is the initialization Vector for the block cipher encryption, generated uniquely (and secretly) each time. To decrypt, parse the input into its three components, use the RSA private key (held separately) to decrypt the RSA block and hence get the CEK to use to decrypt the main body of data. This technique is as strong as its weakest link. Triple DES with a full 192-bit triple key is equivalent in security to a 2048-bit RSA key (see [SP80057]).

For digital signing, unless the message is very short, we generate a message digest of the original message using a hash function, "encrypt" the digest using the RSA private key, and then send this block on to our recipient as a "digital signature", usually together with the message itself.

+------------------+---------------------+
| Original message | RSA Signature block |
+------------------+---------------------+

To verify, the recipient parses the input into its two components and then "decrypts" the RSA block using the sender's public key to recover the message digest. She then independently computes the message digest hash of the received message and compares the two. If they are the same, then the signature has been verified.

Function List

CMS  RSA Keys  Raw RSA  X.509 Certificates  PFX  Triple DES  Cipher  Hash  HMAC  Encoding Conversion  RNG  Miscellaneous 

* New in Version 3.2

CMS functions