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
- Security Considerations
- Algorithms
- VB.NET Project
- VB.NET to VB6
- VB6 Project
- Important Implementation Notes
- Download the source code
- References
- Other Information
Example Projects
There are two example projects.- A VB.NET project that emphasises the steps involved in encryption and decryption using hex-encoding for clarity.
- 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.
[Update 2023]: since this was written back in the day, the preferred method of encryption now is to use an authenticated encryption algorithm like AES-GCM which adds an authenticated tag to the ciphertext. See Authenticated Encryption.
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=nnnIn practice, you can be more succinct.
We suggest that sender and recipient agree on these conventions beforehand:
- The iteration count is always the same value (e.g. 2048). This saves having to transmit it each time.
- 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.
- 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.
Security considerations
- Do not use ECB mode! There are several modes of operation for block ciphers: ECB (Electronic CodeBook), CBC (Cipher Block Chaining), CFB (Cipher FeedBack), OFB (Output FeedBack) and CTR (Counter). For further details, see [BCMO]. In some packages, the default mode is ECB. It is not secure. Always use CBC (Cipher Block Chaining) mode. This requires you to provide a separate Initialization Vector (IV) which must be unique for every message you ever send using the same key.
- A password is not a key! A password is a text string of variable length. Encryption algorithms require the key as a fixed-length bit-string. You must convert the password text string to a bit-string. Use the Password-Based Key Derivation Function algorithm known as PBKDF2 to do this. This method requires a random salt and an iteration count as well as the password text. For convenience, you can use the same random value for the salt as for the IV. Just make sure that the sender and recipient agree on this convention.
- Ciphertext is not text! Ciphertext is a bit string that should not be stored in a "string" type. Encode the ciphertext in hexadecimal or base64, which can be safely stored and transmitted as a string type.
- Use a unique IV each time! Always create a fresh, randomly-generated IV/salt each and every time you encrypt a new message to send. Never give your users the opportunity to re-use an old one or avoid the automatic re-generation process.
Algorithms
Encryption Algorithm
Algorithm: Encryption with password using block cipher in CBC mode
INPUT:
- Plaintext inputText in text format.
- A pre-agreed secret password in text format.
- iterationCount, an integer > 1000.
- Encryption algorithm (e.g. AES-128) with given keySize and blockSize.
OUTPUT:
- Initialization Vector iv in bit-string format.
- cipherText in bit-string format.
- Generate a random value of the same size as the block length of the encryption algorithm
randVal = GenerateRandomValue(blockSize)
- Set
iv = randVal salt = randVal
- Generate a key of the required size for the encryption algorithm
key=KDF2(password, salt, iterationCount, keySize)
- Convert the input text to `binary' format
work = BitStringFromText(inputText)
. - Pad this to the next-highest multiple of the encryption block size
work = Pad(work, blockSize)
. - Encrypt the working block in CBC mode using the key and IV
ciphertext = EncryptCBC(work, key, iv)
- Output iv and cipherText.
Decryption Algorithm
Algorithm: Decryption with password using block cipher in CBC mode
INPUT:
- cipherText in bit-string format.
- A pre-agreed secret password in text format.
- An integer iterationCount
- Initialization Vector iv in bit-string format.
- Encryption algorithm (e.g. AES-128) with given keySize and blockSize.
OUTPUT:
- Plaintext outText in text format or
"decryption error"
- If
Length(iv) != blockSize OR Length(cipherText) Mod blockSize != 0
then output "decryption error" and stop. - Set
salt = iv
- Generate a key of the required size for the encryption algorithm
key=KDF2(password, salt, iterationCount, keySize)
- Decrypt the ciphertext using the key and IV
work = DecryptCBC(cipherText, key, iv)
- Remove the padding, if any
work = Unpad(work, blockSize)
. - If no valid padding is found, output "decryption error" and stop.
- Convert the bit-string ciphertext to `text' format
outText = TextFromBitString(work)
- 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].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 form showing (secret) details:
Decryption form 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)625F094A1FB167F521B6014321A807ECWe 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.
- VB.NET code DemoMain.vb
- VB6 code basMain.bas
- Sample output from VB6 code
- Zipped files (4 kB)
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.
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.
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:-- Converts the text data to be encrypted and the text password into
Byte
arrays using the Visual BasicStrConv()
function. This is important, see Important Implementation Notes. - Pads the input data with between 1 and 16 bytes to make the length an exact multiple of the block size (16-bytes). Make the value of all the padding bytes the same as the number of padding bytes added. Note that we always add padding to make it unambiguous.
- Generates a 16-byte pseudo-random IV using the
RNG_Nonce()
function. We don't need a 'cryptographically secure' random value, just one that is unique. - Uses this IV (or part of it) as the salt in the key derivation function.
- Encrypts the padded plaintext data with AES-128 in CBC mode using the key and IV generated above.
- Prepends the 16-byte IV to the ciphertext bytes and encodes all of this using base64 encoding. This is the message Alice will send to Bob.
- Decode the message from base64 format to binary 'Byte' format.
- Strip off the 16-byte IV.
- Use this IV as the salt to derive the key from the shared secret password.
- Decrypt the ciphertext using the IV from the start of the message and the key derived from the password.
- Carry out a check on bad decryption by looking at the value of the last byte in the decrypted ciphertext. It should be between 1 and 16. If not, flag an error.
- Strip off the required number of padding bytes and convert the resulting Bytes into
text using the
StrConv()
function. - Output the resulting plaintext.
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
- VB.NET project (54 kB). First published February 2007.
- VB6 project (25 kB). First published 2003. Updated February 2007.
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
- [BCMO] NIST Special Publication 800-38A Recommendations for Block Cipher Modes of Operation, Methods and Techniques, Morris Dworkin, December 2001.
- [CMS] RFC 5652, Cryptographic Message Syntax (CMS), R. Housley, September 2009 (obsoletes RFC 3825, RFC 3369 and RFC 2630), <https://tools.ietf.org/html/rfc5652>.
- [FIPS197] Federal Information Processing Standards Publication FIPS PUB 197 Advanced Encryption Standard (AES), U.S. Department Of Commerce/National Institute of Standards and Technology, 26 November 2001.
- [PKCS5] PKCS #5, Password-Based Encryption Standard, RSA Laboratories, Version 2.0, March 1999. Reprinted as [RFC2898].
- [PKCS7] PKCS #7, Cryptographic Message Syntax Standard, RSA Laboratories, Version 1.5, November 1993. Reprinted as [RFC2315].
Other Information
For our introductory pages to other topics in cryptography, see- An introduction to using keys in cryptography.
- Using Padding in encryption.
- Ciphertext is not text! - Storing and representing ciphertext.
- Cross-platform encryption.
- Cryptography with international character sets.
Contact
First published 2003. Source code last updated 2007. This page last updated 9 September 2025