/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