using System.IO; using System.Text; namespace System.Security.Cryptography { /// /// Provides cryptographic functions ready-to-use. /// public class CryptographyHelper { // "readonly" not added due to UnitTests #pragma warning disable IDE0044 // Add "readonly" modifier private static int _saltLength = 8; #pragma warning restore IDE0044 // Add "readonly" modifier private readonly string _masterKeyFile; /// /// Initializes a new instance of the class. /// /// The (absolute) path to the crypto key file. On null the file 'crypto.key' at the executing assembly location will be used. public CryptographyHelper(string keyFile = null) { _masterKeyFile = keyFile; if (string.IsNullOrWhiteSpace(_masterKeyFile)) _masterKeyFile = "crypto.key"; if (!Path.IsPathRooted(_masterKeyFile)) _masterKeyFile = Path.Combine(AppContext.BaseDirectory, _masterKeyFile); string pw = File.Exists(_masterKeyFile) ? File.ReadAllText(_masterKeyFile) : null; if (string.IsNullOrWhiteSpace(pw)) File.WriteAllText(_masterKeyFile, GetRandomString(64)); } #region Instance methods #region AES /// /// Decrypts data using the AES algorithm and a password. /// /// /// When the parameter is null, the key from the file (set on initialize) is used instead. /// /// The encrypted data (cipher). /// The password to use for decryption (optional). /// The decrypted data. public byte[] DecryptAes(byte[] cipher, string password = null) { password ??= File.ReadAllText(_masterKeyFile); return AesDecrypt(cipher, password); } /// /// Encrypts data using the AES algorithm and a password. /// /// /// When the parameter is null, the key from the file (set on initialize) is used instead. /// /// The data to encrypt. /// The password to use for encryption (optional). /// The encrypted data (cipher). public byte[] EncryptAes(byte[] plain, string password = null) { password ??= File.ReadAllText(_masterKeyFile); return AesEncrypt(plain, password); } /// /// Decrypts a Base64 string using the AES algorithm and a password to an UTF-8 string. /// /// The encrypted Base64 encoded string. /// The password to use for decryption (optional). /// The decrypted UTF-8 string string. public string DecryptAes(string cipherStr, string password = null) { byte[] cipher = Convert.FromBase64String(cipherStr); byte[] plain = DecryptAes(cipher, password); return Encoding.UTF8.GetString(plain); } /// /// Encrypts an UTF-8 string using the AES algorithm and a password to a Base64 string. /// /// The UTF-8 string to encrypt. /// The password to use for encryption (optional). /// The encrypted Base64 encoded string. public string EncryptAes(string plainStr, string password = null) { byte[] plain = Encoding.UTF8.GetBytes(plainStr); byte[] cipher = EncryptAes(plain, password); return Convert.ToBase64String(cipher); } #endregion AES #region Triple DES /// /// Decrypts data using the triple DES algorithm and a password. /// /// /// When the parameter is null, the key from the file (set on initialize) is used instead. /// /// The encrypted data (cipher). /// The password to use for decryption (optional). /// The decrypted data. public byte[] DecryptTripleDes(byte[] cipher, string password = null) { password ??= File.ReadAllText(_masterKeyFile); return TripleDesDecrypt(cipher, password); } /// /// Encrypts data using the triple DES algorithm and a password. /// /// /// When the parameter is null, the key from the file (set on initialize) is used instead. /// /// The data to encrypt. /// The password to use for encryption (optional). /// The encrypted data (cipher). public byte[] EncryptTripleDes(byte[] plain, string password = null) { password ??= File.ReadAllText(_masterKeyFile); return TripleDesEncrypt(plain, password); } /// /// Decrypts a Base64 encoded string using the triple DES algorithm and a password. /// /// /// When the parameter is null, the key from the file (set on initialize) is used instead. /// /// The encrypted Base64 encoded string. /// The password to use for decryption (optional). /// The decrypted UTF-8 string. public string DecryptTripleDes(string cipherStr, string password = null) { byte[] cipher = Convert.FromBase64String(cipherStr); byte[] plain = DecryptTripleDes(cipher, password); return Encoding.UTF8.GetString(plain); } /// /// Encrypts an UTF-8 string using the triple DES algorithm and a password. /// /// /// When the parameter is null, the key from the file (set on initialize) is used instead. /// /// The UTF-8 string to encrypt. /// The password to use for encryption (optional). /// The encrypted Base64 encoded string. public string EncryptTripleDes(string plainStr, string password = null) { byte[] plain = Encoding.UTF8.GetBytes(plainStr); byte[] cipher = EncryptTripleDes(plain, password); return Convert.ToBase64String(cipher); } #endregion Triple DES #endregion Instance methods #region Static methods #region Encryption #region AES /// /// Decrypts data using the AES algorithm and a password. /// /// The encrypted data (cipher). /// The password to use for decryption. /// The decrypted data. public static byte[] AesDecrypt(byte[] cipher, string password) { byte[] salt = new byte[_saltLength]; Array.Copy(cipher, salt, _saltLength); using var gen = new Rfc2898DeriveBytes(password, salt); using var aes = Aes.Create(); aes.Mode = CipherMode.CBC; aes.Padding = PaddingMode.PKCS7; aes.Key = gen.GetBytes(aes.KeySize / 8); aes.IV = gen.GetBytes(aes.BlockSize / 8); using var ms = new MemoryStream(); using var cs = new CryptoStream(ms, aes.CreateDecryptor(), CryptoStreamMode.Write); cs.Write(cipher, _saltLength, cipher.Length - _saltLength); cs.FlushFinalBlock(); return ms.ToArray(); } /// /// Decrypts a Base64 string using the AES algorithm and a password to an UTF-8 string. /// /// The encrypted Base64 encoded string. /// The password to use for decryption. /// The decrypted UTF-8 string string. public static string AesDecrypt(string cipherStr, string password) { byte[] cipher = Convert.FromBase64String(cipherStr); byte[] plain = AesDecrypt(cipher, password); return Encoding.UTF8.GetString(plain); } /// /// Encrypts data using the AES algorithm and a password. /// /// The data to encrypt. /// The password to use for encryption. /// The encrypted data (cipher). public static byte[] AesEncrypt(byte[] plain, string password) { byte[] salt = GetRandomBytes(_saltLength); using var gen = new Rfc2898DeriveBytes(password, salt); using var aes = Aes.Create(); aes.Mode = CipherMode.CBC; aes.Padding = PaddingMode.PKCS7; aes.Key = gen.GetBytes(aes.KeySize / 8); aes.IV = gen.GetBytes(aes.BlockSize / 8); using var ms = new MemoryStream(); using var cs = new CryptoStream(ms, aes.CreateEncryptor(), CryptoStreamMode.Write); ms.Write(salt, 0, salt.Length); cs.Write(plain, 0, plain.Length); cs.FlushFinalBlock(); return ms.ToArray(); } /// /// Encrypts an UTF-8 string using the AES algorithm and a password to a Base64 string. /// /// The UTF-8 string to encrypt. /// The password to use for encryption. /// The encrypted Base64 encoded string. public static string AesEncrypt(string plainStr, string password) { byte[] plain = Encoding.UTF8.GetBytes(plainStr); byte[] cipher = AesEncrypt(plain, password); return Convert.ToBase64String(cipher); } #endregion AES #region Triple DES /// /// Decrypts data using the triple DES algorithm and a password. /// /// The encrypted data (cipher). /// The password to use for decryption. /// The decrypted data. public static byte[] TripleDesDecrypt(byte[] cipher, string password) { byte[] salt = new byte[_saltLength]; Array.Copy(cipher, salt, _saltLength); using var gen = new Rfc2898DeriveBytes(password, salt); using var tdes = TripleDES.Create(); tdes.Mode = CipherMode.CBC; tdes.Padding = PaddingMode.PKCS7; tdes.Key = gen.GetBytes(tdes.KeySize / 8); tdes.IV = gen.GetBytes(tdes.BlockSize / 8); using var ms = new MemoryStream(); using var cs = new CryptoStream(ms, tdes.CreateDecryptor(), CryptoStreamMode.Write); cs.Write(cipher, _saltLength, cipher.Length - _saltLength); cs.FlushFinalBlock(); return ms.ToArray(); } /// /// Encrypts data using the triple DES algorithm and a password. /// /// The data to encrypt. /// The password to use for encryption. /// The encrypted data (cipher). public static byte[] TripleDesEncrypt(byte[] plain, string password) { byte[] salt = GetRandomBytes(_saltLength); using var gen = new Rfc2898DeriveBytes(password, salt); using var tdes = TripleDES.Create(); tdes.Mode = CipherMode.CBC; tdes.Padding = PaddingMode.PKCS7; tdes.Key = gen.GetBytes(tdes.KeySize / 8); tdes.IV = gen.GetBytes(tdes.BlockSize / 8); using var ms = new MemoryStream(); using var cs = new CryptoStream(ms, tdes.CreateEncryptor(), CryptoStreamMode.Write); ms.Write(salt, 0, salt.Length); cs.Write(plain, 0, plain.Length); cs.FlushFinalBlock(); return ms.ToArray(); } /// /// Decrypts an Base64 encoded string using the triple DES algorithm and a password. /// /// The encrypted Base64 encoded string. /// The password to use for decryption. /// The decrypted UTF-8 string. public static string TripleDesDecrypt(string cipherStr, string password) { byte[] cipher = Convert.FromBase64String(cipherStr); byte[] plain = TripleDesDecrypt(cipher, password); return Encoding.UTF8.GetString(plain); } /// /// Encrypts an UTF-8 string using the triple DES algorithm and a password. /// /// The UTF-8 string to encrypt. /// The password to use for encryption. /// The encrypted Base64 encoded string. public static string TripleDesEncrypt(string plainStr, string password) { byte[] plain = Encoding.UTF8.GetBytes(plainStr); byte[] cipher = TripleDesEncrypt(plain, password); return Convert.ToBase64String(cipher); } #endregion Triple DES #endregion Encryption #region Hashing #region MD5 /// /// Computes a hash value from a string using the MD5 algorithm. /// /// The string to hash, using UTF-8 encoding. /// The MD5 hash value, in hexadecimal notation. public static string Md5(string str) { return Md5(Encoding.UTF8.GetBytes(str)); } /// /// Computes a hash value from a file using the MD5 algorithm. /// /// The name of the file to read. /// The MD5 hash value, in hexadecimal notation. public static string Md5File(string fileName) { using var md5 = MD5.Create(); using var fs = new FileStream(fileName, FileMode.Open); return md5.ComputeHash(fs).BytesToHex(); } /// /// Computes a hash from a byte array value using the MD5 algorithm. /// /// The byte array. /// The MD5 hash value, in hexadecimal notation. public static string Md5(byte[] bytes) { using var md5 = MD5.Create(); return md5.ComputeHash(bytes).BytesToHex(); } #endregion MD5 #region SHA-1 /// /// Computes a hash value from a string using the SHA-1 algorithm. /// /// The string to hash, using UTF-8 encoding. /// The SHA-1 hash value, in hexadecimal notation. public static string Sha1(string str) { return Sha1(Encoding.UTF8.GetBytes(str)); } /// /// Computes a hash value from a file using the SHA-1 algorithm. /// /// The name of the file to read. /// The SHA-1 hash value, in hexadecimal notation. public static string Sha1File(string fileName) { using var sha1 = SHA1.Create(); using var fs = new FileStream(fileName, FileMode.Open); return sha1.ComputeHash(fs).BytesToHex(); } /// /// Computes a hash from a byte array value using the SHA-1 algorithm. /// /// The byte array. /// The SHA-1 hash value, in hexadecimal notation. public static string Sha1(byte[] bytes) { using var sha1 = SHA1.Create(); return sha1.ComputeHash(bytes).BytesToHex(); } #endregion SHA-1 #region SHA-256 /// /// Computes a hash value from a string using the SHA-256 algorithm. /// /// The string to hash, using UTF-8 encoding. /// The SHA-256 hash value, in hexadecimal notation. public static string Sha256(string str) { return Sha256(Encoding.UTF8.GetBytes(str)); } /// /// Computes a hash value from a file using the SHA-256 algorithm. /// /// The name of the file to read. /// The SHA-256 hash value, in hexadecimal notation. public static string Sha256File(string fileName) { using var sha256 = SHA256.Create(); using var fs = new FileStream(fileName, FileMode.Open); return sha256.ComputeHash(fs).BytesToHex(); } /// /// Computes a hash from a byte array value using the SHA-256 algorithm. /// /// The byte array. /// The SHA-256 hash value, in hexadecimal notation. public static string Sha256(byte[] bytes) { using var sha256 = SHA256.Create(); return sha256.ComputeHash(bytes).BytesToHex(); } #endregion SHA-256 #region SHA-512 /// /// Computes a hash value from a string using the SHA-512 algorithm. /// /// The string to hash, using UTF-8 encoding. /// The SHA-512 hash value, in hexadecimal notation. public static string Sha512(string str) { return Sha512(Encoding.UTF8.GetBytes(str)); } /// /// Computes a hash value from a file using the SHA-512 algorithm. /// /// The name of the file to read. /// The SHA-512 hash value, in hexadecimal notation. public static string Sha512File(string fileName) { using var sha512 = SHA512.Create(); using var fs = new FileStream(fileName, FileMode.Open); return sha512.ComputeHash(fs).BytesToHex(); } /// /// Computes a hash from a byte array value using the SHA-512 algorithm. /// /// The byte array. /// The SHA-512 hash value, in hexadecimal notation. public static string Sha512(byte[] bytes) { using var sha512 = SHA512.Create(); return sha512.ComputeHash(bytes).BytesToHex(); } #endregion SHA-512 #endregion Hashing #region Random /// /// Generates an array with random (non-zero) bytes. /// /// The number of bytes to generate. /// public static byte[] GetRandomBytes(int count) { using var gen = RandomNumberGenerator.Create(); byte[] bytes = new byte[count]; gen.GetNonZeroBytes(bytes); return bytes; } /// /// Generates a string with random characters. /// /// The length of the string to generate. /// The characters to use (Default: [a-zA-Z0-9]). /// public static string GetRandomString(int length, string pool = null) { if (string.IsNullOrWhiteSpace(pool)) pool = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"; var sb = new StringBuilder(length); int multiply = sizeof(int) / sizeof(byte); int len = length * multiply; byte[] bytes = GetRandomBytes(len); for (int i = 0; i < bytes.Length; i += multiply) { uint number = BitConverter.ToUInt32(bytes, i); sb.Append(pool[(int)(number % pool.Length)]); } return sb.ToString(); } #endregion Random #region Secure probing /// /// Determines whether two byte arrays are equal in constant time. This method does not stop /// early if a difference was detected, unless the length differs. /// /// The first array. /// The second array. /// true, if both arrays are equal; otherwise, false. public static bool SecureEquals(byte[] a, byte[] b) { if ((a == null) != (b == null)) return false; if (a.Length != b.Length) return false; int differentBits = 0; for (int i = 0; i < a.Length; i++) { differentBits |= a[i] ^ b[i]; } return differentBits == 0; } /// /// Determines whether two strings are equal in constant time. This method does not stop /// early if a difference was detected, unless the length differs. /// /// The first string. /// The second string. /// true, if both strings are equal; otherwise, false. public static bool SecureEquals(string a, string b) { if ((a == null) != (b == null)) return false; if (a.Length != b.Length) return false; int differentBits = 0; for (int i = 0; i < a.Length; i++) { differentBits |= a[i] ^ b[i]; } return differentBits == 0; } #endregion Secure probing #endregion Static methods } }