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.

Output

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).

Method

Encryption

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

Decryption

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".

Analysis

Let's follow the three steps in encryption and decryption for the ANSI 'Bytes' example:
ENCRYPTION:
         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)
         +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

DECRYPTION:
         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)
         +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
         Decrypt...
         +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
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
IV=FEDCBA9876543210
CT=80C73328BC589F059D5BB05C6CEF7A4B
Or just prepend the IV and send as one hexadecimal string
FEDCBA987654321080C73328BC589F059D5BB05C6CEF7A4B
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
/ty6mHZUMhCAxzMovFifBZ1bsFxs73pL	
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:
48656C6C6F20776F726C6421
480065006C006C006F00200077006F0072006C0064002100
and the padded input blocks and resulting ciphertext are also completely different even though the key and IV are the same:
IB=48656C6C6F20776F726C642104040404
CT=80C73328BC589F059D5BB05C6CEF7A4B
IB=480065006C006C006F00200077006F0072006C00640021000808080808080808
CT=1DD5541870B064E2B419936FD0660FCB947F629F93A78D84B197BA23F54CF4A0

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

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

Conclusion

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>