/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
- using System;
- using System.IO;
- using System.Security.Cryptography;
- using Delta.Utilities.Helpers;
-
- namespace Delta.Utilities.Cryptography
- {
- /// <summary>
- /// AES Cryptography helper class to encrypt and decrypt binary data in a
- /// symmetric way (from both sides) with a key and a random seed value. The
- /// private key should be kept secret (e.g. only be transmitted once
- /// to the client or hidden via source code, so only the client with
- /// the key can decrypt) and the random seed value is usually transmitted
- /// from the client to the server. The random seed value can either be
- /// transmitted as a plain byte array (assuming the private key is kept
- /// secretly enough) or you can encrypt it with a public key using the
- /// <see cref="RSA"/> class to provide real security. In any case both sides
- /// can encrypt and decrypt data with good speed (see Performance tests in
- /// Delta.Utilities.Tests), but both need the exact same AES private key
- /// and seed value. Small messages also can have a slight overhead because
- /// all data is aligned by the seed size (16 bytes).
- /// <para />
- /// This class uses the AES Cryptography functionality of .NET, which works
- /// fine on all platforms the Delta Engine supports. A good article about it:
- /// http://msdn.microsoft.com/en-us/magazine/cc164055.aspx
- /// </summary>
- public class AES
- {
- #region Constants
- /// <summary>
- /// For AES Cryptography the key length can be 128, 192 or 256 bits.
- /// We choose 256 bits, which is 32 bytes. Note: 16 byte keys (128 bits)
- /// are about 10% faster, but it is not worth it. The AES algorithm is
- /// slow anyway and saving 10% CPU time is not going to matter.
- /// </summary>
- public const int KeyLength = 32;
-
- /// <summary>
- /// The seed value is always 16 bytes (128 bits) for AES cryptography.
- /// </summary>
- public const int SeedLength = 16;
- #endregion
-
- #region CreatePrivateKey (Static)
- /// <summary>
- /// Create private key for AES Cryptography in 256 bits, which is 32 bytes.
- /// </summary>
- /// <returns>New private key to be used</returns>
- public static byte[] CreatePrivateKey()
- {
- return RandomHelper.CreateRandomBytes(KeyLength);
- }
- #endregion
-
- #region Encrypt (Static)
- /// <summary>
- /// Encrypt input stream with given key using the AES crypto functions.
- /// Note: Both the length of the input stream and the IV key are saved
- /// into the stream for the Decrypt method. All you need to decrypt is
- /// the data stream and the privateKey. Since this is not very secure it is
- /// only used for testing, see <see cref="SocketHelper.SendMessageBytes"/>
- /// on a real use case, which uses the instance methods of this class.
- /// </summary>
- /// <param name="inputStream">Input stream of some data</param>
- /// <param name="privateKey">256 bit key (32 bytes)</param>
- /// <returns>Encrypted memory stream, which can be passed on for
- /// networking, authentication, saving files, etc.</returns>
- public static MemoryStream Encrypt(Stream inputStream, byte[] privateKey)
- {
- MemoryStream outputStream = new MemoryStream();
- try
- {
- using (Aes aes = new AesManaged())
- {
- aes.Key = privateKey;
- // 128 bit IV key value, which is 16 bytes (the only allowed value)
- aes.GenerateIV();
- //same our own way: cryptic.IV = RandomHelper.CreateRandomBytes(16);
-
- BinaryWriter writer = new BinaryWriter(outputStream);
- writer.Write(aes.IV);
- writer.Write((int)inputStream.Length);
-
- CryptoStream cs = new CryptoStream(outputStream,
- aes.CreateEncryptor(), CryptoStreamMode.Write);
- byte[] allBytes = new byte[inputStream.Length];
- inputStream.Position = 0;
- inputStream.Read(allBytes, 0, allBytes.Length);
- cs.Write(allBytes, 0, allBytes.Length);
- cs.FlushFinalBlock();
- }
- }
- catch (Exception ex)
- {
- Log.Warning("Unable to encrypt data with length " +
- inputStream.Length + " and key=" + privateKey.Write() +
- ": " + ex);
- }
- return outputStream;
- }
- #endregion
-
- #region Decrypt (Static)
- /// <summary>
- /// Decrypt encrypted data again (see the Encrypt method). Note: Both the
- /// length of the input stream and the IV seed are saved into the stream
- /// for this method. All you need to decrypt is the data stream and the
- /// privateKey. Since this is not very secure it is only used for testing,
- /// see <see cref="SocketHelper.SendMessageBytes"/> on a real use case,
- /// which uses the instance methods of this class.
- /// </summary>
- /// <param name="encryptedData">Data stream returned by Encrypt</param>
- /// <param name="privateKey">Private key for decryption (should be kept
- /// really private and never be transmitted). Please note that while this
- /// is pretty safe and without the private key it is very unlikely that
- /// anyone will decrypt this data, keeping the key really private is most
- /// likely impossible if it is published with your application. PGP is
- /// a much safer option (see links above), but it will make your
- /// application much more complex (which is too much for the Delta Engine
- /// cross platform compatibility, but ok for Windows only tools).
- /// </param>
- /// <returns>The original data that was encrypted before with Encrypt.
- /// This data can be used for network transmission, saving files or
- /// authentication data, etc.</returns>
- public static MemoryStream Decrypt(Stream encryptedData, byte[] privateKey)
- {
- byte[] allBytes;
- try
- {
- using (Aes aes = new AesManaged())
- {
- aes.Key = privateKey;
- byte[] ivKey = new byte[16];
- encryptedData.Position = 0;
- BinaryReader reader = new BinaryReader(encryptedData);
- aes.IV = reader.ReadBytes(16);
- int dataLength = reader.ReadInt32();
-
- using (CryptoStream cs = new CryptoStream(encryptedData,
- aes.CreateDecryptor(), CryptoStreamMode.Read))
- {
- allBytes = new byte[Math.Min(dataLength,
- encryptedData.Length - encryptedData.Position)];
- cs.Read(allBytes, 0, allBytes.Length);
- }
- }
- return new MemoryStream(allBytes);
- }
- catch (Exception ex)
- {
- Log.Warning("Unable to decrypt data with length " +
- encryptedData.Length + " and key=" + privateKey.Write() +
- ": " + ex);
- return new MemoryStream();
- }
- }
- #endregion
-
- #region Seed (Public)
- /// <summary>
- /// Get the random seed value (16 byte), which was either generated in
- /// the constructor or passed as an input parameter. Please note that
- /// both the private key and the salt value must be the same for encrypting
- /// and decrypting data.
- /// </summary>
- public byte[] Seed
- {
- get
- {
- return aes.IV;
- }
- }
- #endregion
-
- #region Private
-
- #region aes (Private)
- /// <summary>
- /// Keep an instance of the AES crypto class around, which remembers our
- /// private key (Key) and the seed value (IV). Using the instance methods
- /// of this class is much faster than using the static methods.
- /// </summary>
- private readonly AesCryptoServiceProvider aes;
- #endregion
-
- #endregion
-
- #region Constructors
- /// <summary>
- /// Create new AES Cryptography instance, which is faster than using the
- /// static methods in this class and it provides us with an easy way to
- /// transmit and use the seed (or also called salt) value the first time
- /// and then just use it every time we are encrypting or decrypting data.
- /// Note: Use the Seed property to get the generated random seed value.
- /// </summary>
- /// <param name="privateKey">Private key, which must be 32 bytes</param>
- public AES(byte[] privateKey)
- {
- aes = new AesCryptoServiceProvider();
- // This is the default padding mode, but we want to be sure it is the
- // same on all platforms
- aes.Padding = PaddingMode.PKCS7;
- // Private key, which must be 32 bytes here (16 and 24 bytes also work)
- aes.Key = privateKey;
- // 128 bit IV key value, which is 16 bytes (the only allowed value)
- aes.GenerateIV();
- }
-
- /// <summary>
- /// Create new AES Cryptography instance, which is faster than using the
- /// static methods in this class and it provides us with an easy way to
- /// transmit and use the salt value the first time and then just use it
- /// every time we are encrypting or decrypting something.
- /// </summary>
- /// <param name="privateKey">Private key, which must be 32 bytes</param>
- /// <param name="seedValue">Seed value, which must be 16 bytes
- /// and is usually transmitted from the server via networking (when not
- /// using any extra encryption like RSA)</param>
- public AES(byte[] privateKey, byte[] seedValue)
- {
- aes = new AesCryptoServiceProvider();
- // This is the default padding mode, but we want to be sure it is the
- // same on all platforms
- aes.Padding = PaddingMode.PKCS7;
- // Private key, which must be 32 bytes here (16 and 24 bytes also work)
- aes.Key = privateKey;
- // 128 bit IV key value, which is 16 bytes (the only allowed value)
- aes.IV = seedValue;
- }
- #endregion
-
- #region Encrypt (Public)
- /// <summary>
- /// Encrypt input data with the current Cryptography instance, to decrypt
- /// it the exact same instance with the same key and IV values is needed.
- /// </summary>
- /// <param name="inputData">Input data to encrypt</param>
- /// <returns>The encrypted data</returns>
- public byte[] Encrypt(byte[] inputData)
- {
- MemoryStream writerStream = new MemoryStream();
- using (CryptoStream cs = new CryptoStream(writerStream,
- aes.CreateEncryptor(), CryptoStreamMode.Write))
- {
- cs.Write(inputData, 0, inputData.Length);
- cs.FlushFinalBlock();
- return writerStream.ToArray();
- }
- }
- #endregion
-
- #region Decrypt (Public)
- /// <summary>
- /// Decrypt encrypted data again (see the Encrypt method). Note: The
- /// private key and the IV key must be transmitted and setup before
- /// this method is called. The length of the output data also must be
- /// known and should be transmitted before this block of encrypted data
- /// because the algorithm requires it to be 16 byte blocks, but our output
- /// data can be any size.
- /// </summary>
- /// <param name="encryptedData">Data stream returned by Encrypt</param>
- /// <param name="outputDataLength">Length of the output data (the
- /// encryptedData.Length might be 0-15 bytes bigger).</param>
- public byte[] Decrypt(byte[] encryptedData, int outputDataLength)
- {
- // Note: If you get a "Padding is invalid and cannot be removed." error
- // here it means that the decryptor (which needs to be re-created here
- // to work correctly) cannot find the correct padding. Make sure the
- // data is send in the same order and with this class!
- using (CryptoStream cs = new CryptoStream(new MemoryStream(
- encryptedData), aes.CreateDecryptor(), CryptoStreamMode.Read))
- {
- byte[] decryptedBytes = new byte[outputDataLength];
- cs.Read(decryptedBytes, 0, decryptedBytes.Length);
- return decryptedBytes;
- }
- }
- #endregion
- }
- }
-