DI Management Home > Cryptography > Encrypting variable-length strings with a password

Encrypting variable-length strings with a password


Introduction

This page shows how to encrypt a variable-length text string with a key derived from a text password. It uses AES-128, but could use any of the major encryption algorithms like Triple DES or Blowfish. All the necessary cryptographic functions are provided in the CryptoSys (tm) API library.

Example Projects

There are two example projects.
  1. A VB.NET project that emphasises the steps involved in encryption and decryption using hex-encoding for clarity.
  2. A VB6 project that shows three possible ways to create a key from a password and how the final ciphertext and initialization vector could be transmitted to the recipient in base64 format.

In both cases, we wish to encrypt an ordinary text message using a password or pass phrase string and transmit the ciphertext to a recipient. We assume that the sender and recipient have already agreed on the secret password (which they've passed to each other using a separate, secure channel) and the other necessary parameters. These examples use AES-128 in Cipher Block Chaining (CBC) mode with PKCS#5 padding.

You need to transmit the ciphertext to the recipient as well as the Initialization Vector, Salt and Iteration Count used. In the most general case, the sender needs to transmit the following information:

Ciphertext=.....
IV=....
Salt=...
IterationCount=nnn
In practice, you can be more succinct.

We suggest that sender and recipient agree on these conventions beforehand:

  1. The iteration count is always the same value (e.g. 2048). This saves having to transmit it each time.
  2. The IV and salt will be the same value. This will be exactly 16 bytes (128 bits = the block size) for AES. There is no conflict in using the same value for both these purposes and it reduces the information you need to transmit.
  3. The message will be transmitted in the form Base64Encode(IV || ciphertext). Using base64 encoding makes transmission easy. The recipient just needs to decode and then separate out the first 16 bytes for the IV/salt. Alternatively use hexadecimal encoding.
Obviously you can agree on and use many other variations of this. Just be consistent.

Security considerations

Algorithms

Encryption Algorithm


Algorithm: Encryption with password using block cipher in CBC mode
INPUT:
OUTPUT:
  1. Generate a random value of the same size as the block length of the encryption algorithm
    randVal = GenerateRandomValue(blockSize)
  2. Set
    iv = randVal
    salt = randVal
    
  3. Generate a key of the required size for the encryption algorithm
    key=KDF2(password, salt, iterationCount, keySize)
  4. Convert the input text to `binary' format
    work = BitStringFromText(inputText)
    .
  5. Pad this to the next-highest multiple of the encryption block size
    work = Pad(work, blockSize)
    .
  6. Encrypt the working block in CBC mode using the key and IV
    ciphertext = EncryptCBC(work, key, iv)
  7. Output iv and cipherText.

Decryption Algorithm


Algorithm: Decryption with password using block cipher in CBC mode
INPUT:
OUTPUT:
  1. If
    Length(iv) != blockSize OR Length(cipherText) Mod blockSize != 0
    
    then output "decryption error" and stop.
  2. Set
    salt = iv
  3. Generate a key of the required size for the encryption algorithm
    key=KDF2(password, salt, iterationCount, keySize)
  4. Decrypt the ciphertext using the key and IV
    work = DecryptCBC(cipherText, key, iv)
  5. Remove the padding, if any
    work = Unpad(work, blockSize)
    .
  6. If no valid padding is found, output "decryption error" and stop.
  7. Convert the bit-string ciphertext to `text' format
    outText = TextFromBitString(work)
  8. Output Plaintext outText in text format.

Padding

Before encryption you need to convert the plaintext to bit-string format and then pad so that the input to the encryption process is an exact multiple of the block length. For AES the block length is 16 bytes (128 bits). Our recommended padding procedure is to pad with bytes all of the same value as the number of padding bytes. This is the method recommended in [PKCS5], [PKCS7], and [CMS].
Pad the input with a padding string of between 1 and 16 bytes to make the total length an exact multiple of 16 bytes. The value of each byte of the padding string is set to the number of bytes added - i.e. 16 bytes of value 16 (0x10), 15 bytes of value 15 (0x0F), ..., 2 bytes of 0x02, or one byte of value 0x01.

Note that a padding string is always added, even if the original input is already an exact multiple of 16 bytes. This makes the un-padding process completely unambiguous.

If, after decrypting, you cannot find a valid padding string at the end of the decrypted data, then you have a "decryption error".

VB.NET Project

The VB.NET project shows the distinct phases of encryption and decryption. The small forms display the minimum requirements. The "details" version displays the internal workings, which should be kept secret in a real appication. Download the project from the links below.

Encryption with a password

Decryption

Encryption form showing (secret) details:

Encryption showing (secret) details

Decryption form showing (secret) details:

Decryption showing (secret) details

Further explanation of VB.NET example

The program always generates a fresh IV value, so every time you run it you will get a different result, even for the same password and plaintext. This is by design.

In the example shown above, we derive the 128-bit key as follows:

Password = "password" 
         = (0x)70617373776F7264
Salt     = (0x)B8A112A270D9634EFF3818F6CCBDF5EC
Key      = PBKDF2(Password, Salt, 2048)
         = (0x)900873522F55634679EF64CC25E73354

Then we use that key to compute the ciphertext:

Plaintext   = "Hello world!" 
            = (0x)48656C6C6F20776F726C6421
PaddedInput = (0x)48656C6C6F20776F726C642104040404
IV          = (0x)B8A112A270D9634EFF3818F6CCBDF5EC
Key         = (0x)900873522F55634679EF64CC25E73354
Ciphertext  = (0x)625F094A1FB167F521B6014321A807EC
We use the same randomly-generated value for the salt and the IV.

We need to transmit both the IV and the ciphertext to our recipient. The simplest convention is just to concatenate the 16-byte IV and the ciphertext and transmit that. The recipient would then split the received data to get the IV and ciphertext. If you transmitted using hex-encoding the message would be

B8A112A270D9634EFF3818F6CCBDF5EC625F094A1FB167F521B6014321A807EC
Alternatively, the same message in base64 encoding is
uKESonDZY07/OBj2zL317GJfCUofsWf1IbYBQyGoB+w=	

VB.NET to VB6

2009-06-21 In response to a query from a client, here is a translation of the core VB.NET procedures above into VB6. The VB6 example just outputs to the Immediate Window using Debug.Print but you should be able to figure out how to use it in a real VB6 (or VBA) project.

Visual Basic (VB6) Project

The example program in VB6 uses AES-128 in CBC mode to carry out the encryption and shows three different methods to derive the key from the password string. Two of the the methods used are from PKCS#5 v2.0. These are referred to as PBKDF1 and PBKDF2 where PBKDF stands for Password-Based Key Derivation Function. PBKDF2 is recommended for all new applications. The third method is to show how it should not be done - we suspect that many developers out there are using this method or variants of it.

We show these three alternative methods as educative examples of how you could do it. In practice, please just use the recommended PBKDF2 method.

VB6 Sample Screen Dumps

The first example shows the simple (not recommended) method of deriving the key by simply copying the password byte-by-byte into the key and then leaving the remaining bits as zero.

Simple password derivation

Thus only the first 24 bits out of 128 are used. We still get what appears to be well-encrypted ciphertext and, because we use CBC mode with a different IV each time we carry out the encryption, we will get a completely different ciphertext each time. However, the key space to be searched by an attacker is relatively small, and could be speeded up by constructing a dictionary of possible passwords. This would be true even if we made a hash of the password first.

The second example below shows the use of the PBKDF2 function. Every time the encryption is carried out we now have a completely different key as well as a fresh IV. This is because the password is salted with a different IV each time when deriving the key. Although the resulting ciphertext looks equally as "random" as the first example, the keyspace to be searched by an attacker and the amount of effort required to crack the encryption by the brute force method of trying all possible passwords is an order of magnitude larger.

PBKDF2 password derivation

Use the sample EXE program to try repeated use of these different algorithms and see the changes in the key, IV and resulting encoded ciphertext each time. (BTW, we don't recommend using "abc" as a password either; it's just for demonstration purposes.)

VB6 Algorithm

To encrypt, the program does the following:- To decrypt, we do the reverse:-

Important Implementation Notes

Note how all operations on 'binary' data are carried out using the Visual Basic Byte type, and all textual data (original text input, password and base64-encoded data) are stored in the VB String type. It is important to make sure you differentiate between these two types of data - broadly speaking 'text' and 'binary' - when doing cryptographic operations in Visual Basic.

'Text' consists of readable, printable characters we expect to see on our computer screen or in a book. It might consist of simple US-ASCII/ANSI characters or it could be Unicode or DBCS oriental character strings. 'Binary' data is a string of bits that we conventionally store as bytes or octets.

Binary data must be exactly the same literally bit-for-bit in all systems. Change one bit and the results of any cryptographic operation on it will be completely different. One way to ensure our binary data is always the same is to use the Byte type to store binary data, not the String type.

Text data may be stored differently depending on the particular system we are using. On a ANSI system, each character is stored in one byte. On a Unicode system each character is stored in two bytes; and on a DBCS system, a character may be stored in one or two bytes. It is important to make sure we always convert our 'text' data into exactly the same 'binary' form before we do any encryption.

Download the source code

Both programs require CryptoSys API to be installed on your system. You can download a Trial edition from here.

Erratum

In the VB6 module basWipe change ByVal to ByRef

Public Sub WipeString(ByValByRef str As String)
    Call WIPE_String(str, Len(str))
    str = ""
End Sub

Thanks to Jason Dodson for pointing this out.

References

Other Information

For our introductory pages to other topics in cryptography, see

Contact

Email Us

First published 2003. Source code last updated 2007. This page last updated 7 December 2016