CryptoSysTM KeyExchange Programmers' Manual

Introduction

CryptoSys KeyExchange is a library of primitive functions and methods to enable developers to build applications using Diffie-Hellman key agreement schemes and key establishment protocols. The library conforms to ANSI X9.42-2003 Agreement of Symmetric Keys Using Discrete Logarithm Cryptography [ANSX942]. The random number generator (RNG) uses techniques and recommendations from NIST Special Publication 800-90 Recommendation for Random Number Generation Using Deterministic Random Bit Generators [SP80090].

Key establishment schemes are used by two parties to establish common shared secret information like cryptographic keys. CryptoSys KeyExchange provides functions and methods for programmers in C, C++, Classic Visual Basic (VB6), VBA, COM, ASP, C# and VB.NET. The core executable has a small footprint under 150 kB. It works on all Windows® Win32 systems (W95/98/Me/NT4/2000/XP/2003).

Contents

[Contents]

Algorithms included

Generate domain parameters
Generates a set of domain parameters (p,q,g) both with and without the ANSI X9.42 seed and counter validation parameters.
Validate domain parameters
There is a "quick" test and a slower and more thorough test using the ANSI X9.42 seed and counter validation parameters.
Generate a key pair
Creates a new public/private key pair suitable for both static and ephemeral use. There are options to add extra entropy to the RNG process either directly or via the keyboard.
Validate a public key
Checks that a public key received from another party is valid.
Compute the shared secret
Enables either party to compute the Diffie-Hellman shared secret value.
Derive keying material
Based on concatenation.
There are also utilities to generate random numbers, initialize the random number generator (RNG) with a seed file, add additional user-supplied entropy to the process, and handle hexadecimal-encoded bit strings.

[Contents]

Theory

There is no theory explained here. You are assumed to be familiar with the concepts and have read the relevant references. If you don't understand the theory, this library is not for you. If you can't afford the X9.42 standard (available from the ANSI eStandards Store), then read the Summary of ANSI X9.42 [SUMX942] available from NIST. There is also an RFC [RFC2631] available for free from IETF which is based on an older (and different!) draft of the ANSI standard.

[Contents]

Mechanics

[Contents]

Limitations

[Contents]

Known Issues

We are unable to duplicate the test vectors provided in section D.5.1 of ANSI X9.42-2003 Examples of the Key Derivation function Based on Concatenation. We discuss the details of this further on a page on our web site.

For the record, the current implementation of DeriveKeyingData computes the value of

SHA-1(ZZ || Counter || OtherInfo)
for successive values of Counter = (0000000116, 0000000216, ...) where Counter is represented as a 32-bit octet string in "big-endian" order. The values of ZZ and, if provided, OtherInfo are digested directly as bit strings.

[Contents]

Methods for COM and .NET users

The methods in the COM and .NET classes are almost identical in behaviour. There is a separate help file CryptoSysKX.chm available on the .NET methods and classes. Only the DiffieHellman and kxDh classes require instantiation.

CAUTION: Do not use the GUI option Domain.ShowProgress or the "WithPrompt" methods with an ASP program or in any other environment where dialogs or console windows may interfere with your application.

Class names

.NET ClassCOM ClassDescriptionInstantiate with New
DiffieHellmankxDhDiffie-HellmanYes
RngkxRngRandom number generatorNo
HexkxHexHexadecimal encoding and decodingNo
GeneralkxGenGeneral utilitiesNo

DiffieHellman and kxDh Classes

SetDomainParams
Sets the domain parameters.
GenerateDomainParams
Generate a new set of domain parameters.
IsValidDomainParams
Validate the domain parameters (full version).
IsValidDomainParamsQuick
Validate the domain parameters (quick version).
GenerateKeyPair
Generate a new private/public key pair.
GenerateKeyPairWithPrompt
Generate a new private/public key pair with a prompt for extra entropy.
IsValidPublicKey
Validate a public key.
DeriveSharedSecret
Derive a Diffie-Hellman shared secret value.
DeriveKeyingData
Derive keying data based on concatenation.
Clear
Clears all data in the object and securely wipes any private key.
All bit-string arguments are passed as hexadecimal-encoded strings. Errors are indicated by returning an empty string or False value together with a description in the ErrMsg property.

DiffieHellman and kxDh Properties

ErrMsg
Error message
G
Domain parameter g, a generator
KeyBits
Size of p in bits
P
Domain parameter p, a prime
pgenCounter
Validation counter
Q
Domain parameter q, a prime factor of p-1
Seed
Validation seed
X
Private key, x (or r)
Y
Public key, y (or t)
Only the domain parameters (P, Q, G) and validation parameters (Seed, pGenCounter) can be set directly and then only by using the SetDomainParameters method. The private and public key properties (X, Y) are only used by the GenerateKeyPair method. The ErrMsg property is set if an error occurs.

Rng and kxRng Classes

Generate
Generate a random bit string.
GenerateWithEntropy
Generate a random bit string with user-provided entropy.
GenerateWithPrompt
Generate a random bit string and prompt the user for more entropy.
Initialize
Initialize the PRNG generator with a seed file.
MakeSeedFile
Create a new seed file suitable for use with Rng.Initialize.
Test
Carries out FIPS140-2 RNG statistical tests and a health check on the PRNG.
UpdateSeedFile
Updates the PRNG seed file.

Hex and kxHex classes

Encode/EncodeFromBytes/EncodeFromString
Encode 8-bit binary data to equivalent hexadecimal string format.
DecodeToBytes/DecodeToString
Decodes the specified String representation of a value consisting of hexadecimal (base 16) digits to an equivalent array of 8-bit unsigned integers (or directly to an ANSI string).
Filter
Strip non-hex chars from input string.

General and kxGen Classes

BitLength
Computes the length in bits of a hex-encoded bit string.
CompileTime
Gets date and time the core CryptoSys DLL module was last compiled.
LicenceType
Returns the licence type of the core CryptoSys DLL (D=Developer, T=Trial).
ModuleName
Gets full path name of core CryptoSys DLL module.
PowerUpTests
Carries out FIPS140-2 power-up tests and a health check on the PRNG.
Version
Returns version number of core CryptoSys DLL.
WipeString
Securely wipes a string.

Domain Enumeration

Options for domain generation and validation.
EnforceStdLen
Enforce X9.42 prime bit length requirements (L >= 1024 and L is a multiple of 256).
ShowProgress
Show prime generation/validation progress in console. In a GUI application this will create new temporary console windows for each prime - do not close these windows.
Default
Neither enforce standard length requirements nor show progress.

PrngStrength Enumeration

Specifies the security level for random number generation where the user is promped to type characters on the keyboard.
Bits_112
112 bits of security (default).
Bits_128
128 bits of security.
Default
112 bits of security.

[Contents]

Core functions for C, C++ and "raw" classic Visual Basic users

Syntax

/* DIFFIE-HELLMAN FUNCTIONS */
nRet = KXDH_GenerateDomainParams(szdhP, szdhQ, szdhG, nMaxChars, nKeyBits, nOptions);
nRet = KXDH_GenerateDomainParamsEx(szdhP, szdhQ, szdhG, szValidationSeed, nMaxChars, nKeyBits, nSeedBits, nOptions);
nRet = KXDH_ValidateDomainParams(szdhP, szdhQ, szdhG, nBits, szValidationSeed, nPgenCounter, nOptions);
nRet = KXDH_GenerateKeyPair(szdhPrivate, szdhPublic, nMaxChars, szdhP, szdhQ, szdhG, szUserEntropy, nOptions);
nRet = KXDH_ValidatePublicKey(szdhPublic, szdhP, szdhQ, szdhG);
nRet = KXDH_DeriveSharedSecret(szdhZ, nMaxChars, szdhPrivate, szdhPublic, szdhP, szdhQ, szdhG);
nRet = KXDH_DeriveKeyingData(szdhKEK, nMaxChars, szdhZZ, nKeyBits, szOtherInfo);
/* RANDOM NUMBER FUNCTIONS */
nRet = KX_GenerateRandomData(szOutput, nMaxChars, nBitsReq);
nRet = KX_GenerateRandomDataEx(szOutput, nMaxChars, nBitsReq, szUserEntropy, nOptions);
nRet = KX_PRNG_Initialize(szSeedFile, nOptions);
nRet = KX_PRNG_MakeSeedFile(szSeedFile, szPrompt, nOptions);
nRet = KX_PRNG_UpdateSeedFile(szSeedFile, nOptions);
nRet = KX_PRNG_Test(szFileName, nOptions);
/* HEX FUNCTIONS */
nRet = KX_HexEncode(szOutput, nMaxChars, lpData, nDataLen);
nRet = KX_HexDecode(lpOutput, nOutLen, szHex);
nRet = KX_HexFilter(szOutput, szInput, nChars);
/* GENERAL FUNCTIONS */
nRet = KX_WipeString(szToBeWiped);
nRet = KX_BitLength(szData);
nRet = KX_ErrorLookup(szOutput, nMaxChars, nErrCode);
nRet = KX_Version();
nRet = KX_CompileTime(szOutput, nMaxChars);
nRet = KX_ModuleName(szOutput, nMaxChars, nReserved);
nRet = KX_LicenceType(nReserved);
nRet = KX_PowerUpTests(nReserved);

Notes on core C functions

For more details of the core C functions, their use, options and return values, see the notes in diCrKX.h.

Error Codes

The error codes returned by the core DLL functions are as follows:
0   = OK, success, no error
101 = Parameter is wrong or missing
102 = Data is in wrong format
103 = Input is too short
104 = Invalid hex string
105 = Not enough room in output buffer
106 = Invalid size specified
110 = Unable to generate a prime
111 = Unable to find a value for g
112 = Parameter p is invalid
113 = Parameter q is invalid
114 = Parameter g is invalid
115 = Parameter pgenCounter is invalid
116 = Validation failed
120 = Public key y is invalid
199 = Algorithm failed
201 = Cannot open input file
202 = Cannot create output file
203 = File read error
204 = File write error
205 = File locking error
210 = PRNG: Uninstantiation failed
211 = PRNG: Requested length is too large
212 = PRNG: Function failed
213 = PRNG: Invalid input parameter
214 = PRNG: Function is not available
299 = PRNG: Catastrophic failure

[Contents]

Installing

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 C:\Program Files\CryptoSysKX. Instructions for distributing to end users with your applications are provided separately with the Licensed version in the file distrib.txt.

To uninstall, use Start > Settings > Control Panel > Add/Remove Programs > CryptoSys KeyExchange to remove the application.

[Contents]

Executable DLLs Provided

Core module

The core executable file is diCrKX.dll which is a simple Win32 DLL. This file must exist on the user's system for all programming language interfaces. The file diCrKX.dll is not registered with RegSvr32 (it can't be). To install, just copy to a directory on the user's system in the library search path. This can be the same directory as the executable that is calling it, the current directory, or a directory in the PATH environment setting, but the easiest place is the %windows%\system32 directory which will allow it to be found by anything. For more information on the library search path, refer to the Windows documentation under "SearchPath" or "LoadLibrary".

Wrapper DLLs

There are two wrapper executables provided to allow programming access to the core executable. Both require the core Win32 DLL to exist in the library search path.
  1. diCrSysKXCom.dll is an ActiveX DLL which exposes various classes to allow programming access from ASP, VBscript and other COM applications including VB6. This DLL does require registering with regsvr32.exe. The setup procedure registers this by default.
  2. diCrSysKXNet.dll is a .NET Class Library which exposes various classes to allow programming access from C# and VB.NET projects. This file is referenced from your .NET project. It is not registered.
The source code for these two wrapper libraries is provided. You are welcome to edit and recompile these to suit your own preferences, but please do not ask for support on recompiled libraries.

DLLs required

LanguagediCrKX.dlldiCrSysKXCom.dlldiCrSysKXNet.dll
C/C++YesNoNo
VB6/VBAYesOptionalNo
ASP/VBScriptYesYesNo
C#YesNoYes
VB.NETYesNoYes
Registered?NoYesNo

[Contents]

Using with C/C++

To use with C or C++, include diCrKX.h in your source code and link to diCrKX.lib.
#include "diCrKX.h"
The C interface accesses the functions in the core DLL directly and only requires that the Win32 diCrKX.dll file exists in the system's library search path. See the examples in diCrKX_Example.c and the core function list below. The diCrKX.lib library provided was compiled using MSVC++5 and has been tested with versions 5, 6, 7, and 8 (2005 Express) of Visual C++.

Borland C++

The lib file distributed with the program is made using MSVC and will not work with Borland C++. With Borland you need to generate a new .lib file directly from the DLL using the IMPLIB utility:
implib diCrKX.lib diCrKX.dll
(note the order of the parameters for this command - get it wrong and it destroys your DLL!). The example code has been successfully tested with Borland C++ 5.5.1 for Win32.

Example minimal code in C

/* kx_min.c */

#include <stdio.h>
#include "diCrKX.h"

// Compiler-specific explicit link to library
// This works in MSVC and Borland for LIB in same dir
#pragma comment(lib, ".\\diCrKX.lib")

int main(void)
{
	long ver;

	ver = KX_Version();
	printf("Version=%03ld\n", ver);

	return 0;
}	

[Contents]

Using with .NET: C# and VB.NET

  1. Copy the dotnet diCrSysKXNet.dll library file into a convenient folder.
  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 diCrSysKXNet.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 CryptoSysKX;
    
    or (for VB.NET)
    Imports CryptoSysKX
    
    to your code.
  4. Use the methods as listed below.
Alternatively, with C#, you can just include the source code module CryptoSysKX.cs in your project and there is no need to reference the class library.

Example minimal code in C#

using System;
using CryptoSysKX;

namespace KX_min
{
   class KX_version
   {
     [STAThread]
     static void Main(string[] args)
     {
       int n;
       string s;
       n = General.Version();
       Console.WriteLine("Version = {0}", n);
       s = General.CompileTime();
       Console.WriteLine("CompileTime={0}", s);
       s = General.ModuleName();
       Console.WriteLine("ModuleName={0}", s);
     }
   }
}

Example minimal code in VB.NET

Imports System
Imports CryptoSysKX

Module KX_min
  Sub Main(ByVal args() As String)
    Dim n As Integer
    Dim s As String
    n = General.Version()
    Console.WriteLine("Version = {0}", n)
    s = General.CompileTime()
    Console.WriteLine("CompileTime={0}", s)
    s = General.ModuleName()
    Console.WriteLine("ModuleName={0}", s)
  End Sub
End Module

[Contents]

Using with VB6 and VBA

You can either use the ActiveX interface or call the core functions directly. The ActiveX interface is easier.

Using the ActiveX Interface

  1. Make sure the core Win32 DLL is installed.
  2. Make sure the ActiveX DLL file is registered on the system.
  3. Set a reference to the diCrSysKXCom.dll ActiveX DLL (Project>References>Browse).
  4. Use the methods as listed below.

Calling the core functions directly from VB

  1. Make sure the core Win32 DLL is installed.
  2. Either (a) include the source code module basCrKX.bas in your project or (b) make a reference to diCrKX.tbl in your project (but not both). These files are in the zipped COM source.
  3. Call the core functions. Functions that output text data require the string to be pre-dimensioned using the String(nChars, " ") function. See the source code of diCrSysKXCom for examples and the list of core functions below.

Example minimal code in VB6

This assumes a reference is set to diCrSysKXCom.dll:
Public Sub Test_kxGen_version()
' Display the version number, etc
    Dim oGen As New kxGen
    
    Debug.Print "Version = " & oGen.Version
    Debug.Print "CompileTime = " & oGen.CompileTime
    Debug.Print "ModuleName = " & oGen.ModuleName
    Debug.Print "LicenceType = " & oGen.LicenceType
End Sub

[Contents]

Using with ASP and VBScript

  1. Make sure the core Win32 DLL is installed.
  2. Make sure the ActiveX DLL file is registered on the system.
  3. Create a server object for whichever class you need

Example minimal code in ASP

Dim oGen        
Set oGen = Server.CreateObject("diCrSysKXCom.kxGen")

Response.Write "Version=" & oGen.Version & Chr(13) & Chr(10)
Response.Write "Module Name=" & oGen.ModuleName & Chr(13) & Chr(10)
Response.Write "Compiled=" & oGen.CompileTime & Chr(13) & Chr(10)

[Contents]

Random Number Generator

The random number generator (RNG) used in CryptoSys KeyExchange is a deterministic random bit generator (DRBG) that uses the techniques and recommendations from NIST Special Publication 800-90 Recommendation for Random Number Generation Using Deterministic Random Bit Generators [SP80090].

RNG environment and design restrictions

This library is designed to be used as a developer's toolkit of primitive cryptographic functions in different value-added applications. This requires internal workings that must be unobtrusive and not carry out actions that could interfere with other processes. This leaves us with relatively weak sources of entropy like the time that various functions are called and the state of the machine's memory, active windows, and hard disk.

Because of the different environments in which this library will be used, we are unable to set up, say, a permanent keystroke timer logger on the end-user's machine or have a GenerateData function that just sits there until it's gathered enough entropy to meet a particular security requirement.

RNG Mechanisms

The underlying RNG (or PRNG) functions use the algorithms recommended in NIST SP 800-90 [SP80090] (the "DRBG Standard") to provide a Deterministic Random Bit Generator (DRBG). 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, Prediction Resistance.

Entropy is accumulated at startup and whenever any function in the library is called. We use the "Fortuna" method of pooling to prevent certain attacks from someone who controls some but not all of the entropy sources (see chapter 10 of Ferguson and Schneier, Practical Cryptography, [FERG03]). The more times your application calls the functions in the library before needing some random data, the more entropy will be accumulated.

However, if the main algorithm requires new entropy data before sufficient entropy is available then, rather than failing and returning a "catastrophic" error as specified by the DRBG Standard, our algorithm uses whatever data is available in the relevant pools. This is similar to the behaviour of the dev/urandom facility in Linux.

This means that the security strength of the output might be anything from zero to the design limit of 128 bits. In the absence of further action by the programmer as described below, you would have to assume the worst case of zero bits.

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 amount of entropy to initialise the PRNG. This seed file is updated automatically when used. You can optionally call the Rng.UpdateSeedFile function 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 "prompt" option when generating random data to force the user to generate entropy using random keystrokes and mouse movements. The PRNG_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 PRNG process. 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 PRNG will have at least 128 bits of security strength.

User-supplied entropy is added as "additional input" to the generation process (see the DRBG Standard). 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 useless. 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.

RNG Specification

The RNG functions use the HMAC_DRBG mechanism from the DRBG Standard with SHA-1 as the underlying hash function.
Highest supported security strength = 128
Output block length (outlen) = 160 bits
Maximum number of bits per request = 2^19 bits (i.e. 65536 bytes)
Reseed interval = 1000000 or as dictated by the Fortuna algorithm
Maximum length of personalization string = 160 bits
Maximum length of entropy input = 5120 bits (32 pools x 160 bits)

In Windows®, each thread has its own Generator in Thread Local Storage. Each process has one Accumulator accessed by all Generators and protected by a Critical Section when accessed. The accumulator has 32 "Fortuna" accumulation pools with the minimum pool size before a reseed set to 32 bytes.

The "personalization string" used on instantiation in each thread is derived by hashing the current time, process ID, thread ID, user name and computer name, and so is almost certain to be unique. The "instantiation nonce" is a 32-bit value derived from the current time and process ID which is incremented on the instantiation of any new Generator in a different thread.

In the absolute worst case, if no seed file is used and an attacker can call the GenerateRandomData function before any additional entropy has been generated by the system, the output effectively reduces to a "strong" hash of the current time and personalization string with good backtracking protection. Note that the output will always pass a FIPS-140-2 statistical test because the DRBG_HMAC algorithm used will always ensure that the output of successive requests look random, even given knowledge and control over the inputs.

RNG Seed Files

A seed file of exactly 64 bytes is expected. It must be writable by the user. File locking is used to prevent interference from simultaneous use by others.

Use MakeSeedFile to create a fresh seed file on a new installation. This uses a dialog window and expects the user to type in random keystrokes. Such a GUI interface may not be appropriate in all circumstances.

Alternatively, use the command-line program MkRngSeed.exe included in the distribution to create a seed file.

How can I measure the security level in the RNG output?

You can't. It's a function of how hard it is for an attacker to reproduce the same data, and that depends on the situation when you created it.

[Contents]

Security Considerations

[Contents]

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.

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 SHA-1 and MD5 message digest and CRC-32 values before and after installation.

Random number generator health check

A random number generator Health Check is carried out on power up and every time a new RNG generator is instantiated in a new thread. It can also be carried out on demand with the Rng.Test and General.PowerUpTests methods (or the KX_PRNG_Test and KX_PowerUpTests functions). The health check performs self-tests to obtain assurance that the DRBG continues to operate as designed and implemented according to section 11.3 of [SP80090] including Known Answer Testing, Testing the Instantiate Function, Testing the Generate Function, Testing the Reseed Function and Testing the Uninstantiate Function.

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 stored 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 the process to prevent further use of cryptographic functions.

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

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 KX_PowerUpTests function or General.PowerUpTests method. In the event that such an "on demand" test fails, the module will return an error code but will not terminate the process.

Note 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]

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\CryptoSysKX\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\CryptoSysKX\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\CryptoSysKX\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 ( diCrKX ) 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\diCrKX]
Value Name: EventMessageFile
Data Type: REG_SZ
Data: Full path to DLL file, e.g. "C:\WINNT\system32\diCrKX.dll"
 
Value Name: TypesSupported
Data Type: REG_DWORD
Data: 7 (0x7)

[Contents]

References

Primary references

Other references

[Contents]

Acknowledgements

The source code used in CryptoSys KeyExchange is original code written by David Ireland except for the following:

[Contents]

Examples

These examples try and show how two parties, Alice and Bob, could use the library to exchange a secret key between themselves to use to encrypt and decrypt messages. In practice, Alice and Bob would be working on separate computers and sending the "global" values to each other via some communication channel.

We use a key length of 512 bits here just for speed in the demo. Remember that this is not a valid length for strict X9.42 requirements, so don't use the Domain.EnforceStdLength option to test this.

Note also that generating new domain parameters takes time and is usually a one-off event - i.e. one set of domain parameters is generated and distributed to all parties who then use the same domain parameters for each new set of keys. We just include the operation here to demonstrate its use.

Example code in C/C++

#include <stdio.h>
#include <assert.h>
#include "diCrKX.h"

/* Test size for prime modulus (NB 512 is non-standard for X9.42!) */
#define PBITS 512
#define PCHARS ((PBITS+7)/8)*2
#define PBYTES (PCHARS/2)
/* Required size for key material */
#define KBITS 128
#define KCHARS ((KBITS+7)/8)*2

/* GLOBALS - we use these for variables meant to be transmitted between the parties */
/* Domain parameters */
char g_P[PCHARS+1], g_Q[PCHARS+1], g_G[PCHARS+1];
/* Validation parameters */
char g_ValidationSeed[PCHARS+1];
long g_pgenCounter;
/* Public keys */
char g_Ya[PCHARS+1], g_Yb[PCHARS+1];

void full_exchange(void)
{
/* This example demonstrates an exchange of data between two parties, Alice
   and Bob. Here we use global variables to "pass" data between the two parties
   and we pretend that the two sets of "local" variables are really private.
   In practice Alice's and Bob's actions would be carried out on separate machines.
*/

  /* ALICE'S LOCAL VARIABLES */
  long ra;
  char szdhXa[PCHARS+1];
  char szdhZa[PCHARS+1];
  char szdhKa[KCHARS+1];

  /* BOB'S LOCAL VARIABLES */
  long rb;
  char szdhXb[PCHARS+1];
  char szdhZb[PCHARS+1];
  char szdhKb[KCHARS+1];

  /* ALICE generates a set of Domain Parameters (this could be done by a 3rd party) */
  printf("Generating Domain Parameters...\n");
  ra = KXDH_GenerateDomainParamsEx(g_P, g_Q, g_G, g_ValidationSeed, PCHARS, PBITS, 160, KX_SHOW_PROGRESS);
  assert(ra > 0);
  g_pgenCounter = ra;
  printf("Domain Parameters:\n");
  printf("p=%s\nq=%s\ng=%s\n", g_P, g_Q, g_G);
  printf("Validation Parameters:\n");
  printf("seed=%s\npgenCounter=%ld\n", g_ValidationSeed, g_pgenCounter);

  /* ALICE carries out a quick check */
  ra = KXDH_ValidateDomainParams(g_P, g_Q, g_G, PBITS, "", 0, KX_DEFAULT);
  if (ra != 0) fatal("Failed to validate domain parameters.");
  printf("Alice has checked the domain parameters, OK.\n");

  /* ALICE transmits (p,q,g, seed, pgenCounter) to Bob... */

  /* BOB validates (properly) the domain parameters he receives */
  printf("Validating Domain Parameters...\n");
  rb = KXDH_ValidateDomainParams(g_P, g_Q, g_G, PBITS, g_ValidationSeed, g_pgenCounter, KX_SHOW_PROGRESS);
  if (rb != 0) fatal("Failed to validate domain parameters.");
  printf("Bob has validated the domain parameters, OK.\n");

  /* ALICE generates a random private/public key pair */
  ra = KXDH_GenerateKeyPair(szdhXa, g_Ya, PCHARS, g_P, g_Q, g_G, "", KX_DEFAULT);
  assert(ra == 0);
  printf("Alice's private/public key pair:\n");
  printf("Xa=%s\nYa=%s\n", szdhXa, g_Ya);

  /* ALICE transmits her public key Ya to Bob but keeps her private key Xa secret... */

  /* BOB validates Alice's public key, Ya */
  rb = KXDH_ValidatePublicKey(g_Ya, g_P, g_Q, g_G);
  if (rb != 0) fatal("Alice's public key is not valid.");
  printf("Bob has validated Alice's public key, OK.\n");

  /* BOB generates a random private/public key pair */
  rb = KXDH_GenerateKeyPair(szdhXb, g_Yb, PCHARS, g_P, g_Q, g_G, "", KX_DEFAULT);
  assert(rb == 0);
  printf("Bob's private/public key pair:\n");
  printf("Xb=%s\nYb=%s\n", szdhXb, g_Yb);

  /* BOB transmits his public key Yb to Alice but keeps his private key Xb secret... */

  /* ALICE validates Bob's public key, Yb */
  ra = KXDH_ValidatePublicKey(g_Yb, g_P, g_Q, g_G);
  if (ra != 0) fatal("Bob's public key is not valid.");
  printf("Alice has validated Bob's public key, OK.\n");

  /* Now that Alice and Bob have exchanged their public keys, they can both 
     generate their own copies of the shared secret and keying material */

  /* ALICE computes the shared secret */
  ra = KXDH_DeriveSharedSecret(szdhZa, PCHARS, szdhXa, g_Yb, g_P, g_Q, g_G);
  assert(ra == 0);
  printf("Shared secret as computed by Alice, Za=\n%s\n", szdhZa);

  /* ALICE computes a 128-bit AES key shared with Bob */
  ra = KXDH_DeriveKeyingData(szdhKa, KCHARS, szdhZa, KBITS, NULL);
  assert(ra > 0);
  printf("128-bit key as computed by Alice, Ka=%s\n", szdhKa);

  /* BOB computes the shared secret */
  rb = KXDH_DeriveSharedSecret(szdhZb, PCHARS, szdhXb, g_Ya, g_P, g_Q, g_G);
  assert(rb == 0);
  printf("Shared secret as computed by Bob,   Zb=\n%s\n", szdhZb);

  /* BOB computes a 128-bit AES key shared with Alice */
  rb = KXDH_DeriveKeyingData(szdhKb, KCHARS, szdhZb, KBITS, NULL);
  assert(rb > 0);
  printf("128-bit key as computed by Bob,   Kb=%s\n", szdhKb);

  /*An omniscient entity checks they have the same values */
  assert(strcmp(szdhZa, szdhZb) == 0);
  assert(strcmp(szdhKa, szdhKb) == 0);

  /* ALICE clears sensitive data */
  KX_WipeString(szdhKa);
  KX_WipeString(szdhZa);
  KX_WipeString(szdhXa);
  
  /* BOB clears sensitive data */
  KX_WipeString(szdhKb);
  KX_WipeString(szdhZb);
  KX_WipeString(szdhXb);
}

Example code in C#

//**************************
// EXAMPLE OF FULL EXCHANGE
//**************************
// Use global variables to "transmit" data between the parties
// Domain parameters
static string g_dhP, g_dhQ, g_dhG;
// Validation parameters
static string g_dhSeed;
static int g_dhCounter;
// Public keys
static string g_dhYa, g_dhYb;

static void Full_Exchange()
{
	int nbits = 512;	// We use non-standard 512 for speed in this demo
	// ALICE's PRIVATE VARIABLES
	bool oka;
	string dhZa, dhKa;
	DiffieHellman oDHa = new DiffieHellman();
	// BOB'S PRIVATE VARIABLES
	bool okb;
	string dhZb, dhKb;
	DiffieHellman oDHb = new DiffieHellman();

	Console.WriteLine("FULL KEY EXCHANGE:");
	// ALICE generates a set of domain parameters (this could be done by a 3rd party)
	Console.WriteLine("Generating domain parameters...");
	oka = oDHa.GenerateDomainParams(nbits, Domain.Default);
	Debug.Assert(oka, "GenerateDomainParams failed");
	Console.WriteLine("p={0}", oDHa.P);
	Console.WriteLine("q={0}", oDHa.Q);
	Console.WriteLine("g={0}", oDHa.G);
	Console.WriteLine("seed={0}", oDHa.Seed);
	Console.WriteLine("pgenCounter={0}", oDHa.pgenCounter);

	// ALICE does a quick check
	oka = oDHa.IsValidDomainParamsQuick(Domain.Default);
	Console.WriteLine("IsValidDomainParamsQuick returns {0}", (oka ? "Valid" : "INVALID"));
	Debug.Assert(oka, "IsValidDomainParamsQuick failed");
	
	// ALICE sends these parameters to BOB
	g_dhP = oDHa.P;
	g_dhQ = oDHa.Q;
	g_dhG = oDHa.G;
	g_dhSeed = oDHa.Seed;
	g_dhCounter = oDHa.pgenCounter;

	// (transmit...)

	// BOB receives them and validates them
	oDHb.SetDomainParams(g_dhP, g_dhQ, g_dhG, g_dhSeed, g_dhCounter);
	okb = oDHb.IsValidDomainParams(Domain.ShowProgress);
	Debug.Assert(okb, "IsValidDomainParams failed");
	Console.WriteLine("OK, Bob has validated the domain parameters.");
	
	// ALICE generates a random private/public key pair
	oka = oDHa.GenerateKeyPair();
	Console.WriteLine("GenerateKeyPair returns {0}", (oka ? "Success" : "FAILURE"));
	Debug.Assert(oka, "GenerateKeyPair failed");
	Console.WriteLine("Alice's private public key pair:\nXa={0}\nYa={1}", oDHa.X, oDHa.Y);

	// ALICE sends her public key Ya to BOB but keeps her private key Xa secret
	g_dhYa = oDHa.Y;

	// (transmit...)

	// BOB receives ALICE's public key and validates it
	okb = oDHb.IsValidPublicKey(g_dhYa);
	Console.WriteLine("IsValidPublicKey returns {0}", (okb ? "Valid" : "INVALID"));
	Debug.Assert(okb, "IsValidPublicKey failed");
	Console.WriteLine("OK, Bob has validated Alice's public key");

	// BOB generates a random private/public key pair
	okb = oDHb.GenerateKeyPair();
	Console.WriteLine("GenerateKeyPair returns {0}", (okb ? "Success" : "FAILURE"));
	Debug.Assert(okb, "GenerateKeyPair failed");
	Console.WriteLine("Bob's private public key pair:\nXb={0}\nYb={1}", oDHb.X, oDHb.Y);

	// BOB sends his public key Yb to ALICE but keeps his private key Xb secret
	g_dhYb = oDHb.Y;

	// (transmit...)

	// ALICE receives BOB's public key and validates it
	oka = oDHa.IsValidPublicKey(g_dhYb);
	Console.WriteLine("IsValidPublicKey returns {0}", (oka ? "Valid" : "INVALID"));
	Debug.Assert(oka, "IsValidPublicKey failed");
	Console.WriteLine("OK, Alice has validated Bob's public key");

	// Now that ALICE and BOB have exchanged their public keys, they can both
	// generate their own private copies of the shared secret and keying material.
	// In this case, they do not use OtherInfo.

	// ALICE computes the shared secret
	dhZa = oDHa.DeriveSharedSecret(oDHa.X, g_dhYb);
	Debug.Assert(dhZa.Length > 0, "Failed to derive shared secret");
	Console.WriteLine("Shared secret as computed by Alice,\nZa={0}", dhZa);

	// ALICE computes a 128-bit key to be used to encrypt data with BOB
	// using the `dhStatic' key agreement scheme (ANSI X9.42-2003 8.1.1)
	dhKa = oDHa.DeriveKeyingData(dhZa, 128, null);
	Debug.Assert(dhKa.Length > 0, "Failed to derive keying data");
	Console.WriteLine("128-bit key as computed by Alice,\nKa={0}", dhKa);

	// BOB computes the shared secret
	dhZb = oDHb.DeriveSharedSecret(oDHb.X, g_dhYa);
	Debug.Assert(dhZb.Length > 0, "Failed to derive shared secret");
	Console.WriteLine("Shared secret as computed by Bob,\nZb={0}", dhZb);

	// BOB computes a 128-bit key to be used to encrypt data with ALICE
	dhKb = oDHb.DeriveKeyingData(dhZb, 128, null);
	Debug.Assert(dhKb.Length > 0, "Failed to derive keying data");
	Console.WriteLine("128-bit key as computed by Bob,\nKb={0}", dhKb);

	// An `omniscient entity' checks they have the same values
	Debug.Assert(String.Compare(dhZa, dhZb, true)==0, "Shared secret does not match");
	Debug.Assert(String.Compare(dhKa, dhKb, true)==0, "Keying data does not match");
	Console.WriteLine("OK, shared secrets and keying material matches for Alice and Bob.");

	// ALICE and BOB clear their private keys and other DiffieHellman data
	oDHa.Clear();
	oDHb.Clear();

}

Example code in VB6

Public Sub Test_Full_Exchange()
' Model the full exchange of a secret key between two parties, Alice and Bob,
' including the generation of a new set of domain parameters

    ' We use "global" variables for data to be "transmitted" between the parties
    ' Domain parameters
    Dim g_dhP As String
    Dim g_dhQ As String
    Dim g_dhG As String
    ' Validation parameters
    Dim g_Seed As String
    Dim g_Counter As Long
    ' Public keys
    Dim g_dhYa As String
    Dim g_dhYb As String
    
    ' ALICE's local "private" variables
    Dim oDHa As New kxDh
    Dim IsOkA As Boolean
    Dim dhzA As String
    Dim dhkA As String
    
    ' BOB's local "private" variables
    Dim oDHb As New kxDh
    Dim IsOkB As Boolean
    Dim dhzB As String
    Dim dhkB As String
    
    ' We use a 512-bit key for speed in this demo
    ' (NB this is not a X9.42 standard length)
    Const nKeyBits As Long = 512
    
    ' ALICE generates a new set of Domain Parameters (this could be done by a 3rd party)
    Debug.Print "Generating new domain parameters..."
    Debug.Print oDHa.GenerateDomainParams(nKeyBits)
    Debug.Print "L=" & oDHa.KeyBits
    Debug.Print "p=" & oDHa.P
    Debug.Print "q=" & oDHa.Q
    Debug.Print "g=" & oDHa.G
    Debug.Print "seed=" & oDHa.Seed
    Debug.Print "pgenCounter=" & oDHa.PgenCounter
    
    ' ALICE does a quick check
    Debug.Print "Quick validation..."
    IsOkA = oDHa.IsValidDomainParamsQuick()
    Debug.Print "IsValidDomainParamsQuick returns " & IsOkA
    Debug.Assert IsOkA
    
    ' ALICE sends these to BOB
    g_dhP = oDHa.P
    g_dhQ = oDHa.Q
    g_dhG = oDHa.G
    g_Seed = oDHa.Seed
    g_Counter = oDHa.PgenCounter
    
    ' (transmit...)
    
    ' BOB receives these and validates them
    Call oDHb.SetDomainParams(g_dhP, g_dhQ, g_dhG, g_Seed, g_Counter)
    Debug.Print "Validating domain parameters..."
    IsOkB = oDHb.IsValidDomainParams()
    Debug.Print "IsValidDomainParamsQuick returns " & IsOkB
    Debug.Assert IsOkB
    
    ' ALICE generates a fresh private/public key pair (xA, yA)
    Debug.Print "Alice generates a new key pair..."
    IsOkA = oDHa.GenerateKeyPair()
    Debug.Print "GenerateKeyPair returns " & IsOkA
    Debug.Assert IsOkA
    Debug.Print "xA=" & oDHa.X
    Debug.Print "yA=" & oDHa.Y
    
    ' ALICE sends her public key to BOB
    g_dhYa = oDHa.Y
    
    ' (transmit...)
    
    ' BOB receives ALICE's public key and validates it
    Debug.Print "Bob validates Alice's public key..."
    IsOkB = oDHb.IsValidPublicKey(g_dhYa)
    Debug.Print "Alices's public key is " & IIf(IsOkB, "Valid", "INVALID!")
    Debug.Assert IsOkB
    
    ' BOB generates a fresh private/public key pair (xB, yB)
    Debug.Print "Bob generates a new key pair..."
    IsOkB = oDHb.GenerateKeyPair()
    Debug.Print "GenerateKeyPair returns " & IsOkB
    Debug.Assert IsOkB
    Debug.Print "xB=" & oDHb.X
    Debug.Print "yB=" & oDHb.Y
    
    ' BOB sends his public key to ALICE
    g_dhYb = oDHb.Y
    
    ' (transmit...)
    
    ' ALICE receives BOB's public key and validates it
    Debug.Print "Alice validates Bob's public key..."
    IsOkA = oDHa.IsValidPublicKey(g_dhYb)
    Debug.Print "Bob's public key is " & IIf(IsOkA, "Valid", "INVALID!")
    Debug.Assert IsOkA
    
    ' Now that ALICE and BOB have exchanged their public keys,
    ' they can both generate their own private copies of the shared secret
    ' and keying data. In this case, they do not use `OtherInfo'
    
    ' ALICE derives the shared secret Z given BOB's public key (yB) and her secret key (xA)
    dhzA = oDHa.DeriveSharedSecret(oDHa.X, g_dhYb)
    Debug.Print "Alices derives shared secret = " & dhzA
    Debug.Assert Len(dhzA) > 0
    
    ' BOB derives the shared secret Z given ALICE's public key (yA) and his secret key (xB)
    dhzB = oDHb.DeriveSharedSecret(oDHb.X, g_dhYa)
    Debug.Print "Bob derives shared secret    = " & dhzB
    Debug.Assert Len(dhzB) > 0
    
    ' ALICE derives 128 bits of keying data from the shared secret
    ' using the dhStatic Key Agreement method (ZZ = Z)
    dhkA = oDHa.DeriveKeyingData(dhzA, 128, "")
    Debug.Print "Alice's KeyingData = " & dhkA
    Debug.Assert Len(dhkA) > 0
    
    ' BOB derives 128 bits of keying data from the shared secret
    ' using the dhStatic Key Agreement method (ZZ = Z)
    dhkB = oDHb.DeriveKeyingData(dhzB, 128, "")
    Debug.Print "Bob's KeyingData   = " & dhkB
    Debug.Assert Len(dhkB) > 0
    
    ' An "omniscient entity" confirms they have the same values
    Debug.Assert (dhzA = dhzB)
    Debug.Assert (dhkA = dhkB)
    Debug.Print "OK, shared secrets and keying material match for Alice and Bob"
    
    ' ALICE and BOB clear their private keys and other D-H data
    oDHa.Clear
    oDHb.Clear
    
End Sub

[Contents]

Document Revision History

Version 1.0.0. Released 19 February 2007. Beta Test Version 0.8.0. First published 25 December 2006 by DI Management Services Pty Limited.

[Contents]


Copyright © 2006-7 D.I. Management Services Pty Limited ABN 78 083 210 584, Sydney, Australia. All rights reserved.
<www.cryptosys.net>   <www.di-mgt.com.au>