using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
namespace Codebasement.DomainDrivenDesign.Basement.Utilities
{
/// <summary>
///
/// </summary>
public enum SymmetricCryptoAlgorithm
{
/// <summary>
///
/// </summary>
DES,
/// <summary>
///
/// </summary>
Rijndael
}
/// <summary>
/// CallBack delegate for progress notification
/// </summary>
public delegate void CryptoProgressCallBack(int min, int max, int value);
/// <summary>
/// This is the exception class that is thrown throughout the Decryption process
/// </summary>
public class CryptoHelpException : ApplicationException
{
/// <summary>
/// Initializes a new instance of the <see cref="CryptoHelpException"/> class.
/// </summary>
/// <param name="msg">The MSG.</param>
public CryptoHelpException(string msg) : base(msg)
{
}
}
/// <summary>
/// This class provide static methods for (symmetric) encryption and decryption.
/// </summary>
public class Encryption
{
// the key
/// <summary>
/// The amount of bytes to read from the file
/// </summary>
private const int BUFFER_SIZE = 128*1024;
/// <summary>
/// Tag to make sure this file is readable/decryptable by this class
/// </summary>
private const ulong FC_TAG = 0xFC010203040506CF;
private const string Password = "key";
#region File Encryption
/// <summary>
/// Crypto Random number generator for use in EncryptFile
/// </summary>
private static readonly RandomNumberGenerator rand = new RNGCryptoServiceProvider();
/// <summary>
/// Checks to see if two byte array are equal
/// </summary>
/// <param name="b1">the first byte array</param>
/// <param name="b2">the second byte array</param>
/// <returns>true if b1.Length == b2.Length and each byte in b1 is
/// equal to the corresponding byte in b2</returns>
private static bool CheckByteArrays(byte[] b1, byte[] b2)
{
if (b1.Length == b2.Length)
{
for (int i = 0; i < b1.Length; ++i)
{
if (b1[i] != b2[i])
return false;
}
return true;
}
return false;
}
/// <summary>
/// Creates a Rijndael SymmetricAlgorithm for use in EncryptFile and DecryptFile
/// </summary>
/// <param name="password">the string to use as the password</param>
/// <param name="salt">the salt to use with the password</param>
/// <returns>A SymmetricAlgorithm for encrypting/decrypting with Rijndael</returns>
private static SymmetricAlgorithm CreateRijndael(string password, byte[] salt)
{
Rfc2898DeriveBytes pdb = new Rfc2898DeriveBytes(password, salt); //, "SHA256", 1000);
SymmetricAlgorithm sma = Rijndael.Create();
sma.KeySize = 256;
sma.Key = pdb.GetBytes(32);
sma.Padding = PaddingMode.PKCS7;
return sma;
}
/// <summary>
/// Creates a DES SymmetricAlgorithm for use in EncryptFile and DecryptFile
/// </summary>
/// <param name="password">the string to use as the password</param>
/// <param name="salt">the salt to use with the password</param>
/// <returns>A SymmetricAlgorithm for encrypting/decrypting with Rijndael</returns>
private static SymmetricAlgorithm CreateDES(string password, byte[] salt)
{
Rfc2898DeriveBytes pdb = new Rfc2898DeriveBytes(password, salt);
SymmetricAlgorithm sma = DES.Create();
sma.KeySize = 64;
sma.Key = pdb.GetBytes(8);
return sma;
}
/// <summary>
/// Generates a specified amount of random bytes
/// </summary>
/// <param name="count">the number of bytes to return</param>
/// <returns>a byte array of count size filled with random bytes</returns>
private static byte[] GenerateRandomBytes(int count)
{
byte[] bytes = new byte[count];
rand.GetBytes(bytes);
return bytes;
}
/// <summary>
/// This takes an input file and encrypts it into the output file
/// </summary>
/// <param name="inFile">the file to encrypt</param>
/// <param name="outFile">the file to write the encrypted data to</param>
/// <param name="password">the password to use as the key</param>
/// <param name="callback">the method to call to notify of progress</param>
/// <param name="algorithm">The algorithm.</param>
public static void EncryptFile(string inFile,
string outFile,
string password,
CryptoProgressCallBack callback,
SymmetricCryptoAlgorithm algorithm)
{
using (FileStream fin = File.OpenRead(inFile),
fout = File.OpenWrite(outFile))
{
long lSize = fin.Length; // the size of the input file for storing
int size = (int) lSize; // the size of the input file for progress
byte[] bytes = new byte[BUFFER_SIZE]; // the buffer
int value = 0; // the amount overall read from the input file for progress
SymmetricAlgorithm sma;
byte[] IV;
byte[] salt;
// create the crypting object
if (algorithm == SymmetricCryptoAlgorithm.Rijndael)
{
// generate IV and Salt
IV = GenerateRandomBytes(16);
salt = GenerateRandomBytes(16);
sma = CreateRijndael(password, salt);
}
else
{
// generate IV and Salt
IV = GenerateRandomBytes(8);
salt = GenerateRandomBytes(8);
sma = CreateDES(password, salt);
}
sma.IV = IV;
// write the IV and salt to the beginning of the file
fout.Write(IV, 0, IV.Length);
fout.Write(salt, 0, salt.Length);
// create the hashing and crypto streams
HashAlgorithm hasher = SHA256.Create();
using (CryptoStream cout = new CryptoStream(fout, sma.CreateEncryptor(), CryptoStreamMode.Write),
chash = new CryptoStream(Stream.Null, hasher, CryptoStreamMode.Write))
{
// write the size of the file to the output file
BinaryWriter bw = new BinaryWriter(cout);
bw.Write(lSize);
// write the file cryptor tag to the file
bw.Write(FC_TAG);
// read and the write the bytes to the crypto stream in BUFFER_SIZEd chunks
int read; // the amount of bytes read from the input file
while ((read = fin.Read(bytes, 0, bytes.Length)) != 0)
{
cout.Write(bytes, 0, read);
chash.Write(bytes, 0, read);
value += read;
callback(0, size, value);
}
// flush and close the hashing object
chash.Flush();
chash.Close();
// read the hash
byte[] hash = hasher.Hash;
// write the hash to the end of the file
cout.Write(hash, 0, hash.Length);
// flush and close the cryptostream
cout.Flush();
cout.Close();
}
}
}
/// <summary>
/// takes an input file and decrypts it to the output file
/// </summary>
/// <param name="inFile">the file to decrypt</param>
/// <param name="outFile">the file to write the decrypted data to</param>
/// <param name="password">the password used as the key</param>
/// <param name="callback">the method to call to notify of progress</param>
/// <param name="algorithm">The algorithm.</param>
public static void DecryptFile(string inFile,
string outFile,
string password,
CryptoProgressCallBack callback,
SymmetricCryptoAlgorithm algorithm)
{
// create and open the file streams
using (FileStream fin = File.OpenRead(inFile),
fout = File.OpenWrite(outFile))
{
int size = (int) fin.Length; // the size of the file for progress notification
byte[] bytes = new byte[BUFFER_SIZE]; // byte buffer
int outValue = 0; // the amount of bytes written out
byte[] IV;
byte[] salt;
SymmetricAlgorithm sma;
// create the crypting object
if (algorithm == SymmetricCryptoAlgorithm.Rijndael)
{
IV = new byte[16];
fin.Read(IV, 0, 16);
salt = new byte[16];
fin.Read(salt, 0, 16);
sma = CreateRijndael(password, salt);
}
else
{
IV = new byte[8];
fin.Read(IV, 0, 8);
salt = new byte[8];
fin.Read(salt, 0, 8);
sma = CreateDES(password, salt);
}
sma.IV = IV;
int value = 32;
long lSize; // the size stored in the input stream
// create the hashing object, so that we can verify the file
HashAlgorithm hasher = SHA256.Create();
// create the cryptostreams that will process the file
using (CryptoStream cin = new CryptoStream(fin, sma.CreateDecryptor(), CryptoStreamMode.Read),
chash = new CryptoStream(Stream.Null, hasher, CryptoStreamMode.Write))
{
// read size from file
BinaryReader br = new BinaryReader(cin);
lSize = br.ReadInt64();
ulong tag = br.ReadUInt64();
if (FC_TAG != tag)
throw new CryptoHelpException("File Corrupted!");
//determine number of reads to process on the file
long numReads = lSize/BUFFER_SIZE;
// determine what is left of the file, after numReads
long slack = lSize%BUFFER_SIZE;
// read the buffer_sized chunks
int read; // the amount of bytes read from the stream
for (int i = 0; i < numReads; ++i)
{
read = cin.Read(bytes, 0, bytes.Length);
fout.Write(bytes, 0, read);
chash.Write(bytes, 0, read);
value += read;
outValue += read;
callback(0, size, value);
}
// now read the slack
if (slack > 0)
{
read = cin.Read(bytes, 0, (int) slack);
fout.Write(bytes, 0, read);
chash.Write(bytes, 0, read);
value += read;
outValue += read;
callback(0, size, value);
}
// flush and close the hashing stream
chash.Flush();
chash.Close();
// flush and close the output file
fout.Flush();
fout.Close();
// read the current hash value
byte[] curHash = hasher.Hash;
// get and compare the current and old hash values
byte[] oldHash = new byte[hasher.HashSize/8];
read = cin.Read(oldHash, 0, oldHash.Length);
if ((oldHash.Length != read) || (!CheckByteArrays(oldHash, curHash)))
throw new CryptoHelpException("File Corrupted!");
}
// make sure the written and stored size are equal
if (outValue != lSize)
throw new CryptoHelpException("File Sizes don't match!");
}
}
#endregion File Encryption
#region Password Hash
/// <summary>
/// Encrypts the password.
/// </summary>
/// <param name="userName">Name of the user.</param>
/// <param name="password">The password.</param>
/// <param name="salted">if set to <c>true</c> [salted].</param>
/// <param name="salt">The salt.</param>
/// <returns></returns>
public byte[] EncryptPassword(string userName, string password,
bool salted, byte[] salt)
{
string tmpPassword;
if (salted) // password + salt
{
tmpPassword = Convert.ToBase64String(salt)
+ userName.ToLower() + password;
}
else
{
tmpPassword = password;
}
//Convert the password string into an Array of bytes.
UTF8Encoding textConverter = new UTF8Encoding();
byte[] passBytes = textConverter.GetBytes(tmpPassword);
//Return the encrypted bytes
return new SHA256Managed().ComputeHash(passBytes);
}
// Comparison of two arrays for equality
// You can add a length comparison, but normally
// all hashes are the same size.
/*
private bool PasswordsMatch(byte[] psswd1, byte[] psswd2)
{
try
{
for (int i = 0; i < psswd1.Length; i++)
{
if (psswd1[i] != psswd2[i])
return false;
}
return true;
}
catch (IndexOutOfRangeException)
{
return false;
}
}
*/
#endregion Password Hash
#region Symmetric String Encryption
/// <summary>
/// Method which does the encryption using Rijndael algorithm
/// </summary>
/// <param name="InputText">Data to be encrypted</param>
/// <returns>Encrypted Data</returns>
public static string EncryptRijndaelString(string InputText)
{
RijndaelManaged RijndaelCipher = new RijndaelManaged();
byte[] PlainText = Encoding.Unicode.GetBytes(InputText);
byte[] Salt = Encoding.ASCII.GetBytes(Password.Length.ToString());
//This class uses an extension of the PBKDF1 algorithm defined in the PKCS#5 v2.0
//standard to derive bytes suitable for use as key material from a password.
//The standard is documented in IETF RRC 2898.
//PasswordDeriveBytes SecretKey = new PasswordDeriveBytes(Password, Salt);
Rfc2898DeriveBytes SecretKey = new Rfc2898DeriveBytes(Password, Salt); //, "SHA256", 1000);
//Creates a symmetric encryptor object.
ICryptoTransform Encryptor = RijndaelCipher.CreateEncryptor(SecretKey.GetBytes(32), SecretKey.GetBytes(16));
MemoryStream memoryStream = new MemoryStream();
//Defines a stream that links data streams to cryptographic transformations
CryptoStream cryptoStream = new CryptoStream(memoryStream, Encryptor, CryptoStreamMode.Write);
cryptoStream.Write(PlainText, 0, PlainText.Length);
//Writes the final state and clears the buffer
cryptoStream.FlushFinalBlock();
byte[] CipherBytes = memoryStream.ToArray();
memoryStream.Close();
cryptoStream.Close();
string EncryptedData = Convert.ToBase64String(CipherBytes);
return EncryptedData;
}
/// <summary>
/// Method which does the encryption using Rijndael algorithm.
/// This is for decrypting the data
/// which has orginally being encrypted using the above method
/// </summary>
/// <returns>Decrypted Data</returns>
public static string DecryptRijndaelString(string InputText)
{
RijndaelManaged RijndaelCipher = new RijndaelManaged();
byte[] EncryptedData = Convert.FromBase64String(InputText);
byte[] Salt = Encoding.ASCII.GetBytes(Password.Length.ToString());
//Making of the key for decryption
Rfc2898DeriveBytes SecretKey = new Rfc2898DeriveBytes(Password, Salt); //, "SHA256", 1000);
//Creates a symmetric Rijndael decryptor object.
ICryptoTransform Decryptor = RijndaelCipher.CreateDecryptor(SecretKey.GetBytes(32), SecretKey.GetBytes(16));
MemoryStream memoryStream = new MemoryStream(EncryptedData);
//Defines the cryptographics stream for decryption. The stream contains decrypted data
CryptoStream cryptoStream = new CryptoStream(memoryStream, Decryptor, CryptoStreamMode.Read);
byte[] PlainText = new byte[EncryptedData.Length];
int DecryptedCount = cryptoStream.Read(PlainText, 0, PlainText.Length);
memoryStream.Close();
cryptoStream.Close();
//Converting to string
string DecryptedData = Encoding.Unicode.GetString(PlainText, 0, DecryptedCount);
return DecryptedData;
}
/// <summary>
/// Method which does the encryption using DES algorithm
/// </summary>
/// <param name="InputText">Data to be encrypted</param>
/// <returns>Encrypted Data</returns>
public static string EncryptDESString(string InputText)
{
DESCryptoServiceProvider DESCipher = new DESCryptoServiceProvider();
byte[] PlainText = Encoding.Unicode.GetBytes(InputText);
byte[] Salt = Encoding.ASCII.GetBytes(Password.Length.ToString());
//This class uses an extension of the PBKDF1 algorithm defined in the PKCS#5 v2.0
//standard to derive bytes suitable for use as key material from a password.
//The standard is documented in IETF RRC 2898.
Rfc2898DeriveBytes SecretKey = new Rfc2898DeriveBytes(Password, Salt); //, "SHA256", 1000);
//Creates a symmetric encryptor object.
ICryptoTransform Encryptor = DESCipher.CreateEncryptor(SecretKey.GetBytes(8), SecretKey.GetBytes(8));
MemoryStream memoryStream = new MemoryStream();
//Defines a stream that links data streams to cryptographic transformations
CryptoStream cryptoStream = new CryptoStream(memoryStream, Encryptor, CryptoStreamMode.Write);
cryptoStream.Write(PlainText, 0, PlainText.Length);
//Writes the final state and clears the buffer
cryptoStream.FlushFinalBlock();
byte[] CipherBytes = memoryStream.ToArray();
memoryStream.Close();
cryptoStream.Close();
string EncryptedData = Convert.ToBase64String(CipherBytes);
return EncryptedData;
}
/// <summary>
/// Method which does the encryption using DES algorithm.
/// This is for decrypting the data
/// which has orginally being encrypted using the above method
/// </summary>
/// <returns>Decrypted Data</returns>
public static string DecryptDESString(string InputText)
{
DESCryptoServiceProvider DESCipher = new DESCryptoServiceProvider();
byte[] EncryptedData = Convert.FromBase64String(InputText);
byte[] Salt = Encoding.ASCII.GetBytes(Password.Length.ToString());
//Making of the key for decryption
Rfc2898DeriveBytes SecretKey = new Rfc2898DeriveBytes(Password, Salt); //, "SHA256", 1000);
//Creates a symmetric decryptor object.
ICryptoTransform Decryptor = DESCipher.CreateDecryptor(SecretKey.GetBytes(8), SecretKey.GetBytes(8));
MemoryStream memoryStream = new MemoryStream(EncryptedData);
//Defines the cryptographics stream for decryption. The stream contains decrypted data
CryptoStream cryptoStream = new CryptoStream(memoryStream, Decryptor, CryptoStreamMode.Read);
byte[] PlainText = new byte[EncryptedData.Length];
int DecryptedCount = cryptoStream.Read(PlainText, 0, PlainText.Length);
memoryStream.Close();
cryptoStream.Close();
//Converting to string
string DecryptedData = Encoding.Unicode.GetString(PlainText, 0, DecryptedCount);
return DecryptedData;
}
/// <summary>
///
/// </summary>
/// <param name="InputString"></param>
/// <param name="EncryptedString"></param>
/// <returns></returns>
public static bool CompareRijndaelStrings(string InputString, string EncryptedString)
{
return EncryptRijndaelString(InputString) == EncryptedString;
}
/// <summary>
///
/// </summary>
/// <param name="InputString"></param>
/// <param name="EncryptedString"></param>
/// <returns></returns>
public static bool CompareDESStrings(string InputString, string EncryptedString)
{
return EncryptDESString(InputString) == EncryptedString;
}
#endregion Symmetric String Encryption
}
}