Encryption.cs 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. using System;
  2. using System.Diagnostics.CodeAnalysis;
  3. using System.IO;
  4. using System.Linq;
  5. using System.Security.Cryptography;
  6. using System.Text;
  7. namespace InABox.Core
  8. {
  9. public static class Encryption
  10. {
  11. // This constant is used to determine the keysize of the encryption algorithm in bits.
  12. // We divide this by 8 within the code below to get the equivalent number of bytes.
  13. private const int Keysize = 256;
  14. // This constant determines the number of iterations for the password bytes generation function.
  15. private const int DerivationIterations = 1000;
  16. public static string Encrypt(string plainText, string passPhrase, bool simple = false)
  17. {
  18. if (simple)
  19. {
  20. var encrypted = new byte[plainText.Length];
  21. for (var i = 0; i < plainText.Length; i++)
  22. {
  23. var ch = plainText[i];
  24. encrypted[i] = (byte)(ch + passPhrase[i % passPhrase.Length]);
  25. }
  26. return Convert.ToBase64String(encrypted);
  27. }
  28. // Salt and IV is randomly generated each time, but is preprended to encrypted cipher text
  29. // so that the same Salt and IV values can be used when decrypting.
  30. var saltStringBytes = Generate256BitsOfRandomEntropy();
  31. var ivStringBytes = Generate256BitsOfRandomEntropy();
  32. var plainTextBytes = Encoding.UTF8.GetBytes(plainText);
  33. using (var password = new Rfc2898DeriveBytes(passPhrase, saltStringBytes, DerivationIterations))
  34. {
  35. var keyBytes = password.GetBytes(Keysize / 8);
  36. using (var symmetricKey = new RijndaelManaged())
  37. {
  38. symmetricKey.BlockSize = 256;
  39. symmetricKey.Mode = CipherMode.CBC;
  40. symmetricKey.Padding = PaddingMode.PKCS7;
  41. using (var encryptor = symmetricKey.CreateEncryptor(keyBytes, ivStringBytes))
  42. {
  43. using (var memoryStream = new MemoryStream())
  44. {
  45. using (var cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write))
  46. {
  47. cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length);
  48. cryptoStream.FlushFinalBlock();
  49. // Create the final bytes as a concatenation of the random salt bytes, the random iv bytes and the cipher bytes.
  50. var cipherTextBytes = saltStringBytes;
  51. cipherTextBytes = cipherTextBytes.Concat(ivStringBytes).ToArray();
  52. cipherTextBytes = cipherTextBytes.Concat(memoryStream.ToArray()).ToArray();
  53. memoryStream.Close();
  54. cryptoStream.Close();
  55. return Convert.ToBase64String(cipherTextBytes);
  56. }
  57. }
  58. }
  59. }
  60. }
  61. }
  62. public static string Decrypt(string cipherText, string passPhrase, bool simpleEncryption = true)
  63. {
  64. if (string.IsNullOrWhiteSpace(cipherText))
  65. throw new Exception("Text may not be blank!");
  66. // New Simplified Decryption to handle .Net6 clients
  67. // Kenric needs this to get the GenHTTP server running
  68. // Needs to be revisited (stronger encryption? TLS/SSL?) when moving to .Net6 overall
  69. if (cipherText.Length < 64 || simpleEncryption == true)
  70. {
  71. var encrypted = Convert.FromBase64String(cipherText);
  72. var decrypted = new byte[encrypted.Length];
  73. for (var i = 0; i < encrypted.Length; i++)
  74. {
  75. var ch = encrypted[i];
  76. decrypted[i] = (byte)(ch - passPhrase[i % passPhrase.Length]);
  77. }
  78. return Encoding.UTF8.GetString(decrypted);
  79. }
  80. // Get the complete stream of bytes that represent:
  81. // [32 bytes of Salt] + [32 bytes of IV] + [n bytes of CipherText]
  82. var cipherTextBytesWithSaltAndIv = Convert.FromBase64String(cipherText);
  83. // Get the saltbytes by extracting the first 32 bytes from the supplied cipherText bytes.
  84. var saltStringBytes = cipherTextBytesWithSaltAndIv.Take(Keysize / 8).ToArray();
  85. // Get the IV bytes by extracting the next 32 bytes from the supplied cipherText bytes.
  86. var ivStringBytes = cipherTextBytesWithSaltAndIv.Skip(Keysize / 8).Take(Keysize / 8).ToArray();
  87. // Get the actual cipher text bytes by removing the first 64 bytes from the cipherText string.
  88. var cipherTextBytes = cipherTextBytesWithSaltAndIv.Skip(Keysize / 8 * 2).Take(cipherTextBytesWithSaltAndIv.Length - Keysize / 8 * 2)
  89. .ToArray();
  90. using (var password = new Rfc2898DeriveBytes(passPhrase, saltStringBytes, DerivationIterations))
  91. {
  92. var keyBytes = password.GetBytes(Keysize / 8);
  93. using (var symmetricKey = new RijndaelManaged())
  94. {
  95. symmetricKey.BlockSize = 256;
  96. symmetricKey.Mode = CipherMode.CBC;
  97. symmetricKey.Padding = PaddingMode.PKCS7;
  98. using (var decryptor = symmetricKey.CreateDecryptor(keyBytes, ivStringBytes))
  99. {
  100. using (var memoryStream = new MemoryStream(cipherTextBytes))
  101. {
  102. using (var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read))
  103. {
  104. var plainTextBytes = new byte[cipherTextBytes.Length];
  105. var decryptedByteCount = cryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length);
  106. memoryStream.Close();
  107. cryptoStream.Close();
  108. return Encoding.UTF8.GetString(plainTextBytes, 0, decryptedByteCount);
  109. }
  110. }
  111. }
  112. }
  113. }
  114. }
  115. public static bool Decrypt(string cipherText, string passPhrase, out string decrypted)
  116. {
  117. decrypted = "";
  118. var result = false;
  119. try
  120. {
  121. decrypted = Decrypt(cipherText, passPhrase);
  122. result = true;
  123. }
  124. catch (Exception e)
  125. {
  126. }
  127. return result;
  128. }
  129. public static byte[] Generate256BitsOfRandomEntropy()
  130. {
  131. var randomBytes = new byte[32]; // 32 Bytes will give us 256 bits.
  132. using (var rngCsp = new RNGCryptoServiceProvider())
  133. {
  134. // Fill the array with cryptographically secure random bytes.
  135. rngCsp.GetBytes(randomBytes);
  136. }
  137. return randomBytes;
  138. }
  139. public static string EncryptV2(string data, byte[] key)
  140. {
  141. if (TryEncryptV2(data, key, out var result, out var error))
  142. {
  143. return result;
  144. }
  145. throw new Exception(error);
  146. }
  147. public static bool TryEncryptV2(string data, byte[] key, [NotNullWhen(true)] out string? result, [NotNullWhen(false)] out string? error)
  148. {
  149. try
  150. {
  151. using var stream = new MemoryStream();
  152. using (var aes = Aes.Create())
  153. {
  154. aes.Key = key;
  155. var iv = aes.IV;
  156. stream.Write(iv, 0, iv.Length);
  157. using var cryptoStream = new CryptoStream(stream, aes.CreateEncryptor(), CryptoStreamMode.Write);
  158. using var writer = new StreamWriter(cryptoStream);
  159. writer.Write(data);
  160. }
  161. result = Convert.ToBase64String(stream.ToArray());
  162. error = null;
  163. return true;
  164. }
  165. catch(Exception e)
  166. {
  167. error = $"Encryption failed! {CoreUtils.FormatException(e)}";
  168. result = null;
  169. return false;
  170. }
  171. }
  172. public static string DecryptV2(string data, byte[] key)
  173. {
  174. if(TryDecryptV2(data, key, out var result, out var error))
  175. {
  176. return result;
  177. }
  178. throw new Exception(error);
  179. }
  180. public static bool TryDecryptV2(string data, byte[] key, [NotNullWhen(true)] out string? result, [NotNullWhen(false)] out string? error)
  181. {
  182. try
  183. {
  184. using var stream = new MemoryStream(Convert.FromBase64String(data));
  185. using var aes = Aes.Create();
  186. var iv = new byte[aes.IV.Length];
  187. if (stream.Read(iv, 0, iv.Length) != iv.Length)
  188. {
  189. error = $"Decryption failed! Data is too short for IV.";
  190. result = null;
  191. return false;
  192. }
  193. using var cryptoStream = new CryptoStream(stream, aes.CreateDecryptor(key, iv), CryptoStreamMode.Read);
  194. using var decryptReader = new StreamReader(cryptoStream);
  195. var decrypted = decryptReader.ReadToEnd();
  196. result = decrypted;
  197. error = null;
  198. return true;
  199. }
  200. catch(Exception e)
  201. {
  202. error = $"Decryption failed! {CoreUtils.FormatException(e)}";
  203. result = null;
  204. return false;
  205. }
  206. }
  207. }
  208. }