PageRenderTime 60ms CodeModel.GetById 34ms RepoModel.GetById 0ms app.codeStats 0ms

/Utilities/Cryptography/AES.cs

#
C# | 281 lines | 139 code | 17 blank | 125 comment | 0 complexity | e29374ff3d5f6a20af10f4ee18e51689 MD5 | raw file
Possible License(s): Apache-2.0
  1. using System;
  2. using System.IO;
  3. using System.Security.Cryptography;
  4. using Delta.Utilities.Helpers;
  5. namespace Delta.Utilities.Cryptography
  6. {
  7. /// <summary>
  8. /// AES Cryptography helper class to encrypt and decrypt binary data in a
  9. /// symmetric way (from both sides) with a key and a random seed value. The
  10. /// private key should be kept secret (e.g. only be transmitted once
  11. /// to the client or hidden via source code, so only the client with
  12. /// the key can decrypt) and the random seed value is usually transmitted
  13. /// from the client to the server. The random seed value can either be
  14. /// transmitted as a plain byte array (assuming the private key is kept
  15. /// secretly enough) or you can encrypt it with a public key using the
  16. /// <see cref="RSA"/> class to provide real security. In any case both sides
  17. /// can encrypt and decrypt data with good speed (see Performance tests in
  18. /// Delta.Utilities.Tests), but both need the exact same AES private key
  19. /// and seed value. Small messages also can have a slight overhead because
  20. /// all data is aligned by the seed size (16 bytes).
  21. /// <para />
  22. /// This class uses the AES Cryptography functionality of .NET, which works
  23. /// fine on all platforms the Delta Engine supports. A good article about it:
  24. /// http://msdn.microsoft.com/en-us/magazine/cc164055.aspx
  25. /// </summary>
  26. public class AES
  27. {
  28. #region Constants
  29. /// <summary>
  30. /// For AES Cryptography the key length can be 128, 192 or 256 bits.
  31. /// We choose 256 bits, which is 32 bytes. Note: 16 byte keys (128 bits)
  32. /// are about 10% faster, but it is not worth it. The AES algorithm is
  33. /// slow anyway and saving 10% CPU time is not going to matter.
  34. /// </summary>
  35. public const int KeyLength = 32;
  36. /// <summary>
  37. /// The seed value is always 16 bytes (128 bits) for AES cryptography.
  38. /// </summary>
  39. public const int SeedLength = 16;
  40. #endregion
  41. #region CreatePrivateKey (Static)
  42. /// <summary>
  43. /// Create private key for AES Cryptography in 256 bits, which is 32 bytes.
  44. /// </summary>
  45. /// <returns>New private key to be used</returns>
  46. public static byte[] CreatePrivateKey()
  47. {
  48. return RandomHelper.CreateRandomBytes(KeyLength);
  49. }
  50. #endregion
  51. #region Encrypt (Static)
  52. /// <summary>
  53. /// Encrypt input stream with given key using the AES crypto functions.
  54. /// Note: Both the length of the input stream and the IV key are saved
  55. /// into the stream for the Decrypt method. All you need to decrypt is
  56. /// the data stream and the privateKey. Since this is not very secure it is
  57. /// only used for testing, see <see cref="SocketHelper.SendMessageBytes"/>
  58. /// on a real use case, which uses the instance methods of this class.
  59. /// </summary>
  60. /// <param name="inputStream">Input stream of some data</param>
  61. /// <param name="privateKey">256 bit key (32 bytes)</param>
  62. /// <returns>Encrypted memory stream, which can be passed on for
  63. /// networking, authentication, saving files, etc.</returns>
  64. public static MemoryStream Encrypt(Stream inputStream, byte[] privateKey)
  65. {
  66. MemoryStream outputStream = new MemoryStream();
  67. try
  68. {
  69. using (Aes aes = new AesManaged())
  70. {
  71. aes.Key = privateKey;
  72. // 128 bit IV key value, which is 16 bytes (the only allowed value)
  73. aes.GenerateIV();
  74. //same our own way: cryptic.IV = RandomHelper.CreateRandomBytes(16);
  75. BinaryWriter writer = new BinaryWriter(outputStream);
  76. writer.Write(aes.IV);
  77. writer.Write((int)inputStream.Length);
  78. CryptoStream cs = new CryptoStream(outputStream,
  79. aes.CreateEncryptor(), CryptoStreamMode.Write);
  80. byte[] allBytes = new byte[inputStream.Length];
  81. inputStream.Position = 0;
  82. inputStream.Read(allBytes, 0, allBytes.Length);
  83. cs.Write(allBytes, 0, allBytes.Length);
  84. cs.FlushFinalBlock();
  85. }
  86. }
  87. catch (Exception ex)
  88. {
  89. Log.Warning("Unable to encrypt data with length " +
  90. inputStream.Length + " and key=" + privateKey.Write() +
  91. ": " + ex);
  92. }
  93. return outputStream;
  94. }
  95. #endregion
  96. #region Decrypt (Static)
  97. /// <summary>
  98. /// Decrypt encrypted data again (see the Encrypt method). Note: Both the
  99. /// length of the input stream and the IV seed are saved into the stream
  100. /// for this method. All you need to decrypt is the data stream and the
  101. /// privateKey. Since this is not very secure it is only used for testing,
  102. /// see <see cref="SocketHelper.SendMessageBytes"/> on a real use case,
  103. /// which uses the instance methods of this class.
  104. /// </summary>
  105. /// <param name="encryptedData">Data stream returned by Encrypt</param>
  106. /// <param name="privateKey">Private key for decryption (should be kept
  107. /// really private and never be transmitted). Please note that while this
  108. /// is pretty safe and without the private key it is very unlikely that
  109. /// anyone will decrypt this data, keeping the key really private is most
  110. /// likely impossible if it is published with your application. PGP is
  111. /// a much safer option (see links above), but it will make your
  112. /// application much more complex (which is too much for the Delta Engine
  113. /// cross platform compatibility, but ok for Windows only tools).
  114. /// </param>
  115. /// <returns>The original data that was encrypted before with Encrypt.
  116. /// This data can be used for network transmission, saving files or
  117. /// authentication data, etc.</returns>
  118. public static MemoryStream Decrypt(Stream encryptedData, byte[] privateKey)
  119. {
  120. byte[] allBytes;
  121. try
  122. {
  123. using (Aes aes = new AesManaged())
  124. {
  125. aes.Key = privateKey;
  126. byte[] ivKey = new byte[16];
  127. encryptedData.Position = 0;
  128. BinaryReader reader = new BinaryReader(encryptedData);
  129. aes.IV = reader.ReadBytes(16);
  130. int dataLength = reader.ReadInt32();
  131. using (CryptoStream cs = new CryptoStream(encryptedData,
  132. aes.CreateDecryptor(), CryptoStreamMode.Read))
  133. {
  134. allBytes = new byte[Math.Min(dataLength,
  135. encryptedData.Length - encryptedData.Position)];
  136. cs.Read(allBytes, 0, allBytes.Length);
  137. }
  138. }
  139. return new MemoryStream(allBytes);
  140. }
  141. catch (Exception ex)
  142. {
  143. Log.Warning("Unable to decrypt data with length " +
  144. encryptedData.Length + " and key=" + privateKey.Write() +
  145. ": " + ex);
  146. return new MemoryStream();
  147. }
  148. }
  149. #endregion
  150. #region Seed (Public)
  151. /// <summary>
  152. /// Get the random seed value (16 byte), which was either generated in
  153. /// the constructor or passed as an input parameter. Please note that
  154. /// both the private key and the salt value must be the same for encrypting
  155. /// and decrypting data.
  156. /// </summary>
  157. public byte[] Seed
  158. {
  159. get
  160. {
  161. return aes.IV;
  162. }
  163. }
  164. #endregion
  165. #region Private
  166. #region aes (Private)
  167. /// <summary>
  168. /// Keep an instance of the AES crypto class around, which remembers our
  169. /// private key (Key) and the seed value (IV). Using the instance methods
  170. /// of this class is much faster than using the static methods.
  171. /// </summary>
  172. private readonly AesCryptoServiceProvider aes;
  173. #endregion
  174. #endregion
  175. #region Constructors
  176. /// <summary>
  177. /// Create new AES Cryptography instance, which is faster than using the
  178. /// static methods in this class and it provides us with an easy way to
  179. /// transmit and use the seed (or also called salt) value the first time
  180. /// and then just use it every time we are encrypting or decrypting data.
  181. /// Note: Use the Seed property to get the generated random seed value.
  182. /// </summary>
  183. /// <param name="privateKey">Private key, which must be 32 bytes</param>
  184. public AES(byte[] privateKey)
  185. {
  186. aes = new AesCryptoServiceProvider();
  187. // This is the default padding mode, but we want to be sure it is the
  188. // same on all platforms
  189. aes.Padding = PaddingMode.PKCS7;
  190. // Private key, which must be 32 bytes here (16 and 24 bytes also work)
  191. aes.Key = privateKey;
  192. // 128 bit IV key value, which is 16 bytes (the only allowed value)
  193. aes.GenerateIV();
  194. }
  195. /// <summary>
  196. /// Create new AES Cryptography instance, which is faster than using the
  197. /// static methods in this class and it provides us with an easy way to
  198. /// transmit and use the salt value the first time and then just use it
  199. /// every time we are encrypting or decrypting something.
  200. /// </summary>
  201. /// <param name="privateKey">Private key, which must be 32 bytes</param>
  202. /// <param name="seedValue">Seed value, which must be 16 bytes
  203. /// and is usually transmitted from the server via networking (when not
  204. /// using any extra encryption like RSA)</param>
  205. public AES(byte[] privateKey, byte[] seedValue)
  206. {
  207. aes = new AesCryptoServiceProvider();
  208. // This is the default padding mode, but we want to be sure it is the
  209. // same on all platforms
  210. aes.Padding = PaddingMode.PKCS7;
  211. // Private key, which must be 32 bytes here (16 and 24 bytes also work)
  212. aes.Key = privateKey;
  213. // 128 bit IV key value, which is 16 bytes (the only allowed value)
  214. aes.IV = seedValue;
  215. }
  216. #endregion
  217. #region Encrypt (Public)
  218. /// <summary>
  219. /// Encrypt input data with the current Cryptography instance, to decrypt
  220. /// it the exact same instance with the same key and IV values is needed.
  221. /// </summary>
  222. /// <param name="inputData">Input data to encrypt</param>
  223. /// <returns>The encrypted data</returns>
  224. public byte[] Encrypt(byte[] inputData)
  225. {
  226. MemoryStream writerStream = new MemoryStream();
  227. using (CryptoStream cs = new CryptoStream(writerStream,
  228. aes.CreateEncryptor(), CryptoStreamMode.Write))
  229. {
  230. cs.Write(inputData, 0, inputData.Length);
  231. cs.FlushFinalBlock();
  232. return writerStream.ToArray();
  233. }
  234. }
  235. #endregion
  236. #region Decrypt (Public)
  237. /// <summary>
  238. /// Decrypt encrypted data again (see the Encrypt method). Note: The
  239. /// private key and the IV key must be transmitted and setup before
  240. /// this method is called. The length of the output data also must be
  241. /// known and should be transmitted before this block of encrypted data
  242. /// because the algorithm requires it to be 16 byte blocks, but our output
  243. /// data can be any size.
  244. /// </summary>
  245. /// <param name="encryptedData">Data stream returned by Encrypt</param>
  246. /// <param name="outputDataLength">Length of the output data (the
  247. /// encryptedData.Length might be 0-15 bytes bigger).</param>
  248. public byte[] Decrypt(byte[] encryptedData, int outputDataLength)
  249. {
  250. // Note: If you get a "Padding is invalid and cannot be removed." error
  251. // here it means that the decryptor (which needs to be re-created here
  252. // to work correctly) cannot find the correct padding. Make sure the
  253. // data is send in the same order and with this class!
  254. using (CryptoStream cs = new CryptoStream(new MemoryStream(
  255. encryptedData), aes.CreateDecryptor(), CryptoStreamMode.Read))
  256. {
  257. byte[] decryptedBytes = new byte[outputDataLength];
  258. cs.Read(decryptedBytes, 0, decryptedBytes.Length);
  259. return decryptedBytes;
  260. }
  261. }
  262. #endregion
  263. }
  264. }