Encryption Scheme using Triple DES in CBC mode

Example Code in C/C++

The C code in EncryptBytesAndHex.c (zipped, 5kB) shows how you could encrypt an arbitrary-length string using the functions in CryptoSys API. This example uses Triple DES in CBC mode. The three examples show how to encrypt an ANSI string in Byte mode, in Hex mode, and how you could encrypt a string that consists of Unicode `wide' characters. See also C/C++ Bytes Conversion Issues below.


The output from this example code should look like this. Note the difference in the input and output blocks for the ANSI `narrow' characters compared to the Unicode `wide' characters.

Example Code in VB6/VBA

The code in basTDEABytesAndHex.bas does the same. A full VB6 project is in EncryptBytesAndHex.VB6.zip (21 kB).

You can make your own mind up which way is easier for you (we prefer the Hex version in VB).

Security Issues

The key and Initialization Vector (IV) are hard-coded in this example so as not to obscure the technique we are trying to show. In practice you would generate the key randomly in a separate operation and pass the key data between the parties using a separate secure channel. You must never hard-code a key in production code. The IV should be generated randomly every time you encrypt something and can be passed to the receiver in the clear along with the ciphertext (see Sending the ciphertext to the recipient).



INPUT: plaintext string, key, IV.
  1. convert string to bytes
  2. pad
  3. encrypt bytes
OUTPUT: ciphertext bytes.


INPUT: ciphertext bytes, key, IV.
  1. decrypt bytes
  2. unpad (or return error if invalid)
  3. convert bytes to string
OUTPUT: plaintext string or "decryption error".


Let's follow the three steps in encryption and decryption for the ANSI 'Bytes' example:
         Given string of text to be encrypted...
szPlain: |'H'|'e'|'l'|'l'|'o'|' '|'w'|'o'|'r'|'l'|'d'|'!'|'\0'| (zero-terminated string)
         Convert string to bytes...
lpPlain: |48 |65 |6C |6C |6F |20 |77 |6F |72 |6C |64 |21 | (len=12)
         Pad to next multiple of block length...
lpBlock: |48 |65 |6C |6C |6F |20 |77 |6F |72 |6C |64 |21 |04 |04 |04 |04 | (len=16)
         Encrypt the block...
lpCipher:|80 |C7 |33 |28 |BC |58 |9F |05 |9D |5B |B0 |5C |6C |EF |7A |4B | (len=16)

         Given ciphertext bytes to be decrypted...
lpCipher:|80 |C7 |33 |28 |BC |58 |9F |05 |9D |5B |B0 |5C |6C |EF |7A |4B | (len=16)
lpBlock: |48 |65 |6C |6C |6F |20 |77 |6F |72 |6C |64 |21 |04 |04 |04 |04 | (len=16)
         Strip the padding...
lpCheck: |48 |65 |6C |6C |6F |20 |77 |6F |72 |6C |64 |21 | (len=12)
         Convert bytes to string...
szCheck: |'H'|'e'|'l'|'l'|'o'|' '|'w'|'o'|'r'|'l'|'d'|'!'|'\0'| (zero-terminated string)

Note that the output from the encryption operation (lpCipher, nCipher) must not be converted back to or treated as a 'text' string because it could contain a zero byte. This problem is particularly pernicious because only certain outputs will fail, leading to much scratching of heads and unfounded accusations of bugs to the purveyors of cryptographic APIs.

Sending the ciphertext to the recipient

To pass the output of this example to the recipient, you could send a simple text message with the ciphertext encoded in hexadecimal like this
Or just prepend the IV and send as one hexadecimal string
provided the recipient knows that the first 8 bytes are the IV. Or send the same data in base64, which is shorter but case-sensitive
Or save as a binary file and send that.

C/C++ Byte Conversion Issues

An ANSI string is stored in C and C++ as a sequence of non-zero char types terminated by a zero character. The length of the string can be determined just by examining it. Before encryption we need to (1) convert this to an array of bytes and (2) add padding so that the length of the input to the encryption function is an exact multiple of the block size, in this case 8. A byte array consists of a sequence of unsigned char types, often typedef'd to BYTE. With byte arrays in C we also need to keep a separate variable for the length (because a byte array could contain a zero value and so strlen does not work).

The quick-and-dirty way to convert an ANSI string to a byte array is easy in C using pointers and typecasts. Too easy, unfortunately, and this `trick' is disallowed in more modern languages.

char szPlain[] = "Hello world!";
unsigned char *lpPlain = NULL;
long nPlain;
lpPlain = (unsigned char*)szPlain;
nPlain = strlen(szPlain);
A stricter alternative method might allocate a completely separate set of data, like this
nPlain = strlen(szPlain);
lpPlain = malloc(nPlain);
memcpy(lpPlain, szPlain, nPlain);
just don't forget to free it.

Both char and unsigned char types are stored in 8-bit octets. These two types may be treated identically on your system or they may not be. Unfortunately you can usually get away with treating them as the same thing and this can result in very muddled (and buggy) code. We make it a very strict rule to distinguish carefully between 'text' strings and 'byte' arrays in our own code because we have been caught out too many times. Conceptually they are different beasts so treat them differently.

Unicode Strings

In our examples, both the `narrow' ANSI and `wide' Unicode input strings displayed exactly the same:
'Hello world!'
but when we look at them as byte arrays we have completely different data:
and the padded input blocks and resulting ciphertext are also completely different even though the key and IV are the same:

Note how we convert the decrypted plaintext bytes (lpBlock, nPlain) back to a Unicode string

nwchars = nPlain / sizeof(wchar_t);
lpszCheck = malloc((nwchars + 1) * sizeof(wchar_t));
memcpy(lpszCheck, lpBlock, nPlain);
lpszCheck[nwchars] = L'\0';

Big-endian vs little-endian

The Unicode example above was carried out on an Intel computer that stores its data in `little-endian' format. Had we carried this out on a 'big-endian' computer like a Motorola, the same text string in byte format would look like

and the ciphertext would be completely different:
For two users passing data between systems of the same 'endianness' this problem would not arise, but it would if the systems were different.


Be careful with wide-character text in C/C++ programs; it is much trickier to deal with than ANSI. Always treat `text' and `bytes' as separate entities even though there are shortcuts that obscure the difference. Explicitly carry out the three steps in your code.

This page last updated 18 August 2006

Valid HTML 4.01! Valid CSS

Home | Blowfish | Rijndael AES | DES | Triple DES | SHA-1 | SHA-256 | Random numbers | Compression | CryptoSys Manual | Purchase | Feedback | CryptoSys PKI | Search | Cryptography Software Code | Contact us
Copyright © 2006 D.I. Management Services Pty Limited ABN 78 083 210 584, Sydney, Australia. All rights reserved.
<www.di-mgt.com.au>   <www.cryptosys.net>