C# code
///////////////////////////////////////////////////////////////////////////////
// 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.
//
//
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
///////////////////////////////////////////////////////////////////////////////
'------------------------------------------------------------------------------
' 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
'------------------------------------------------------------------------------
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!
With a couple of projects that
I've been given recently, I needed to add some encryption. Luckily I had a
utility class laying around that implemented AES (otherwise known as Rijndael).
Advanced Encryption Standard is an encryption standard adopted by the US Govt that is
approved by the NSA. The great thing about it though is that it's incredibly
easy to implement (about 24 lines of code to encrypt, 23 to decrypt). And since
you're here I'm fairly sure that you want to know how to do just that:
///
<summary>
/// Utility class that handles encryption
/// </summary>
public static class AESEncryption
{
#region Static Functions
/// <summary>
/// Encrypts a string
/// </summary>
/// <param name="PlainText">Text to be encrypted</param>
/// <param name="Password">Password to encrypt with</param>
/// <param name="Salt">Salt to encrypt with</param>
/// <param name="HashAlgorithm">Can be either SHA1 or MD5</param>
/// <param name="PasswordIterations">Number of iterations to do</param>
/// <param name="InitialVector">Needs to be 16 ASCII characters long</param>
/// <param name="KeySize">Can be 128, 192, or 256</param>
/// <returns>An encrypted string</returns>
public static string Encrypt(string PlainText, string Password,
string Salt, string HashAlgorithm,
int PasswordIterations, string InitialVector,
int KeySize)
{
try
{
if (string.IsNullOrEmpty(PlainText))
return "";
byte[] InitialVectorBytes = Encoding.ASCII.GetBytes(InitialVector);
byte[] SaltValueBytes = Encoding.ASCII.GetBytes(Salt);
byte[] PlainTextBytes = Encoding.UTF8.GetBytes(PlainText);
PasswordDeriveBytes DerivedPassword = new PasswordDeriveBytes(Password, SaltValueBytes, HashAlgorithm, PasswordIterations);
byte[] KeyBytes = DerivedPassword.GetBytes(KeySize / 8);
RijndaelManaged SymmetricKey = new RijndaelManaged();
SymmetricKey.Mode = CipherMode.CBC;
byte[] CipherTextBytes = null;
using (ICryptoTransform Encryptor = SymmetricKey.CreateEncryptor(KeyBytes, InitialVectorBytes))
{
using (MemoryStream MemStream = new MemoryStream())
{
using (CryptoStream CryptoStream = new CryptoStream(MemStream, Encryptor, CryptoStreamMode.Write))
{
CryptoStream.Write(PlainTextBytes, 0, PlainTextBytes.Length);
CryptoStream.FlushFinalBlock();
CipherTextBytes = MemStream.ToArray();
MemStream.Close();
CryptoStream.Close();
}
}
}
SymmetricKey.Clear();
return Convert.ToBase64String(CipherTextBytes);
}
catch { throw; }
}
/// <summary>
/// Decrypts a string
/// </summary>
/// <param name="CipherText">Text to be decrypted</param>
/// <param name="Password">Password to decrypt with</param>
/// <param name="Salt">Salt to decrypt with</param>
/// <param name="HashAlgorithm">Can be either SHA1 or MD5</param>
/// <param name="PasswordIterations">Number of iterations to do</param>
/// <param name="InitialVector">Needs to be 16 ASCII characters long</param>
/// <param name="KeySize">Can be 128, 192, or 256</param>
/// <returns>A decrypted string</returns>
public static string Decrypt(string CipherText, string Password,
string Salt, string HashAlgorithm,
int PasswordIterations, string InitialVector,
int KeySize)
{
try
{
if (string.IsNullOrEmpty(CipherText))
return "";
byte[] InitialVectorBytes = Encoding.ASCII.GetBytes(InitialVector);
byte[] SaltValueBytes = Encoding.ASCII.GetBytes(Salt);
byte[] CipherTextBytes = Convert.FromBase64String(CipherText);
PasswordDeriveBytes DerivedPassword = new PasswordDeriveBytes(Password, SaltValueBytes, HashAlgorithm, PasswordIterations);
byte[] KeyBytes = DerivedPassword.GetBytes(KeySize / 8);
RijndaelManaged SymmetricKey = new RijndaelManaged();
SymmetricKey.Mode = CipherMode.CBC;
byte[] PlainTextBytes = new byte[CipherTextBytes.Length];
int ByteCount = 0;
using (ICryptoTransform Decryptor = SymmetricKey.CreateDecryptor(KeyBytes, InitialVectorBytes))
{
using (MemoryStream MemStream = new MemoryStream(CipherTextBytes))
{
using (CryptoStream CryptoStream = new CryptoStream(MemStream, Decryptor, CryptoStreamMode.Read))
{
ByteCount = CryptoStream.Read(PlainTextBytes, 0, PlainTextBytes.Length);
MemStream.Close();
CryptoStream.Close();
}
}
}
SymmetricKey.Clear();
return Encoding.UTF8.GetString(PlainTextBytes, 0, ByteCount);
}
catch { throw; }
}
#endregion
}
/// Utility class that handles encryption
/// </summary>
public static class AESEncryption
{
#region Static Functions
/// <summary>
/// Encrypts a string
/// </summary>
/// <param name="PlainText">Text to be encrypted</param>
/// <param name="Password">Password to encrypt with</param>
/// <param name="Salt">Salt to encrypt with</param>
/// <param name="HashAlgorithm">Can be either SHA1 or MD5</param>
/// <param name="PasswordIterations">Number of iterations to do</param>
/// <param name="InitialVector">Needs to be 16 ASCII characters long</param>
/// <param name="KeySize">Can be 128, 192, or 256</param>
/// <returns>An encrypted string</returns>
public static string Encrypt(string PlainText, string Password,
string Salt, string HashAlgorithm,
int PasswordIterations, string InitialVector,
int KeySize)
{
try
{
if (string.IsNullOrEmpty(PlainText))
return "";
byte[] InitialVectorBytes = Encoding.ASCII.GetBytes(InitialVector);
byte[] SaltValueBytes = Encoding.ASCII.GetBytes(Salt);
byte[] PlainTextBytes = Encoding.UTF8.GetBytes(PlainText);
PasswordDeriveBytes DerivedPassword = new PasswordDeriveBytes(Password, SaltValueBytes, HashAlgorithm, PasswordIterations);
byte[] KeyBytes = DerivedPassword.GetBytes(KeySize / 8);
RijndaelManaged SymmetricKey = new RijndaelManaged();
SymmetricKey.Mode = CipherMode.CBC;
byte[] CipherTextBytes = null;
using (ICryptoTransform Encryptor = SymmetricKey.CreateEncryptor(KeyBytes, InitialVectorBytes))
{
using (MemoryStream MemStream = new MemoryStream())
{
using (CryptoStream CryptoStream = new CryptoStream(MemStream, Encryptor, CryptoStreamMode.Write))
{
CryptoStream.Write(PlainTextBytes, 0, PlainTextBytes.Length);
CryptoStream.FlushFinalBlock();
CipherTextBytes = MemStream.ToArray();
MemStream.Close();
CryptoStream.Close();
}
}
}
SymmetricKey.Clear();
return Convert.ToBase64String(CipherTextBytes);
}
catch { throw; }
}
/// <summary>
/// Decrypts a string
/// </summary>
/// <param name="CipherText">Text to be decrypted</param>
/// <param name="Password">Password to decrypt with</param>
/// <param name="Salt">Salt to decrypt with</param>
/// <param name="HashAlgorithm">Can be either SHA1 or MD5</param>
/// <param name="PasswordIterations">Number of iterations to do</param>
/// <param name="InitialVector">Needs to be 16 ASCII characters long</param>
/// <param name="KeySize">Can be 128, 192, or 256</param>
/// <returns>A decrypted string</returns>
public static string Decrypt(string CipherText, string Password,
string Salt, string HashAlgorithm,
int PasswordIterations, string InitialVector,
int KeySize)
{
try
{
if (string.IsNullOrEmpty(CipherText))
return "";
byte[] InitialVectorBytes = Encoding.ASCII.GetBytes(InitialVector);
byte[] SaltValueBytes = Encoding.ASCII.GetBytes(Salt);
byte[] CipherTextBytes = Convert.FromBase64String(CipherText);
PasswordDeriveBytes DerivedPassword = new PasswordDeriveBytes(Password, SaltValueBytes, HashAlgorithm, PasswordIterations);
byte[] KeyBytes = DerivedPassword.GetBytes(KeySize / 8);
RijndaelManaged SymmetricKey = new RijndaelManaged();
SymmetricKey.Mode = CipherMode.CBC;
byte[] PlainTextBytes = new byte[CipherTextBytes.Length];
int ByteCount = 0;
using (ICryptoTransform Decryptor = SymmetricKey.CreateDecryptor(KeyBytes, InitialVectorBytes))
{
using (MemoryStream MemStream = new MemoryStream(CipherTextBytes))
{
using (CryptoStream CryptoStream = new CryptoStream(MemStream, Decryptor, CryptoStreamMode.Read))
{
ByteCount = CryptoStream.Read(PlainTextBytes, 0, PlainTextBytes.Length);
MemStream.Close();
CryptoStream.Close();
}
}
}
SymmetricKey.Clear();
return Encoding.UTF8.GetString(PlainTextBytes, 0, ByteCount);
}
catch { throw; }
}
#endregion
}
It's pretty straight forward, but if
you want to use them it would probably help to know the following:
- HashAlgorithm can be SHA1 or MD5.
- InitialVector should be a string of 16 ASCII
characters.
- KeySize can be 128, 192, or 256.
- The Salt string really acts as a second password.
- PasswordIterations is the number of times the algorithm
is run on the text.
Other than that, go crazy with the
inputs. In otherwords, when using the code it would look something like this:
string
FinalValue=AESEncryption.Encrypt("My Text","My
Password","Salt or
Password2","SHA1",2,"16CHARSLONG12345",256);
Tuesday, February 3, 2009
How To: Encrypt and Decrypt string Using C#
I think Any Developer need to protect his data from nasty peopleEx. We will assume we have an application (windows application – web application ….etc) will connect to
Active directory or Team Foundation Server or any application need authentication to connect to it
First thing any developer should thinking about how I made my application secured
So when we pass the credentials will pass it Encrypted and from another side will decrypt these credentials.
In this article I’ll show you how to encrypt any string and decrypt it.
Create a new windows application using C# the design will be something like the below image:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Security.Cryptography;
using System.IO;
namespace RsaEncryption
{
public partial class Main : Form
{
public Main()
{
InitializeComponent();
}
private static string sKey = "UJYHCX783her*&5@$%#(MJCX**38n*#6835ncv56tvbry(&#MX98cn342cn4*&X#&";
public static string Encrypt(string sPainText)
{
if (sPainText.Length == 0)
return (sPainText);
return (EncryptString(sPainText, sKey));
}
public static string Decrypt(string sEncryptText)
{
if (sEncryptText.Length == 0)
return (sEncryptText);
return (DecryptString(sEncryptText, sKey));
}
protected static string EncryptString(string InputText, string Password)
{
// "Password" string variable is nothing but the key(your secret key) value which is sent from the front end.
// "InputText" string variable is the actual password sent from the login page.
// We are now going to create an instance of the
// Rihndael class.
RijndaelManaged RijndaelCipher = new RijndaelManaged();
// First we need to turn the input strings into a byte array.
byte[] PlainText = System.Text.Encoding.Unicode.GetBytes(InputText);
// We are using Salt to make it harder to guess our key
// using a dictionary attack.
byte[] Salt = Encoding.ASCII.GetBytes(Password.Length.ToString());
// The (Secret Key) will be generated from the specified
// password and Salt.
//PasswordDeriveBytes -- It Derives a key from a password
PasswordDeriveBytes SecretKey = new PasswordDeriveBytes(Password, Salt);
// Create a encryptor from the existing SecretKey bytes.
// We use 32 bytes for the secret key
// (the default Rijndael key length is 256 bit = 32 bytes) and
// then 16 bytes for the IV (initialization vector),
// (the default Rijndael IV length is 128 bit = 16 bytes)
ICryptoTransform Encryptor = RijndaelCipher.CreateEncryptor(SecretKey.GetBytes(16), SecretKey.GetBytes(16));
// Create a MemoryStream that is going to hold the encrypted bytes
MemoryStream memoryStream = new MemoryStream();
// Create a CryptoStream through which we are going to be processing our data.
// CryptoStreamMode.Write means that we are going to be writing data
// to the stream and the output will be written in the MemoryStream
// we have provided. (always use write mode for encryption)
CryptoStream cryptoStream = new CryptoStream(memoryStream, Encryptor, CryptoStreamMode.Write);
// Start the encryption process.
cryptoStream.Write(PlainText, 0, PlainText.Length);
// Finish encrypting.
cryptoStream.FlushFinalBlock();
// Convert our encrypted data from a memoryStream into a byte array.
byte[] CipherBytes = memoryStream.ToArray();
// Close both streams.
memoryStream.Close();
cryptoStream.Close();
// Convert encrypted data into a base64-encoded string.
// A common mistake would be to use an Encoding class for that.
// It does not work, because not all byte values can be
// represented by characters. We are going to be using Base64 encoding
// That is designed exactly for what we are trying to do.
string EncryptedData = Convert.ToBase64String(CipherBytes);
// Return encrypted string.
return EncryptedData;
}
protected static string DecryptString(string InputText, string Password)
{
try
{
RijndaelManaged RijndaelCipher = new RijndaelManaged();
byte[] EncryptedData = Convert.FromBase64String(InputText);
byte[] Salt = Encoding.ASCII.GetBytes(Password.Length.ToString());
PasswordDeriveBytes SecretKey = new PasswordDeriveBytes(Password, Salt);
// Create a decryptor from the existing SecretKey bytes.
ICryptoTransform Decryptor = RijndaelCipher.CreateDecryptor(SecretKey.GetBytes(16), SecretKey.GetBytes(16));
MemoryStream memoryStream = new MemoryStream(EncryptedData);
// Create a CryptoStream. (always use Read mode for decryption).
CryptoStream cryptoStream = new CryptoStream(memoryStream, Decryptor, CryptoStreamMode.Read);
// Since at this point we don't know what the size of decrypted data
// will be, allocate the buffer long enough to hold EncryptedData;
// DecryptedData is never longer than EncryptedData.
byte[] PlainText = new byte[EncryptedData.Length];
// Start decrypting.
int DecryptedCount = cryptoStream.Read(PlainText, 0, PlainText.Length);
memoryStream.Close();
cryptoStream.Close();
// Convert decrypted data into a string.
string DecryptedData = Encoding.Unicode.GetString(PlainText, 0, DecryptedCount);
// Return decrypted string.
return DecryptedData;
}
catch (Exception exception)
{
return (exception.Message);
}
}
private void btnEncrypt_Click(object sender, EventArgs e)
{
txtEncryption.Text= Encrypt(txtPassword.Text);
}
private void btnDecrypt_Click(object sender, EventArgs e)
{
MessageBox.Show(Decrypt(txtEncryption.Text));
}
}
}
No comments:
Post a Comment