PageRenderTime 74ms CodeModel.GetById 40ms app.highlight 10ms RepoModel.GetById 20ms app.codeStats 0ms

/Utilities/Cryptography/AES.cs

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