Core Encryption Code
using System.Security.Cryptography;
using System.IO;
Encryption
public byte[] AES_Encrypt(byte[] bytesToBeEncrypted, byte[] passwordBytes)
{
byte[] encryptedBytes = null;
byte[] saltBytes = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 };
using (MemoryStream ms = new MemoryStream())
{
using (RijndaelManaged AES = new RijndaelManaged())
{
AES.KeySize = 256;
AES.BlockSize = 128;
var key = new Rfc2898DeriveBytes(passwordBytes, saltBytes, 1000);
AES.Key = key.GetBytes(AES.KeySize / 8);
AES.IV = key.GetBytes(AES.BlockSize / 8);
AES.Mode = CipherMode.CBC;
using (var cs = new CryptoStream(ms, AES.CreateEncryptor(), CryptoStreamMode.Write))
{
cs.Write(bytesToBeEncrypted, 0, bytesToBeEncrypted.Length);
cs.Close();
}
encryptedBytes = ms.ToArray();
}
}
return encryptedBytes;
}
Decryption
public byte[] AES_Decrypt(byte[] bytesToBeDecrypted, byte[] passwordBytes)
{
byte[] decryptedBytes = null;
byte[] saltBytes = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 };
using (MemoryStream ms = new MemoryStream())
{
using (RijndaelManaged AES = new RijndaelManaged())
{
AES.KeySize = 256;
AES.BlockSize = 128;
var key = new Rfc2898DeriveBytes(passwordBytes, saltBytes, 1000);
AES.Key = key.GetBytes(AES.KeySize / 8);
AES.IV = key.GetBytes(AES.BlockSize / 8);
AES.Mode = CipherMode.CBC;
using (var cs = new CryptoStream(ms, AES.CreateDecryptor(), CryptoStreamMode.Write))
{
cs.Write(bytesToBeDecrypted, 0, bytesToBeDecrypted.Length);
cs.Close();
}
decryptedBytes = ms.ToArray();
}
}
return decryptedBytes;
}
Example of Encrypting String
Encrypt String
public string EncryptText(string input, string password)
{
byte[] bytesToBeEncrypted = Encoding.UTF8.GetBytes(input);
byte[] passwordBytes = Encoding.UTF8.GetBytes(password);
passwordBytes = SHA256.Create().ComputeHash(passwordBytes);
byte[] bytesEncrypted = AES_Encrypt(bytesToBeEncrypted, passwordBytes);
string result = Convert.ToBase64String(bytesEncrypted);
return result;
}
Decrypt String
public string DecryptText(string input, string password)
{
byte[] bytesToBeDecrypted = Convert.FromBase64String(input);
byte[] passwordBytes = Encoding.UTF8.GetBytes(password);
passwordBytes = SHA256.Create().ComputeHash(passwordBytes);
byte[] bytesDecrypted = AES_Decrypt(bytesToBeDecrypted, passwordBytes);
string result = Encoding.UTF8.GetString(bytesDecrypted);
return result;
}
You'll notice that the encrypted string is stored in base64 encoded mode. For those who is not familiar with base64 encoding, you may want to read Base What? A Practical Introduction to Base Encoding[^]
Getting Randomized Encryption Result with Salt
If we encrypt the same context (i.e. string of "Hello World") for 10 times, the encrypted results will be the same. What if we want the results different from each time it is encrypted?
What I do is appending a random salt bytes in front of the original bytes before encryption, and remove it after decryption.
Example of Appending Randomized Salt Before Encrypting a String
public static string EncryptString(string text, string password)
{
byte[] baPwd = Encoding.UTF8.GetBytes(password);
byte[] baPwdHash = SHA256Managed.Create().ComputeHash(baPwd);
byte[] baText = Encoding.UTF8.GetBytes(text);
byte[] baSalt = GetRandomBytes();
byte[] baEncrypted = new byte[baSalt.Length + baText.Length];
for (int i = 0; i < baSalt.Length; i++)
baEncrypted[i] = baSalt[i];
for (int i = 0; i < baText.Length; i++)
baEncrypted[i + baSalt.Length] = baText[i];
baEncrypted = AES_Encrypt(baEncrypted, baPwdHash);
string result = Convert.ToBase64String(baEncrypted);
return result;
}
Example of Removing the Salt after Decryption
public static string DecryptString(string text, string password)
{
byte[] baPwd = Encoding.UTF8.GetBytes(password);
byte[] baPwdHash = SHA256Managed.Create().ComputeHash(baPwd);
byte[] baText = Convert.FromBase64String(text);
byte[] baDecrypted = AES_Decrypt(baText, baPwdHash);
int saltLength = GetSaltLength();
byte[] baResult = new byte[baDecrypted.Length - saltLength];
for (int i = 0; i < baResult.Length; i++)
baResult[i] = baDecrypted[i + saltLength];
string result = Encoding.UTF8.GetString(baResult);
return result;
}
Code for getting random bytes
public static byte[] GetRandomBytes()
{
int saltLength = GetSaltLength();
byte[] ba = new byte[saltLength];
RNGCryptoServiceProvider.Create().GetBytes(ba);
return ba;
}
public static int GetSaltLength()
{
return 8;
}
Another way of getting random bytes is by using System.Random
. However, System.Random is strongly not recommended to be used in cryptography. This is because System.Random is not a true random. The changes of the value are following a specific sequence and pattern and it is predictable. RNGCrypto is truly randomize and the generated values does not follow a pattern and it is unpredictable.
About Storing Password in System.String
If you are working this on a desktop pc application, you are recommended not to store the password in plain string. Strings cannot be manually disposed. It will be disposed by Garbage Collector, however, it is hard to tell how long will the string continue to stay in memory before it is completely dispose. At the time it remains in memory, it is easily retrievable. One of the suggested method is by capturing the password key strokes and store it into byte array (or list of bytes) and wipe off the byte array immediately after it is used (for example, fill the byte array with 0 (zero), etc).
Another option is by using System.Security.SecureString
. System.Security.SecureString can be destroyed manually at any time. The value stored within System.Security.SecureString is encrypted.
Example of Using SecureString
using System.Security;
using System.Runtime.InteropServices;
Storing text into SecureString:
SecureString secureString = new SecureString();
secureString.AppendChar('h');
secureString.AppendChar('e');
secureString.AppendChar('l');
secureString.AppendChar('l');
secureString.AppendChar('o');
Retrieving data from SecureString
byte[] secureStringBytes = null;
IntPtr unmanagedBytes = Marshal.SecureStringToGlobalAllocAnsi(secureString);
try
{
unsafe
{
byte* byteArray = (byte*)unmanagedBytes.ToPointer();
byte* pEnd = byteArray;
while (*pEnd++ != 0) { }
int length = (int)((pEnd - byteArray) - 1);
secureStringBytes = new byte[length];
for (int i = 0; i < length; ++i)
{
secureStringBytes[i] = *(byteArray + i);
}
}
}
finally
{
Marshal.ZeroFreeGlobalAllocAnsi(unmanagedBytes);
}
return secureStringBytes;
Retriving data from SecureString involves executing unsafe code (for handling unmanaged bytes). Therefore you have to allow your project to be built with unsafe code allowed. This option is available at your project's Properties settings.
Or else you will receive error message:
Unsafe code may only appear if compiling with /unsafe