Show Table of Contents

Description

These code samples demonstrate how to use randomly generated bytes (so-called salt) to produce different ciphertext from the same plaintext values encrypted with the same symmetric key and initialization vector. This approach can be used to reduce the risk of dictionary attacks without relying on different keys, initialization vectors, and other encryption parameters. It allows a program to eliminate one trip to the database (or another storage media holding encrypted data), which may be needed for obtaining the value of initialization vector, making the application more efficient. The samples use the Rijndael (AES) algorithm, but you can easily adjust them to use Triple-DES or any other symmetric key algorithm supported by .NET Framework.

Back to top

Disclaimer

These code samples are provided for demonstration purpose only. Use at your own risk.

Back to top

License

These code samples are released under the dual open source license.

Back to top

C# code sample

///////////////////////////////////////////////////////////////////////////////
// SAMPLE: Illustrates symmetric key encryption and decryption using Rijndael
//         algorithm. In addition to performing encryption, this sample 
//         explains how to add a salt value (randomly generated bytes) to 
//         plain text before the value is encrypted. This can help reduce the
//         risk of dictionary attacks.
//
// To run this sample, create a new Visual C# project using the Console 
// Application template and replace the contents of the Module1.vb file with
// the code below.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
//
// Copyright (C) 2003 Obviex(TM). All rights reserved.
//
using System;
using System.IO;
using System.Text;
using System.Security.Cryptography;
 
/// <summary>
/// This class uses a symmetric key algorithm (Rijndael/AES) to encrypt and
/// decrypt data. As long as it is initialized with the same constructor
/// parameters, the class will use the same key. Before performing encryption,
/// the class can prepend random bytes to plain text and generate different
/// encrypted values from the same plain text, encryption key, initialization
/// vector, and other parameters. This class is thread-safe.
/// </summary>
/// <remarks>
/// Be careful when performing encryption and decryption. There is a bug
/// ("feature"?) in .NET Framework, which causes corruption of encryptor/
/// decryptor if a cryptographic exception occurs during encryption/
/// decryption operation. To correct the problem, re-initialize the class
/// instance when a cryptographic exception occurs.
/// </remarks>
public class RijndaelEnhanced
{
    #region Private members
    // If hashing algorithm is not specified, use SHA-1.
    private static string  DEFAULT_HASH_ALGORITHM  = "SHA1";
 
    // If key size is not specified, use the longest 256-bit key.
    private static int     DEFAULT_KEY_SIZE        = 256;
 
    // Do not allow salt to be longer than 255 bytes, because we have only
    // 1 byte to store its length. 
    private static int     MAX_ALLOWED_SALT_LEN    = 255;
 
    // Do not allow salt to be smaller than 4 bytes, because we use the first
    // 4 bytes of salt to store its length. 
    private static int     MIN_ALLOWED_SALT_LEN    = 4;
 
    // Random salt value will be between 4 and 8 bytes long.
    private static int     DEFAULT_MIN_SALT_LEN    = MIN_ALLOWED_SALT_LEN;
    private static int     DEFAULT_MAX_SALT_LEN    = 8;
 
    // Use these members to save min and max salt lengths.
    private int     minSaltLen              = -1;
    private int     maxSaltLen              = -1;
 
    // These members will be used to perform encryption and decryption.
    private ICryptoTransform encryptor      = null;
    private ICryptoTransform decryptor      = null;
    #endregion
 
    #region Constructors
    /// <summary>
    /// Use this constructor if you are planning to perform encryption/
    /// decryption with 256-bit key, derived using 1 password iteration,
    /// hashing without salt, no initialization vector, electronic codebook
    /// (ECB) mode, SHA-1 hashing algorithm, and 4-to-8 byte long salt.
    /// </summary>
    /// <param name="passPhrase">
    /// Passphrase from which a pseudo-random password will be derived.
    /// The derived password will be used to generate the encryption key.
    /// Passphrase can be any string. In this example we assume that the
    /// passphrase is an ASCII string. Passphrase value must be kept in
    /// secret.
    /// </param>
    /// <remarks>
    /// This constructor is not recommended because it does not use
    /// initialization vector and uses the ECB cipher mode, which is less
    /// secure than the CBC mode.
    /// </remarks>
    public RijndaelEnhanced(string  passPhrase) : 
        this(passPhrase, null)
    {
    }
 
    /// <summary>
    /// Use this constructor if you are planning to perform encryption/
    /// decryption with 256-bit key, derived using 1 password iteration,
    /// hashing without salt, cipher block chaining (CBC) mode, SHA-1
    /// hashing algorithm, and 4-to-8 byte long salt.
    /// </summary>
    /// <param name="passPhrase">
    /// Passphrase from which a pseudo-random password will be derived.
    /// The derived password will be used to generate the encryption key.
    /// Passphrase can be any string. In this example we assume that the
    /// passphrase is an ASCII string. Passphrase value must be kept in
    /// secret.
    /// </param>
    /// <param name="initVector">
    /// Initialization vector (IV). This value is required to encrypt the
    /// first block of plaintext data. For RijndaelManaged class IV must be
    /// exactly 16 ASCII characters long. IV value does not have to be kept
    /// in secret.
    /// </param>
    public RijndaelEnhanced(string  passPhrase,
                            string  initVector) :
        this(passPhrase, initVector, -1)
    {
    }
 
    /// <summary>
    /// Use this constructor if you are planning to perform encryption/
    /// decryption with 256-bit key, derived using 1 password iteration,
    /// hashing without salt, cipher block chaining (CBC) mode, SHA-1 
    /// hashing algorithm, and 0-to-8 byte long salt.
    /// </summary>
    /// <param name="passPhrase">
    /// Passphrase from which a pseudo-random password will be derived.
    /// The derived password will be used to generate the encryption key
    /// Passphrase can be any string. In this example we assume that the
    /// passphrase is an ASCII string. Passphrase value must be kept in
    /// secret.
    /// </param>
    /// <param name="initVector">
    /// Initialization vector (IV). This value is required to encrypt the
    /// first block of plaintext data. For RijndaelManaged class IV must be
    /// exactly 16 ASCII characters long. IV value does not have to be kept
    /// in secret.
    /// </param>
    /// <param name="minSaltLen">
    /// Min size (in bytes) of randomly generated salt which will be added at
    /// the beginning of plain text before encryption is performed. When this
    /// value is less than 4, the default min value will be used (currently 4
    /// bytes).
    /// </param>
    public RijndaelEnhanced(string  passPhrase,
                            string  initVector,
                            int     minSaltLen) :
        this(passPhrase, initVector, minSaltLen, -1)
    {
    }
 
    /// <summary>
    /// Use this constructor if you are planning to perform encryption/
    /// decryption with 256-bit key, derived using 1 password iteration,
    /// hashing without salt, cipher block chaining (CBC) mode, SHA-1
    /// hashing algorithm. Use the minSaltLen and maxSaltLen parameters to
    /// specify the size of randomly generated salt.
    /// </summary>
    /// <param name="passPhrase">
    /// Passphrase from which a pseudo-random password will be derived.
    /// The derived password will be used to generate the encryption key.
    /// Passphrase can be any string. In this example we assume that the
    /// passphrase is an ASCII string. Passphrase value must be kept in
    /// secret.
    /// </param>
    /// <param name="initVector">
    /// Initialization vector (IV). This value is required to encrypt the
    /// first block of plaintext data. For RijndaelManaged class IV must be
    /// exactly 16 ASCII characters long. IV value does not have to be kept
    /// in secret.
    /// </param>
    /// <param name="minSaltLen">
    /// Min size (in bytes) of randomly generated salt which will be added at
    /// the beginning of plain text before encryption is performed. When this
    /// value is less than 4, the default min value will be used (currently 4
    /// bytes).
    /// </param>
    /// <param name="maxSaltLen">
    /// Max size (in bytes) of randomly generated salt which will be added at
    /// the beginning of plain text before encryption is performed. When this
    /// value is negative or greater than 255, the default max value will be
    /// used (currently 8 bytes). If max value is 0 (zero) or if it is smaller
    /// than the specified min value (which can be adjusted to default value),
    /// salt will not be used and plain text value will be encrypted as is.
    /// In this case, salt will not be processed during decryption either.
    /// </param>
    public RijndaelEnhanced(string  passPhrase,
                            string  initVector,
                            int     minSaltLen,
                            int     maxSaltLen) :
        this(passPhrase, initVector, minSaltLen, maxSaltLen, -1)
    {
    }
 
    /// <summary>
    /// Use this constructor if you are planning to perform encryption/
    /// decryption using the key derived from 1 password iteration,
    /// hashing without salt, cipher block chaining (CBC) mode, and
    /// SHA-1 hashing algorithm.
    /// </summary>
    /// <param name="passPhrase">
    /// Passphrase from which a pseudo-random password will be derived.
    /// The derived password will be used to generate the encryption key.
    /// Passphrase can be any string. In this example we assume that the
    /// passphrase is an ASCII string. Passphrase value must be kept in
    /// secret.
    /// </param>
    /// <param name="initVector">
    /// Initialization vector (IV). This value is required to encrypt the
    /// first block of plaintext data. For RijndaelManaged class IV must be
    /// exactly 16 ASCII characters long. IV value does not have to be kept
    /// in secret.
    /// </param>
    /// <param name="minSaltLen">
    /// Min size (in bytes) of randomly generated salt which will be added at
    /// the beginning of plain text before encryption is performed. When this
    /// value is less than 4, the default min value will be used (currently 4
    /// bytes).
    /// </param>
    /// <param name="maxSaltLen">
    /// Max size (in bytes) of randomly generated salt which will be added at
    /// the beginning of plain text before encryption is performed. When this
    /// value is negative or greater than 255, the default max value will be 
    /// used (currently 8 bytes). If max value is 0 (zero) or if it is smaller
    /// than the specified min value (which can be adjusted to default value),
    /// salt will not be used and plain text value will be encrypted as is.
    /// In this case, salt will not be processed during decryption either.
    /// </param>
    /// <param name="keySize">
    /// Size of symmetric key (in bits): 128, 192, or 256.
    /// </param>
    public RijndaelEnhanced(string  passPhrase,
                            string  initVector,
                            int     minSaltLen,
                            int     maxSaltLen,
                            int     keySize) :
        this(passPhrase, initVector, minSaltLen, maxSaltLen, keySize, null)
    {
    }
 
    /// <summary>
    /// Use this constructor if you are planning to perform encryption/
    /// decryption using the key derived from 1 password iteration, hashing 
    /// without salt, and cipher block chaining (CBC) mode.
    /// </summary>
    /// <param name="passPhrase">
    /// Passphrase from which a pseudo-random password will be derived.
    /// The derived password will be used to generate the encryption key.
    /// Passphrase can be any string. In this example we assume that the
    /// passphrase is an ASCII string. Passphrase value must be kept in
    /// secret.
    /// </param>
    /// <param name="initVector">
    /// Initialization vector (IV). This value is required to encrypt the
    /// first block of plaintext data. For RijndaelManaged class IV must be
    /// exactly 16 ASCII characters long. IV value does not have to be kept
    /// in secret.
    /// </param>
    /// <param name="minSaltLen">
    /// Min size (in bytes) of randomly generated salt which will be added at
    /// the beginning of plain text before encryption is performed. When this
    /// value is less than 4, the default min value will be used (currently 4
    /// bytes).
    /// </param>
    /// <param name="maxSaltLen">
    /// Max size (in bytes) of randomly generated salt which will be added at
    /// the beginning of plain text before encryption is performed. When this
    /// value is negative or greater than 255, the default max value will be
    /// used (currently 8 bytes). If max value is 0 (zero) or if it is smaller
    /// than the specified min value (which can be adjusted to default value),
    /// salt will not be used and plain text value will be encrypted as is.
    /// In this case, salt will not be processed during decryption either.
    /// </param>
    /// <param name="keySize">
    /// Size of symmetric key (in bits): 128, 192, or 256.
    /// </param>
    /// <param name="hashAlgorithm">
    /// Hashing algorithm: "MD5" or "SHA1". SHA1 is recommended.
    /// </param>
    public RijndaelEnhanced(string  passPhrase,
                            string  initVector,
                            int     minSaltLen,
                            int     maxSaltLen, 
                            int     keySize,
                            string  hashAlgorithm) : 
        this(passPhrase, initVector, minSaltLen, maxSaltLen, keySize, 
             hashAlgorithm, null)
    {
    }
 
    /// <summary>
    /// Use this constructor if you are planning to perform encryption/
    /// decryption using the key derived from 1 password iteration, and
    /// cipher block chaining (CBC) mode.
    /// </summary>
    /// <param name="passPhrase">
    /// Passphrase from which a pseudo-random password will be derived.
    /// The derived password will be used to generate the encryption key.
    /// Passphrase can be any string. In this example we assume that the
    /// passphrase is an ASCII string. Passphrase value must be kept in
    /// secret.
    /// </param>
    /// <param name="initVector">
    /// Initialization vector (IV). This value is required to encrypt the
    /// first block of plaintext data. For RijndaelManaged class IV must be
    /// exactly 16 ASCII characters long. IV value does not have to be kept
    /// in secret.
    /// </param>
    /// <param name="minSaltLen">
    /// Min size (in bytes) of randomly generated salt which will be added at
    /// the beginning of plain text before encryption is performed. When this
    /// value is less than 4, the default min value will be used (currently 4
    /// bytes).
    /// </param>
    /// <param name="maxSaltLen">
    /// Max size (in bytes) of randomly generated salt which will be added at
    /// the beginning of plain text before encryption is performed. When this
    /// value is negative or greater than 255, the default max value will be
    /// used (currently 8 bytes). If max value is 0 (zero) or if it is smaller
    /// than the specified min value (which can be adjusted to default value),
    /// salt will not be used and plain text value will be encrypted as is.
    /// In this case, salt will not be processed during decryption either.
    /// </param>
    /// <param name="keySize">
    /// Size of symmetric key (in bits): 128, 192, or 256.
    /// </param>
    /// <param name="hashAlgorithm">
    /// Hashing algorithm: "MD5" or "SHA1". SHA1 is recommended.
    /// </param>
    /// <param name="saltValue">
    /// Salt value used for password hashing during key generation. This is
    /// not the same as the salt we will use during encryption. This parameter
    /// can be any string.
    /// </param>
    public RijndaelEnhanced(string  passPhrase,
                            string  initVector,
                            int     minSaltLen,
                            int     maxSaltLen, 
                            int     keySize,
                            string  hashAlgorithm,
                            string  saltValue) : 
        this(passPhrase, initVector, minSaltLen, maxSaltLen, keySize, 
             hashAlgorithm, saltValue, 1)
    {
    }
 
    /// <summary>
    /// Use this constructor if you are planning to perform encryption/
    /// decryption with the key derived from the explicitly specified
    /// parameters.
    /// </summary>
    /// <param name="passPhrase">
    /// Passphrase from which a pseudo-random password will be derived.
    /// The derived password will be used to generate the encryption key
    /// Passphrase can be any string. In this example we assume that the
    /// passphrase is an ASCII string. Passphrase value must be kept in
    /// secret.
    /// </param>
    /// <param name="initVector">
    /// Initialization vector (IV). This value is required to encrypt the
    /// first block of plaintext data. For RijndaelManaged class IV must be
    /// exactly 16 ASCII characters long. IV value does not have to be kept
    /// in secret.
    /// </param>
    /// <param name="minSaltLen">
    /// Min size (in bytes) of randomly generated salt which will be added at
    /// the beginning of plain text before encryption is performed. When this
    /// value is less than 4, the default min value will be used (currently 4
    /// bytes).
    /// </param>
    /// <param name="maxSaltLen">
    /// Max size (in bytes) of randomly generated salt which will be added at
    /// the beginning of plain text before encryption is performed. When this
    /// value is negative or greater than 255, the default max value will be
    /// used (currently 8 bytes). If max value is 0 (zero) or if it is smaller
    /// than the specified min value (which can be adjusted to default value),
    /// salt will not be used and plain text value will be encrypted as is.
    /// In this case, salt will not be processed during decryption either.
    /// </param>
    /// <param name="keySize">
    /// Size of symmetric key (in bits): 128, 192, or 256.
    /// </param>
    /// <param name="hashAlgorithm">
    /// Hashing algorithm: "MD5" or "SHA1". SHA1 is recommended.
    /// </param>
    /// <param name="saltValue">
    /// Salt value used for password hashing during key generation. This is
    /// not the same as the salt we will use during encryption. This parameter
    /// can be any string.
    /// </param>
    /// <param name="passwordIterations">
    /// Number of iterations used to hash password. More iterations are
    /// considered more secure but may take longer.
    /// </param>
    public RijndaelEnhanced(string  passPhrase,
                            string  initVector,
                            int     minSaltLen,
                            int     maxSaltLen, 
                            int     keySize,
                            string  hashAlgorithm,
                            string  saltValue,
                            int     passwordIterations)
    {
        // Save min salt length; set it to default if invalid value is passed.
        if (minSaltLen < MIN_ALLOWED_SALT_LEN)
            this.minSaltLen = DEFAULT_MIN_SALT_LEN;
        else
            this.minSaltLen = minSaltLen;
 
        // Save max salt length; set it to default if invalid value is passed.
        if (maxSaltLen < 0 || maxSaltLen > MAX_ALLOWED_SALT_LEN)
            this.maxSaltLen = DEFAULT_MAX_SALT_LEN;
        else
            this.maxSaltLen = maxSaltLen;
 
        // Set the size of cryptographic key.
        if (keySize <= 0)
            keySize = DEFAULT_KEY_SIZE;
 
        // Set the name of algorithm. Make sure it is in UPPER CASE and does
        // not use dashes, e.g. change "sha-1" to "SHA1".
        if (hashAlgorithm == null)
            hashAlgorithm = DEFAULT_HASH_ALGORITHM;
        else
            hashAlgorithm = hashAlgorithm.ToUpper().Replace("-", "");
 
        // Initialization vector converted to a byte array.
        byte[] initVectorBytes = null;
 
        // Salt used for password hashing (to generate the key, not during
        // encryption) converted to a byte array.
        byte[] saltValueBytes  = null;
 
        // Get bytes of initialization vector.
        if (initVector == null)
            initVectorBytes = new byte[0];
        else
            initVectorBytes = Encoding.ASCII.GetBytes(initVector);
 
        // Get bytes of salt (used in hashing).
        if (saltValue == null)
            saltValueBytes = new byte[0];
        else
            saltValueBytes = Encoding.ASCII.GetBytes(saltValue);
 
        // Generate password, which will be used to derive the key.
        PasswordDeriveBytes password = new PasswordDeriveBytes(
                                                   passPhrase,
                                                   saltValueBytes,
                                                   hashAlgorithm,
                                                   passwordIterations);
 
        // Convert key to a byte array adjusting the size from bits to bytes.
        byte[] keyBytes = password.GetBytes(keySize / 8);
 
        // Initialize Rijndael key object.
        RijndaelManaged symmetricKey = new RijndaelManaged();
 
        // If we do not have initialization vector, we cannot use the CBC mode.
        // The only alternative is the ECB mode (which is not as good).
        if (initVectorBytes.Length == 0)
            symmetricKey.Mode = CipherMode.ECB;
        else
            symmetricKey.Mode = CipherMode.CBC;
 
        // Create encryptor and decryptor, which we will use for cryptographic
        // operations.
        encryptor = symmetricKey.CreateEncryptor(keyBytes, initVectorBytes);
        decryptor = symmetricKey.CreateDecryptor(keyBytes, initVectorBytes);
    }
    #endregion
 
    #region Encryption routines
    /// <summary>
    /// Encrypts a string value generating a base64-encoded string.
    /// </summary>
    /// <param name="plainText">
    /// Plain text string to be encrypted.
    /// </param>
    /// <returns>
    /// Cipher text formatted as a base64-encoded string.
    /// </returns>
    public string Encrypt(string plainText)
    {
        return Encrypt(Encoding.UTF8.GetBytes(plainText));
    }
 
    /// <summary>
    /// Encrypts a byte array generating a base64-encoded string.
    /// </summary>
    /// <param name="plainTextBytes">
    /// Plain text bytes to be encrypted.
    /// </param>
    /// <returns>
    /// Cipher text formatted as a base64-encoded string.
    /// </returns>
    public string Encrypt(byte[] plainTextBytes)
    {
        return Convert.ToBase64String(EncryptToBytes(plainTextBytes));
    }
 
    /// <summary>
    /// Encrypts a string value generating a byte array of cipher text.
    /// </summary>
    /// <param name="plainText">
    /// Plain text string to be encrypted.
    /// </param>
    /// <returns>
    /// Cipher text formatted as a byte array.
    /// </returns>
    public byte[] EncryptToBytes(string plainText)
    {
        return EncryptToBytes(Encoding.UTF8.GetBytes(plainText));
    }
 
    /// <summary>
    /// Encrypts a byte array generating a byte array of cipher text.
    /// </summary>
    /// <param name="plainTextBytes">
    /// Plain text bytes to be encrypted.
    /// </param>
    /// <returns>
    /// Cipher text formatted as a byte array.
    /// </returns>
    public byte[] EncryptToBytes(byte[] plainTextBytes)
    {
        // Add salt at the beginning of the plain text bytes (if needed).
        byte[] plainTextBytesWithSalt = AddSalt(plainTextBytes);
 
        // Encryption will be performed using memory stream.
        MemoryStream memoryStream = new MemoryStream();
        
        // Let's make cryptographic operations thread-safe.
        lock (this)
        {
            // To perform encryption, we must use the Write mode.
            CryptoStream cryptoStream = new CryptoStream(
                                               memoryStream, 
                                               encryptor,
                                                CryptoStreamMode.Write);
 
            // Start encrypting data.
            cryptoStream.Write( plainTextBytesWithSalt, 
                                0, 
                               plainTextBytesWithSalt.Length);
             
            // Finish the encryption operation.
            cryptoStream.FlushFinalBlock();
 
            // Move encrypted data from memory into a byte array.
            byte[] cipherTextBytes = memoryStream.ToArray();
               
            // Close memory streams.
            memoryStream.Close();
            cryptoStream.Close();
 
            // Return encrypted data.
            return cipherTextBytes;
        }
    }
    #endregion
 
    #region Decryption routines
    /// <summary>
    /// Decrypts a base64-encoded cipher text value generating a string result.
    /// </summary>
    /// <param name="cipherText">
    /// Base64-encoded cipher text string to be decrypted.
    /// </param>
    /// <returns>
    /// Decrypted string value.
    /// </returns>
    public string Decrypt(string cipherText)
    {
        return Decrypt(Convert.FromBase64String(cipherText));
    }
 
    /// <summary>
    /// Decrypts a byte array containing cipher text value and generates a
    /// string result.
    /// </summary>
    /// <param name="cipherTextBytes">
    /// Byte array containing encrypted data.
    /// </param>
    /// <returns>
    /// Decrypted string value.
    /// </returns>
    public string Decrypt(byte[] cipherTextBytes)
    {
        return Encoding.UTF8.GetString(DecryptToBytes(cipherTextBytes));
    }
 
    /// <summary>
    /// Decrypts a base64-encoded cipher text value and generates a byte array
    /// of plain text data.
    /// </summary>
    /// <param name="cipherText">
    /// Base64-encoded cipher text string to be decrypted.
    /// </param>
    /// <returns>
    /// Byte array containing decrypted value.
    /// </returns>
    public byte[] DecryptToBytes(string cipherText)
    {
        return DecryptToBytes(Convert.FromBase64String(cipherText));
    }
 
    /// <summary>
    /// Decrypts a base64-encoded cipher text value and generates a byte array
    /// of plain text data.
    /// </summary>
    /// <param name="cipherTextBytes">
    /// Byte array containing encrypted data.
    /// </param>
    /// <returns>
    /// Byte array containing decrypted value.
    /// </returns>
    public byte[] DecryptToBytes(byte[] cipherTextBytes)
    {
        byte[] decryptedBytes      = null;
        byte[] plainTextBytes      = null;
        int    decryptedByteCount  = 0;
        int    saltLen             = 0;
 
        MemoryStream memoryStream = new MemoryStream(cipherTextBytes);
 
        // Since we do not know how big decrypted value will be, use the same
        // size as cipher text. Cipher text is always longer than plain text
        // (in block cipher encryption), so we will just use the number of
        // decrypted data byte after we know how big it is.
        decryptedBytes = new byte[cipherTextBytes.Length];
 
        // Let's make cryptographic operations thread-safe.
        lock (this)
        {
            // To perform decryption, we must use the Read mode.
            CryptoStream  cryptoStream = new CryptoStream(
                                               memoryStream,
                                               decryptor,
                                               CryptoStreamMode.Read);
 
            // Decrypting data and get the count of plain text bytes.
            decryptedByteCount  = cryptoStream.Read(decryptedBytes,
                                                    0, 
                                                    decryptedBytes.Length);
            // Release memory.
            memoryStream.Close();
            cryptoStream.Close();
        }
 
        // If we are using salt, get its length from the first 4 bytes of plain
        // text data.
        if (maxSaltLen > 0 && maxSaltLen >= minSaltLen)
        {
            saltLen =   (decryptedBytes[0] & 0x03) |
                        (decryptedBytes[1] & 0x0c) |
                        (decryptedBytes[2] & 0x30) |
                        (decryptedBytes[3] & 0xc0);
        }
 
        // Allocate the byte array to hold the original plain text (without salt).
        plainTextBytes = new byte[decryptedByteCount - saltLen];
 
        // Copy original plain text discarding the salt value if needed.
        Array.Copy(decryptedBytes, saltLen, plainTextBytes, 
                    0, decryptedByteCount - saltLen);
 
        // Return original plain text value.
        return plainTextBytes;
    }
    #endregion
 
    #region Helper functions
    /// <summary>
    /// Adds an array of randomly generated bytes at the beginning of the
    /// array holding original plain text value.
    /// </summary>
    /// <param name="plainTextBytes">
    /// Byte array containing original plain text value.
    /// </param>
    /// <returns>
    /// Either original array of plain text bytes (if salt is not used) or a
    /// modified array containing a randomly generated salt added at the 
    /// beginning of the plain text bytes. 
    /// </returns>
    private byte[] AddSalt(byte[] plainTextBytes)
    {
        // The max salt value of 0 (zero) indicates that we should not use 
        // salt. Also do not use salt if the max salt value is smaller than
        // the min value.
        if (maxSaltLen == 0 || maxSaltLen < minSaltLen)
            return plainTextBytes;
 
        // Generate the salt.
        byte[] saltBytes = GenerateSalt();
 
        // Allocate array which will hold salt and plain text bytes.
        byte[] plainTextBytesWithSalt = new byte[plainTextBytes.Length +
                                                 saltBytes.Length];
        // First, copy salt bytes.
        Array.Copy(saltBytes, plainTextBytesWithSalt, saltBytes.Length);
 
        // Append plain text bytes to the salt value.
        Array.Copy( plainTextBytes, 0, 
                    plainTextBytesWithSalt, saltBytes.Length,
                    plainTextBytes.Length);
 
        return plainTextBytesWithSalt;
    }
 
    /// <summary>
    /// Generates an array holding cryptographically strong bytes.
    /// </summary>
    /// <returns>
    /// Array of randomly generated bytes.
    /// </returns>
    /// <remarks>
    /// Salt size will be defined at random or exactly as specified by the
    /// minSlatLen and maxSaltLen parameters passed to the object constructor.
    /// The first four bytes of the salt array will contain the salt length
    /// split into four two-bit pieces.
    /// </remarks>
    private byte[] GenerateSalt()
    {
        // We don't have the length, yet.
        int saltLen = 0;
 
        // If min and max salt values are the same, it should not be random.
        if (minSaltLen == maxSaltLen)
            saltLen = minSaltLen;
        // Use random number generator to calculate salt length.
        else
            saltLen = GenerateRandomNumber(minSaltLen, maxSaltLen);
 
        // Allocate byte array to hold our salt.
        byte[] salt = new byte[saltLen];
 
        // Populate salt with cryptographically strong bytes.
        RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
 
        rng.GetNonZeroBytes(salt);
 
        // Split salt length (always one byte) into four two-bit pieces and
        // store these pieces in the first four bytes of the salt array.
        salt[0] = (byte)((salt[0] & 0xfc) | (saltLen & 0x03));
        salt[1] = (byte)((salt[1] & 0xf3) | (saltLen & 0x0c));
        salt[2] = (byte)((salt[2] & 0xcf) | (saltLen & 0x30));
        salt[3] = (byte)((salt[3] & 0x3f) | (saltLen & 0xc0));
 
        return salt;
    }
 
    /// <summary>
    /// Generates random integer.
    /// </summary>
    /// <param name="minValue">
    /// Min value (inclusive).
    /// </param>
    /// <param name="maxValue">
    /// Max value (inclusive).
    /// </param>
    /// <returns>
    /// Random integer value between the min and max values (inclusive).
    /// </returns>
    /// <remarks>
    /// This methods overcomes the limitations of .NET Framework's Random
    /// class, which - when initialized multiple times within a very short
    /// period of time - can generate the same "random" number.
    /// </remarks>
    private int GenerateRandomNumber(int minValue, int maxValue)
    {
        // We will make up an integer seed from 4 bytes of this array.
        byte[] randomBytes = new byte[4];
 
        // Generate 4 random bytes.
        RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
        rng.GetBytes(randomBytes);
 
        // Convert four random bytes into a positive integer value.
        int seed = ((randomBytes[0] & 0x7f) << 24) |
                    (randomBytes[1]         << 16) |
                    (randomBytes[2]         << 8 ) |
                    (randomBytes[3]);
 
        // Now, this looks more like real randomization.
        Random  random  = new Random(seed);
 
        // Calculate a random number.
        return random.Next(minValue, maxValue+1);
    }
    #endregion
}
 
/// <summary>
/// Illustrates the use of RijndaelEnhanced class to encrypt and decrypt data
/// using a random salt value.
/// </summary>
public class RijndaelEnhancedTest
{
    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    [STAThread]
    static void Main(string[] args)
    {
        string  plainText  = "Hello, World!";    // original plaintext
        string  cipherText = "";                 // encrypted text
        string  passPhrase = "Pas5pr@se";        // can be any string
        string  initVector = "@1B2c3D4e5F6g7H8"; // must be 16 bytes
 
        // Before encrypting data, we will append plain text to a random
        // salt value, which will be between 4 and 8 bytes long (implicitly
        // used defaults).
        RijndaelEnhanced rijndaelKey = 
            new RijndaelEnhanced(passPhrase, initVector);
 
        Console.WriteLine(String.Format("Plaintext   : {0}\n", plainText));
 
        // Encrypt the same plain text data 10 time (using the same key,
        // initialization vector, etc) and see the resulting cipher text;
        // encrypted values will be different.
        for (int i=0; i<10; i++)
        {
            cipherText =rijndaelKey.Encrypt(plainText);
            Console.WriteLine(
                String.Format("Encrypted #{0}: {1}", i, cipherText));
            plainText =rijndaelKey.Decrypt(cipherText);
        }
 
        // Make sure we got decryption working correctly.
        Console.WriteLine(String.Format("\nDecrypted   : {0}", plainText));
    }
}
//
// END OF FILE
///////////////////////////////////////////////////////////////////////////////

Back to top

VB.NET code sample

'------------------------------------------------------------------------------
' SAMPLE: Illustrates symmetric key encryption and decryption using Rijndael
'         algorithm. In addition to performing encryption, this sample 
'         explains how to add a salt value (randomly generated bytes) to 
'         plain text before the value is encrypted. This can help reduce the
'         risk of dictionary attacks.
'
' To run this sample, create a new Visual Basic.NET project using the Console 
' Application template and replace the contents of the Module1.vb file with
' the code below.
'
' THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
' EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED
' WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
'
' Copyright (C) 2003 Obviex(TM). All rights reserved.
'
Option Strict Off
 
Imports System
Imports System.IO
Imports System.Text
Imports System.Security.Cryptography
Imports Microsoft.VisualBasic
 
Module Module1
 
' <summary>
' This class uses a symmetric key algorithm (Rijndael/AES) to encrypt and
' decrypt data. As long as it is initialized with the same constructor
' parameters, the class will use the same key. Before performing encryption,
' the class can prepend random bytes to plain text and generate different
' encrypted values from the same plain text, encryption key, initialization
' vector, and other parameters. This class is thread-safe.
' </summary>
' <remarks>
' Be careful when performing encryption and decryption. There is a bug
' ("feature"?) in .NET Framework, which causes corruption of encryptor/
' decryptor if a cryptographic exception occurs during encryption/
' decryption operation. To correct the problem, re-initialize the class
' instance when a cryptographic exception occurs.
' </remarks>
Public Class RijndaelEnhanced
 
    ' If hashing algorithm is not specified, use SHA-1.
    Private Shared Dim DEFAULT_HASH_ALGORITHM As String = "SHA1"
 
    ' If key size is not specified, use the longest 256-bit key.
    Private Shared Dim DEFAULT_KEY_SIZE       As Integer= 256
 
    ' Do not allow salt to be longer than 255 bytes, because we have only
    ' 1 byte to store its length. 
    Private Shared Dim MAX_ALLOWED_SALT_LEN   As Integer= 255
 
    ' Do not allow salt to be smaller than 4 bytes, because we use the first
    ' 4 bytes of salt to store its length. 
    Private Shared Dim MIN_ALLOWED_SALT_LEN   As Integer= 4
 
    ' Random salt value will be between 4 and 8 bytes long.
    Private Shared Dim DEFAULT_MIN_SALT_LEN   As Integer= MIN_ALLOWED_SALT_LEN
    Private Shared Dim DEFAULT_MAX_SALT_LEN   As Integer= 8
 
    ' Use these members to save min and max salt lengths.
    Private Dim minSaltLen As Integer = -1
    Private Dim maxSaltLen As Integer = -1
 
    ' These members will be used to perform encryption and decryption.
    Private Dim encryptor As ICryptoTransform = Nothing
    Private Dim decryptor As ICryptoTransform = Nothing
 
    ' <summary>
    ' Use this constructor if you are planning to perform encryption/
    ' decryption with 256-bit key, derived using 1 password iteration,
    ' hashing without salt, no initialization vector, electronic codebook
    ' (ECB) mode, SHA-1 hashing algorithm, and 4-to-8 byte long salt.
    ' </summary>
    ' <param name="passPhrase">
    ' Passphrase from which a pseudo-random password will be derived.
    ' The derived password will be used to generate the encryption key.
    ' Passphrase can be any string. In this example we assume that the
    ' passphrase is an ASCII string. Passphrase value must be kept in
    ' secret.
    ' </param>
    ' <remarks>
    ' This constructor is not recommended because it does not use
    ' initialization vector and uses the ECB cipher mode, which is less
    ' secure than the CBC mode.
    ' </remarks>
    Public Sub New(passPhrase As String)
        Me.New(passPhrase, Nothing)
    End Sub
 
    ' <summary>
    ' Use this constructor if you are planning to perform encryption/
    ' decryption with 256-bit key, derived using 1 password iteration,
    ' hashing without salt, cipher block chaining (CBC) mode, SHA-1
    ' hashing algorithm, and 4-to-8 byte long salt.
    ' </summary>
    ' <param name="passPhrase">
    ' Passphrase from which a pseudo-random password will be derived.
    ' The derived password will be used to generate the encryption key.
    ' Passphrase can be any string. In this example we assume that the
    ' passphrase is an ASCII string. Passphrase value must be kept in
    ' secret.
    ' </param>
    ' <param name="initVector">
    ' Initialization vector (IV). This value is required to encrypt the
    ' first block of plaintext data. For RijndaelManaged class IV must be
    ' exactly 16 ASCII characters long. IV value does not have to be kept
    ' in secret.
    ' </param>
    Public Sub New(passPhrase  As String, _
                   initVector  As String)
        Me.New(passPhrase, initVector, -1)
    End Sub
 
    ' <summary>
    ' Use this constructor if you are planning to perform encryption/
    ' decryption with 256-bit key, derived using 1 password iteration,
    ' hashing without salt, cipher block chaining (CBC) mode, SHA-1 
    ' hashing algorithm, and 0-to-8 byte long salt.
    ' </summary>
    ' <param name="passPhrase">
    ' Passphrase from which a pseudo-random password will be derived.
    ' The derived password will be used to generate the encryption key
    ' Passphrase can be any string. In this example we assume that the
    ' passphrase is an ASCII string. Passphrase value must be kept in
    ' secret.
    ' </param>
    ' <param name="initVector">
    ' Initialization vector (IV). This value is required to encrypt the
    ' first block of plaintext data. For RijndaelManaged class IV must be
    ' exactly 16 ASCII characters long. IV value does not have to be kept
    ' in secret.
    ' </param>
    ' <param name="minSaltLen">
    ' Min size (in bytes) of randomly generated salt which will be added at
    ' the beginning of plain text before encryption is performed. When this
    ' value is less than 4, the default min value will be used (currently 4
    ' bytes).
    ' </param>
    Public Sub New(passPhrase  As String, _
                   initVector  As String, _
                   minSaltLen  As Integer)
        Me.New(passPhrase, initVector, minSaltLen, -1)
    End Sub
 
    ' <summary>
    ' Use this constructor if you are planning to perform encryption/
    ' decryption with 256-bit key, derived using 1 password iteration,
    ' hashing without salt, cipher block chaining (CBC) mode, SHA-1
    ' hashing algorithm. Use the minSaltLen and maxSaltLen parameters to
    ' specify the size of randomly generated salt.
    ' </summary>
    ' <param name="passPhrase">
    ' Passphrase from which a pseudo-random password will be derived.
    ' The derived password will be used to generate the encryption key.
    ' Passphrase can be any string. In this example we assume that the
    ' passphrase is an ASCII string. Passphrase value must be kept in
    ' secret.
    ' </param>
    ' <param name="initVector">
    ' Initialization vector (IV). This value is required to encrypt the
    ' first block of plaintext data. For RijndaelManaged class IV must be
    ' exactly 16 ASCII characters long. IV value does not have to be kept
    ' in secret.
    ' </param>
    ' <param name="minSaltLen">
    ' Min size (in bytes) of randomly generated salt which will be added at
    ' the beginning of plain text before encryption is performed. When this
    ' value is less than 4, the default min value will be used (currently 4
    ' bytes).
    ' </param>
    ' <param name="maxSaltLen">
    ' Max size (in bytes) of randomly generated salt which will be added at
    ' the beginning of plain text before encryption is performed. When this
    ' value is negative or greater than 255, the default max value will be
    ' used (currently 8 bytes). If max value is 0 (zero) or if it is smaller
    ' than the specified min value (which can be adjusted to default value),
    ' salt will not be used and plain text value will be encrypted as is.
    ' In this case, salt will not be processed during decryption either.
    ' </param>
    Public Sub New(passPhrase  As String,  _
                   initVector  As String,  _
                   minSaltLen  As Integer, _
                   maxSaltLen  As Integer)
        Me.New(passPhrase, initVector, minSaltLen, maxSaltLen, -1)
    End Sub
 
    ' <summary>
    ' Use this constructor if you are planning to perform encryption/
    ' decryption using the key derived from 1 password iteration,
    ' hashing without salt, cipher block chaining (CBC) mode, and
    ' SHA-1 hashing algorithm.
    ' </summary>
    ' <param name="passPhrase">
    ' Passphrase from which a pseudo-random password will be derived.
    ' The derived password will be used to generate the encryption key.
    ' Passphrase can be any string. In this example we assume that the
    ' passphrase is an ASCII string. Passphrase value must be kept in
    ' secret.
    ' </param>
    ' <param name="initVector">
    ' Initialization vector (IV). This value is required to encrypt the
    ' first block of plaintext data. For RijndaelManaged class IV must be
    ' exactly 16 ASCII characters long. IV value does not have to be kept
    ' in secret.
    ' </param>
    ' <param name="minSaltLen">
    ' Min size (in bytes) of randomly generated salt which will be added at
    ' the beginning of plain text before encryption is performed. When this
    ' value is less than 4, the default min value will be used (currently 4
    ' bytes).
    ' </param>
    ' <param name="maxSaltLen">
    ' Max size (in bytes) of randomly generated salt which will be added at
    ' the beginning of plain text before encryption is performed. When this
    ' value is negative or greater than 255, the default max value will be 
    ' used (currently 8 bytes). If max value is 0 (zero) or if it is smaller
    ' than the specified min value (which can be adjusted to default value),
    ' salt will not be used and plain text value will be encrypted as is.
    ' In this case, salt will not be processed during decryption either.
    ' </param>
    ' <param name="keySize">
    ' Size of symmetric key (in bits): 128, 192, or 256.
    ' </param>
    Public Sub New(passPhrase  As String,  _
                   initVector  As String,  _
                   minSaltLen  As Integer, _
                   maxSaltLen  As Integer, _
                   keySize     As Integer)
        Me.New(passPhrase, initVector, minSaltLen, maxSaltLen, keySize, _
               Nothing)
    End Sub
 
    ' <summary>
    ' Use this constructor if you are planning to perform encryption/
    ' decryption using the key derived from 1 password iteration, hashing 
    ' without salt, and cipher block chaining (CBC) mode.
    ' </summary>
    ' <param name="passPhrase">
    ' Passphrase from which a pseudo-random password will be derived.
    ' The derived password will be used to generate the encryption key.
    ' Passphrase can be any string. In this example we assume that the
    ' passphrase is an ASCII string. Passphrase value must be kept in
    ' secret.
    ' </param>
    ' <param name="initVector">
    ' Initialization vector (IV). This value is required to encrypt the
    ' first block of plaintext data. For RijndaelManaged class IV must be
    ' exactly 16 ASCII characters long. IV value does not have to be kept
    ' in secret.
    ' </param>
    ' <param name="minSaltLen">
    ' Min size (in bytes) of randomly generated salt which will be added at
    ' the beginning of plain text before encryption is performed. When this
    ' value is less than 4, the default min value will be used (currently 4
    ' bytes).
    ' </param>
    ' <param name="maxSaltLen">
    ' Max size (in bytes) of randomly generated salt which will be added at
    ' the beginning of plain text before encryption is performed. When this
    ' value is negative or greater than 255, the default max value will be
    ' used (currently 8 bytes). If max value is 0 (zero) or if it is smaller
    ' than the specified min value (which can be adjusted to default value),
    ' salt will not be used and plain text value will be encrypted as is.
    ' In this case, salt will not be processed during decryption either.
    ' </param>
    ' <param name="keySize">
    ' Size of symmetric key (in bits): 128, 192, or 256.
    ' </param>
    ' <param name="hashAlgorithm">
    ' Hashing algorithm: "MD5" or "SHA1". SHA1 is recommended.
    ' </param>
    Public Sub New(passPhrase      As String,  _
                   initVector      As String,  _
                   minSaltLen      As Integer, _
                   maxSaltLen      As Integer, _
                   keySize         As Integer, _
                   hashAlgorithm   As String)
        Me.New(passPhrase, initVector, minSaltLen, maxSaltLen, keySize, _
               hashAlgorithm, Nothing)
    End Sub
 
    ' <summary>
    ' Use this constructor if you are planning to perform encryption/
    ' decryption using the key derived from 1 password iteration, and
    ' cipher block chaining (CBC) mode.
    ' </summary>
    ' <param name="passPhrase">
    ' Passphrase from which a pseudo-random password will be derived.
    ' The derived password will be used to generate the encryption key.
    ' Passphrase can be any string. In this example we assume that the
    ' passphrase is an ASCII string. Passphrase value must be kept in
    ' secret.
    ' </param>
    ' <param name="initVector">
    ' Initialization vector (IV). This value is required to encrypt the
    ' first block of plaintext data. For RijndaelManaged class IV must be
    ' exactly 16 ASCII characters long. IV value does not have to be kept
    ' in secret.
    ' </param>
    ' <param name="minSaltLen">
    ' Min size (in bytes) of randomly generated salt which will be added at
    ' the beginning of plain text before encryption is performed. When this
    ' value is less than 4, the default min value will be used (currently 4
    ' bytes).
    ' </param>
    ' <param name="maxSaltLen">
    ' Max size (in bytes) of randomly generated salt which will be added at
    ' the beginning of plain text before encryption is performed. When this
    ' value is negative or greater than 255, the default max value will be
    ' used (currently 8 bytes). If max value is 0 (zero) or if it is smaller
    ' than the specified min value (which can be adjusted to default value),
    ' salt will not be used and plain text value will be encrypted as is.
    ' In this case, salt will not be processed during decryption either.
    ' </param>
    ' <param name="keySize">
    ' Size of symmetric key (in bits): 128, 192, or 256.
    ' </param>
    ' <param name="hashAlgorithm">
    ' Hashing algorithm: "MD5" or "SHA1". SHA1 is recommended.
    ' </param>
    ' <param name="saltValue">
    ' Salt value used for password hashing during key generation. This is
    ' not the same as the salt we will use during encryption. This parameter
    ' can be any string.
    ' </param>
    Public Sub New(passPhrase      As String,  _
                   initVector      As String,  _
                   minSaltLen      As Integer, _
                   maxSaltLen      As Integer, _
                   keySize         As Integer, _
                   hashAlgorithm   As String,  _
                   saltValue       As String)
        Me.New(passPhrase, initVector, minSaltLen, maxSaltLen, keySize, _
               hashAlgorithm, saltValue, 1)
    End Sub
 
    ' <summary>
    ' Use this constructor if you are planning to perform encryption/
    ' decryption with the key derived from the explicitly specified
    ' parameters.
    ' </summary>
    ' <param name="passPhrase">
    ' Passphrase from which a pseudo-random password will be derived.
    ' The derived password will be used to generate the encryption key
    ' Passphrase can be any string. In this example we assume that the
    ' passphrase is an ASCII string. Passphrase value must be kept in
    ' secret.
    ' </param>
    ' <param name="initVector">
    ' Initialization vector (IV). This value is required to encrypt the
    ' first block of plaintext data. For RijndaelManaged class IV must be
    ' exactly 16 ASCII characters long. IV value does not have to be kept
    ' in secret.
    ' </param>
    ' <param name="minSaltLen">
    ' Min size (in bytes) of randomly generated salt which will be added at
    ' the beginning of plain text before encryption is performed. When this
    ' value is less than 4, the default min value will be used (currently 4
    ' bytes).
    ' </param>
    ' <param name="maxSaltLen">
    ' Max size (in bytes) of randomly generated salt which will be added at
    ' the beginning of plain text before encryption is performed. When this
    ' value is negative or greater than 255, the default max value will be
    ' used (currently 8 bytes). If max value is 0 (zero) or if it is smaller
    ' than the specified min value (which can be adjusted to default value),
    ' salt will not be used and plain text value will be encrypted as is.
    ' In this case, salt will not be processed during decryption either.
    ' </param>
    ' <param name="keySize">
    ' Size of symmetric key (in bits): 128, 192, or 256.
    ' </param>
    ' <param name="hashAlgorithm">
    ' Hashing algorithm: "MD5" or "SHA1". SHA1 is recommended.
    ' </param>
    ' <param name="saltValue">
    ' Salt value used for password hashing during key generation. This is
    ' not the same as the salt we will use during encryption. This parameter
    ' can be any string.
    ' </param>
    ' <param name="passwordIterations">
    ' Number of iterations used to hash password. More iterations are
    ' considered more secure but may take longer.
    ' </param>
    Public Sub New(passPhrase          As String,  _
                   initVector          As String,  _
                   minSaltLen          As Integer, _
                   maxSaltLen          As Integer, _
                   keySize             As Integer, _
                   hashAlgorithm       As String,  _
                   saltValue           As String,  _
                   passwordIterations  As Integer)
 
        ' Save min salt length; set it to default if invalid value is passed.
        If (minSaltLen < MIN_ALLOWED_SALT_LEN) Then
            Me.minSaltLen = DEFAULT_MIN_SALT_LEN
        Else
            Me.minSaltLen = minSaltLen
        End If
 
        ' Save max salt length; set it to default if invalid value is passed.
        If (maxSaltLen < 0 Or maxSaltLen > MAX_ALLOWED_SALT_LEN) Then
            Me.maxSaltLen = DEFAULT_MAX_SALT_LEN
        Else
            Me.maxSaltLen = maxSaltLen
        End If
 
        ' Set the size of cryptographic key.
        If (keySize <= 0) Then
            keySize = DEFAULT_KEY_SIZE
        End If
 
        ' Set the name of algorithm. Make sure it is in UPPER CASE and does
        ' not use dashes, e.g. change "sha-1" to "SHA1".
        If (hashAlgorithm Is Nothing) Then
            hashAlgorithm = DEFAULT_HASH_ALGORITHM
        Else
            hashAlgorithm = hashAlgorithm.ToUpper().Replace("-", "")
        End If
 
        ' Initialization vector converted to a byte array.
        Dim initVectorBytes() As Byte = Nothing
 
        ' Salt used for password hashing (to generate the key, not during
        ' encryption) converted to a byte array.
        Dim saltValueBytes() As Byte = Nothing
 
        ' Get bytes of initialization vector.
        If (initVector Is Nothing) Then
            initVectorBytes = New Byte(){}
        Else
            initVectorBytes = Encoding.ASCII.GetBytes(initVector)
        End If
 
        ' Get bytes of salt (used in hashing).
        If(saltValue Is Nothing) Then
            saltValueBytes = New Byte(){}
        Else
            saltValueBytes = Encoding.ASCII.GetBytes(saltValue)
        End If
 
        ' Generate password, which will be used to derive the key.
        Dim password As PasswordDeriveBytes = New PasswordDeriveBytes(  _
                                                    passPhrase,         _
                                                    saltValueBytes,     _
                                                    hashAlgorithm,      _
                                                    passwordIterations)
 
        ' Convert key to a byte array adjusting the size from bits to bytes.
        Dim keyBytes() As Byte = password.GetBytes(keySize / 8)
 
        ' Initialize Rijndael key object.
        Dim symmetricKey As RijndaelManaged = New RijndaelManaged()
 
        ' If we do not have initialization vector, we cannot use the CBC mode.
        ' The only alternative is the ECB mode (which is not as good).
        If (initVectorBytes.Length = 0) Then
            symmetricKey.Mode = CipherMode.ECB
        Else
            symmetricKey.Mode = CipherMode.CBC
        End If
 
        ' Create encryptor and decryptor, which we will use for cryptographic
        ' operations.
        encryptor = symmetricKey.CreateEncryptor(keyBytes, initVectorBytes)
        decryptor = symmetricKey.CreateDecryptor(keyBytes, initVectorBytes)
    End Sub
 
    ' <summary>
    ' Encrypts a string value generating a base64-encoded string.
    ' </summary>
    ' <param name="plainText">
    ' Plain text string to be encrypted.
    ' </param>
    ' <returns>
    ' Cipher text formatted as a base64-encoded string.
    ' </returns>
    Public Function Encrypt(plainText As String) As String
        Encrypt = Encrypt(Encoding.UTF8.GetBytes(plainText))
    End Function
 
    ' <summary>
    ' Encrypts a byte array generating a base64-encoded string.
    ' </summary>
    ' <param name="plainTextBytes">
    ' Plain text bytes to be encrypted.
    ' </param>
    ' <returns>
    ' Cipher text formatted as a base64-encoded string.
    ' </returns>
    Public Function Encrypt(plainTextBytes As Byte()) As String
        Encrypt = Convert.ToBase64String(EncryptToBytes(plainTextBytes))
    End Function
 
    ' <summary>
    ' Encrypts a string value generating a byte array of cipher text.
    ' </summary>
    ' <param name="plainText">
    ' Plain text string to be encrypted.
    ' </param>
    ' <returns>
    ' Cipher text formatted as a byte array.
    ' </returns>
    Public Function EncryptToBytes(plainText As String) As Byte()
        EncryptToBytes = EncryptToBytes(Encoding.UTF8.GetBytes(plainText))
    End Function
 
    ' <summary>
    ' Encrypts a byte array generating a byte array of cipher text.
    ' </summary>
    ' <param name="plainTextBytes">
    ' Plain text bytes to be encrypted.
    ' </param>
    ' <returns>
    ' Cipher text formatted as a byte array.
    ' </returns>
    Public Function EncryptToBytes(plainTextBytes As Byte()) As Byte()
 
        ' Add salt at the beginning of the plain text bytes (if needed).
        Dim plainTextBytesWithSalt() As Byte = AddSalt(plainTextBytes)
 
        ' Encryption will be performed using memory stream.
        Dim memoryStream As MemoryStream = New MemoryStream()
        Dim cryptoStream As CryptoStream = Nothing
 
        ' Let's make cryptographic operations thread-safe.
        SyncLock Me
            ' To perform encryption, we must use the Write mode.
            cryptoStream = New CryptoStream(memoryStream,   _
                                            encryptor,      _
                                            CryptoStreamMode.Write)
 
            ' Start encrypting data.
            cryptoStream.Write( plainTextBytesWithSalt, _ 
                                0,                      _
                                plainTextBytesWithSalt.Length)
             
            ' Finish the encryption operation.
            cryptoStream.FlushFinalBlock
 
            ' Move encrypted data from memory into a byte array.
            Dim cipherTextBytes() As Byte = memoryStream.ToArray()
               
            ' Close memory streams.
            memoryStream.Close
            cryptoStream.Close
 
            ' Return encrypted data.
            EncryptToBytes = cipherTextBytes
        End SyncLock
    End Function
 
    ' <summary>
    ' Decrypts a base64-encoded cipher text value generating a string result.
    ' </summary>
    ' <param name="cipherText">
    ' Base64-encoded cipher text string to be decrypted.
    ' </param>
    ' <returns>
    ' Decrypted string value.
    ' </returns>
    Public Function Decrypt(cipherText As String) As String
        Decrypt = Decrypt(Convert.FromBase64String(cipherText))
    End Function
 
    ' <summary>
    ' Decrypts a byte array containing cipher text value and generates a
    ' string result.
    ' </summary>
    ' <param name="cipherTextBytes">
    ' Byte array containing encrypted data.
    ' </param>
    ' <returns>
    ' Decrypted string value.
    ' </returns>
    Public Function Decrypt(cipherTextBytes As Byte()) As String
        Decrypt = Encoding.UTF8.GetString(DecryptToBytes(cipherTextBytes))
    End Function
 
    ' <summary>
    ' Decrypts a base64-encoded cipher text value and generates a byte array
    ' of plain text data.
    ' </summary>
    ' <param name="cipherText">
    ' Base64-encoded cipher text string to be decrypted.
    ' </param>
    ' <returns>
    ' Byte array containing decrypted value.
    ' </returns>
    Public Function DecryptToBytes(cipherText As String) As Byte()
        DecryptToBytes = DecryptToBytes(Convert.FromBase64String(cipherText))
    End Function
 
    ' <summary>
    ' Decrypts a base64-encoded cipher text value and generates a byte array
    ' of plain text data.
    ' </summary>
    ' <param name="cipherTextBytes">
    ' Byte array containing encrypted data.
    ' </param>
    ' <returns>
    ' Byte array containing decrypted value.
    ' </returns>
    Public Function DecryptToBytes(cipherTextBytes As Byte()) As Byte()
 
        Dim decryptedBytes()    As Byte   = Nothing
        Dim plainTextBytes()    As Byte   = Nothing
        Dim decryptedByteCount  As Integer  = 0
        Dim saltLen             As Integer  = 0
 
        Dim memoryStream As MemoryStream = New MemoryStream(cipherTextBytes)
 
        ' Since we do not know how big decrypted value will be, use the same
        ' size as cipher text. Cipher text is always longer than plain text
        ' (in block cipher encryption), so we will just use the number of
        ' decrypted data byte after we know how big it is.
        decryptedBytes = New Byte(cipherTextBytes.Length - 1){}
 
        ' Let's make cryptographic operations thread-safe.
        SyncLock Me
            ' To perform decryption, we must use the Read mode.
            Dim cryptoStream As CryptoStream = New CryptoStream(        _
                                                        memoryStream,   _
                                                        decryptor,      _
                                                        CryptoStreamMode.Read)
 
            ' Decrypting data and get the count of plain text bytes.
            decryptedByteCount  = cryptoStream.Read(decryptedBytes, _
                                                    0,              _
                                                    decryptedBytes.Length)
            ' Release memory.
            memoryStream.Close
            cryptoStream.Close
        End SyncLock
 
        ' If we are using salt, get its length from the first 4 bytes of plain
        ' text data.
        If (maxSaltLen > 0 And maxSaltLen >= minSaltLen) Then
            saltLen =   (decryptedBytes(0) And &H03) Or _
                        (decryptedBytes(1) And &H0c) Or _
                        (decryptedBytes(2) And &H30) Or _
                        (decryptedBytes(3) And &Hc0)
        End If
 
        ' Allocate the byte array to hold the original plain text
        ' (without salt).
        plainTextBytes = New Byte(decryptedByteCount - saltLen - 1){}
 
        ' Copy original plain text discarding the salt value if needed.
        Array.Copy(decryptedBytes, saltLen, plainTextBytes, _
                    0, decryptedByteCount - saltLen)
 
        ' Return original plain text value.
        DecryptToBytes = plainTextBytes
    End Function
 
    ' <summary>
    ' Adds an array of randomly generated bytes at the beginning of the
    ' array holding original plain text value.
    ' </summary>
    ' <param name="plainTextBytes">
    ' Byte array containing original plain text value.
    ' </param>
    ' <returns>
    ' Either original array of plain text bytes (if salt is not used) or a
    ' modified array containing a randomly generated salt added at the 
    ' beginning of the plain text bytes. 
    ' </returns>
    Private Function AddSalt(plainTextBytes As Byte()) As Byte()
 
        ' The max salt value of 0 (zero) indicates that we should not use 
        ' salt. Also do not use salt if the max salt value is smaller than
        ' the min value.
        If (maxSaltLen = 0 Or maxSaltLen < minSaltLen) Then
            AddSalt = plainTextBytes
            Exit Function
        End If
 
        ' Generate the salt.
        Dim saltBytes() As Byte = GenerateSalt()
 
        ' Allocate array which will hold salt and plain text bytes.
        Dim plainTextBytesWithSalt() As Byte = New Byte( _
                                                plainTextBytes.Length + _
                                                saltBytes.Length - 1){}
        ' First, copy salt bytes.
        Array.Copy(saltBytes, plainTextBytesWithSalt, saltBytes.Length)
 
        ' Append plain text bytes to the salt value.
        Array.Copy( plainTextBytes, 0, _
                    plainTextBytesWithSalt, saltBytes.Length, _
                    plainTextBytes.Length)
 
        AddSalt = plainTextBytesWithSalt
    End Function
 
    ' <summary>
    ' Generates an array holding cryptographically strong bytes.
    ' </summary>
    ' <returns>
    ' Array of randomly generated bytes.
    ' </returns>
    ' <remarks>
    ' Salt size will be defined at random or exactly as specified by the
    ' minSlatLen and maxSaltLen parameters passed to the object constructor.
    ' The first four bytes of the salt array will contain the salt length
    ' split into four two-bit pieces.
    ' </remarks>
    Private Function GenerateSalt() As Byte()
 
        ' We don't have the length, yet.
        Dim saltLen As Integer = 0
 
        ' If min and max salt values are the same, it should not be random.
        If (minSaltLen = maxSaltLen) Then
            saltLen = minSaltLen
        ' Use random number generator to calculate salt length.
        Else
            saltLen = GenerateRandomNumber(minSaltLen, maxSaltLen)
        End If
 
        ' Allocate byte array to hold our salt.
        Dim salt() As Byte = New Byte(saltLen - 1){}
 
        ' Populate salt with cryptographically strong bytes.
        Dim rng As RNGCryptoServiceProvider = New RNGCryptoServiceProvider()
 
        rng.GetNonZeroBytes(salt)
 
        ' Split salt length (always one byte) into four two-bit pieces and
        ' store these pieces in the first four bytes of the salt array.
        salt(0) = ((salt(0) And &Hfc) Or(saltLen And &H03))
        salt(1) = ((salt(1) And &Hf3) Or(saltLen And &H0c))
        salt(2) = ((salt(2) And &Hcf) Or(saltLen And &H30))
        salt(3) = ((salt(3) And &H3f) Or(saltLen And &Hc0))
 
        GenerateSalt = salt
    End Function
 
    ' <summary>
    ' Generates random integer.
    ' </summary>
    ' <param name="minValue">
    ' Min value (inclusive).
    ' </param>
    ' <param name="maxValue">
    ' Max value (inclusive).
    ' </param>
    ' <returns>
    ' Random integer value between the min and max values (inclusive).
    ' </returns>
    ' <remarks>
    ' This methods overcomes the limitations of .NET Framework's Random
    ' class, which - when initialized multiple times within a very short
    ' period of time - can generate the same "random" number.
    ' </remarks>
    Private Function GenerateRandomNumber(minValue As Integer, _
                                          maxValue As Integer) As Integer
 
        ' We will make up an integer seed from 4 bytes of this array.
        Dim randomBytes() As Byte = New Byte(3){}
 
        ' Generate 4 random bytes.
        Dim rng As RNGCryptoServiceProvider = New RNGCryptoServiceProvider()
        rng.GetBytes(randomBytes)
 
        ' Convert four random bytes into a positive integer value.
        Dim seed As Integer =  ((randomBytes(0) And &H7f) << 24) Or _
                                (randomBytes(1)           << 16) Or _
                                (randomBytes(2)           <<  8) Or _
                                (randomBytes(3))
 
        ' Now, this looks more like real randomization.
        Dim random As Random = New Random(seed)
 
        ' Calculate a random number.
        GenerateRandomNumber = random.Next(minValue, maxValue+1)
  End Function
 
End Class
 
' <summary>
' The main entry point for the application.
' </summary>
Sub Main()
    Dim plainText  As String = "Hello, World!"    ' original plaintext
    Dim cipherText As String = ""                 ' encrypted text
    Dim passPhrase As String = "Pas5pr@se"        ' can be any string
    Dim initVector As String = "@1B2c3D4e5F6g7H8" ' must be 16 bytes
 
    ' Before encrypting data, we will append plain text to a random
    ' salt value, which will be between 4 and 8 bytes long (implicitly
    ' used defaults).
    Dim rijndaelKey As RijndaelEnhanced = _
        New RijndaelEnhanced(passPhrase, initVector)
 
    Console.WriteLine(String.Format("Plaintext   : {0}{1}", plainText, vbCrLf))
 
    ' Encrypt the same plain text data 10 time (using the same key,
    ' initialization vector, etc) and see the resulting cipher text;
    ' encrypted values will be different.
    Dim I As Integer
    For I=0 To 9
        cipherText = rijndaelKey.Encrypt(plainText)
        Console.WriteLine( _
            String.Format("Encrypted #{0}: {1}", i, cipherText))
        plainText = rijndaelKey.Decrypt(cipherText)
    Next
 
    ' Make sure we got decryption working correctly.
    Console.WriteLine(String.Format("{0}Decrypted   : {1}", vbCrLf, plainText))
End Sub
 
End Module
'
' END OF FILE
'------------------------------------------------------------------------------

Back to top

Program output

Plaintext   : Hello, World!

Encrypted #0: aZrQkBMKK98tnjddY/AUHxDeNlutylhcZ/ADWpNrJ38=
Encrypted #1: HhN01vcEEtMmwdNFliM8QYg+Y89xzBOJJG+BH/ARC7g=
Encrypted #2: uabeD7m8GdB9Kqm8tLI62zvkGN6Jf2+E0VtOJfRQoRU=
Encrypted #3: rtgB+F7ol9fEKRu/nm91qX3ZZSeHqKUFyK7rLiLDpAs=
Encrypted #4: MRuYU16m41MElUKPs4LZBxE5Mw8G9VlSf2Q0EKSXmT4=
Encrypted #5: UAHWYyVmdwQj4NHK5y0r6KdRe/98OlEmJnTLb3Gn3yQ=
Encrypted #6: TUpHFCiE6NYkp+NIwW9b/hGEEgv8V5IfI8i0yip9tEo=
Encrypted #7: 1B9x2aQ8aoQgSnxv6561fPr7x36RooNFtZJD7xhnVm0=
Encrypted #8: SM+mAj0AWHfgBNdPM4lm7wV5t/PfJxxNvkWkcb1WS5g=
Encrypted #9: GGKnT87PFOCXALr5rX4wiU3xkhFZym1sLQqGZZTANzE=

Decrypted   : Hello, World!

Back to top