123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226 |
- using System;
- using System.Diagnostics.CodeAnalysis;
- using System.IO;
- using System.Linq;
- using System.Security.Cryptography;
- using System.Text;
- namespace InABox.Core
- {
- public static class Encryption
- {
- // This constant is used to determine the keysize of the encryption algorithm in bits.
- // We divide this by 8 within the code below to get the equivalent number of bytes.
- private const int Keysize = 256;
- // This constant determines the number of iterations for the password bytes generation function.
- private const int DerivationIterations = 1000;
- public static string Encrypt(string plainText, string passPhrase, bool simple = false)
- {
- if (simple)
- {
- var encrypted = new byte[plainText.Length];
- for (var i = 0; i < plainText.Length; i++)
- {
- var ch = plainText[i];
- encrypted[i] = (byte)(ch + passPhrase[i % passPhrase.Length]);
- }
- return Convert.ToBase64String(encrypted);
- }
- // Salt and IV is randomly generated each time, but is preprended to encrypted cipher text
- // so that the same Salt and IV values can be used when decrypting.
- var saltStringBytes = Generate256BitsOfRandomEntropy();
- var ivStringBytes = Generate256BitsOfRandomEntropy();
- var plainTextBytes = Encoding.UTF8.GetBytes(plainText);
- using (var password = new Rfc2898DeriveBytes(passPhrase, saltStringBytes, DerivationIterations))
- {
- var keyBytes = password.GetBytes(Keysize / 8);
- using (var symmetricKey = new RijndaelManaged())
- {
- symmetricKey.BlockSize = 256;
- symmetricKey.Mode = CipherMode.CBC;
- symmetricKey.Padding = PaddingMode.PKCS7;
- using (var encryptor = symmetricKey.CreateEncryptor(keyBytes, ivStringBytes))
- {
- using (var memoryStream = new MemoryStream())
- {
- using (var cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write))
- {
- cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length);
- cryptoStream.FlushFinalBlock();
- // Create the final bytes as a concatenation of the random salt bytes, the random iv bytes and the cipher bytes.
- var cipherTextBytes = saltStringBytes;
- cipherTextBytes = cipherTextBytes.Concat(ivStringBytes).ToArray();
- cipherTextBytes = cipherTextBytes.Concat(memoryStream.ToArray()).ToArray();
- memoryStream.Close();
- cryptoStream.Close();
- return Convert.ToBase64String(cipherTextBytes);
- }
- }
- }
- }
- }
- }
- public static string Decrypt(string cipherText, string passPhrase, bool simpleEncryption = true)
- {
- if (string.IsNullOrWhiteSpace(cipherText))
- throw new Exception("Text may not be blank!");
- // New Simplified Decryption to handle .Net6 clients
- // Kenric needs this to get the GenHTTP server running
- // Needs to be revisited (stronger encryption? TLS/SSL?) when moving to .Net6 overall
- if (cipherText.Length < 64 || simpleEncryption == true)
- {
- var encrypted = Convert.FromBase64String(cipherText);
- var decrypted = new byte[encrypted.Length];
- for (var i = 0; i < encrypted.Length; i++)
- {
- var ch = encrypted[i];
- decrypted[i] = (byte)(ch - passPhrase[i % passPhrase.Length]);
- }
- return Encoding.UTF8.GetString(decrypted);
- }
- // Get the complete stream of bytes that represent:
- // [32 bytes of Salt] + [32 bytes of IV] + [n bytes of CipherText]
- var cipherTextBytesWithSaltAndIv = Convert.FromBase64String(cipherText);
- // Get the saltbytes by extracting the first 32 bytes from the supplied cipherText bytes.
- var saltStringBytes = cipherTextBytesWithSaltAndIv.Take(Keysize / 8).ToArray();
- // Get the IV bytes by extracting the next 32 bytes from the supplied cipherText bytes.
- var ivStringBytes = cipherTextBytesWithSaltAndIv.Skip(Keysize / 8).Take(Keysize / 8).ToArray();
- // Get the actual cipher text bytes by removing the first 64 bytes from the cipherText string.
- var cipherTextBytes = cipherTextBytesWithSaltAndIv.Skip(Keysize / 8 * 2).Take(cipherTextBytesWithSaltAndIv.Length - Keysize / 8 * 2)
- .ToArray();
- using (var password = new Rfc2898DeriveBytes(passPhrase, saltStringBytes, DerivationIterations))
- {
- var keyBytes = password.GetBytes(Keysize / 8);
- using (var symmetricKey = new RijndaelManaged())
- {
- symmetricKey.BlockSize = 256;
- symmetricKey.Mode = CipherMode.CBC;
- symmetricKey.Padding = PaddingMode.PKCS7;
- using (var decryptor = symmetricKey.CreateDecryptor(keyBytes, ivStringBytes))
- {
- using (var memoryStream = new MemoryStream(cipherTextBytes))
- {
- using (var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read))
- {
- var plainTextBytes = new byte[cipherTextBytes.Length];
- var decryptedByteCount = cryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length);
- memoryStream.Close();
- cryptoStream.Close();
- return Encoding.UTF8.GetString(plainTextBytes, 0, decryptedByteCount);
- }
- }
- }
- }
- }
- }
- public static bool Decrypt(string cipherText, string passPhrase, out string decrypted)
- {
- decrypted = "";
- var result = false;
- try
- {
- decrypted = Decrypt(cipherText, passPhrase);
- result = true;
- }
- catch (Exception e)
- {
- }
- return result;
- }
- public static byte[] Generate256BitsOfRandomEntropy()
- {
- var randomBytes = new byte[32]; // 32 Bytes will give us 256 bits.
- using (var rngCsp = new RNGCryptoServiceProvider())
- {
- // Fill the array with cryptographically secure random bytes.
- rngCsp.GetBytes(randomBytes);
- }
- return randomBytes;
- }
- public static string EncryptV2(string data, byte[] key)
- {
- if (TryEncryptV2(data, key, out var result, out var error))
- {
- return result;
- }
- throw new Exception(error);
- }
- public static bool TryEncryptV2(string data, byte[] key, [NotNullWhen(true)] out string? result, [NotNullWhen(false)] out string? error)
- {
- try
- {
- using var stream = new MemoryStream();
- using (var aes = Aes.Create())
- {
- aes.Key = key;
- var iv = aes.IV;
- stream.Write(iv, 0, iv.Length);
- using var cryptoStream = new CryptoStream(stream, aes.CreateEncryptor(), CryptoStreamMode.Write);
- using var writer = new StreamWriter(cryptoStream);
- writer.Write(data);
- }
- result = Convert.ToBase64String(stream.ToArray());
- error = null;
- return true;
- }
- catch(Exception e)
- {
- error = $"Encryption failed! {CoreUtils.FormatException(e)}";
- result = null;
- return false;
- }
- }
- public static string DecryptV2(string data, byte[] key)
- {
- if(TryDecryptV2(data, key, out var result, out var error))
- {
- return result;
- }
- throw new Exception(error);
- }
- public static bool TryDecryptV2(string data, byte[] key, [NotNullWhen(true)] out string? result, [NotNullWhen(false)] out string? error)
- {
- try
- {
- using var stream = new MemoryStream(Convert.FromBase64String(data));
- using var aes = Aes.Create();
- var iv = new byte[aes.IV.Length];
- if (stream.Read(iv, 0, iv.Length) != iv.Length)
- {
- error = $"Decryption failed! Data is too short for IV.";
- result = null;
- return false;
- }
- using var cryptoStream = new CryptoStream(stream, aes.CreateDecryptor(key, iv), CryptoStreamMode.Read);
- using var decryptReader = new StreamReader(cryptoStream);
- var decrypted = decryptReader.ReadToEnd();
- result = decrypted;
- error = null;
- return true;
- }
- catch(Exception e)
- {
- error = $"Decryption failed! {CoreUtils.FormatException(e)}";
- result = null;
- return false;
- }
- }
- }
- }
|