#region Copyright 2010-2014 by Roger Knapp, Licensed under the Apache License, Version 2.0 /* Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #endregion using System; using System.Collections.Generic; using System.Security.Cryptography; using System.Text; using System.Reflection; using System.IO; using CSharpTest.Net.IO; using CSharpTest.Net.Utils; using CSharpTest.Net.Collections; using CSharpTest.Net.Interfaces; using Cryp = System.Security.Cryptography.CryptoStream; namespace CSharpTest.Net.Crypto { /// /// Provides AES-256 bit encryption using a global IV (Init vector) based on the current process' entry /// assembly. /// public class AESCryptoKey : CryptoKey { private static byte[] _iv; /// Creates a default IV for the crypto provider if AESCryptoKey.CryptoIV is not set private static byte[] DefaultIV() { System.Diagnostics.Trace.TraceWarning("The default IV has been deprecated, please set the AESCryptoKey.ProcessDefaultIV value."); Assembly asmKey = (Assembly.GetEntryAssembly() ?? Assembly.GetExecutingAssembly()); AssemblyName asmName = asmKey.GetName(); byte[] pk = Encoding.UTF8.GetBytes(asmName.Name); return Hash.MD5(pk).ToArray(); } /// /// Used to define the IV for AES keys created in this process, by default this is MD5(UTF8(Name)) where /// Name is the short-name of either the entry-point assembly, or "CSharpTest.Net.Library" if undefined. /// /// /// The process default IV is used with AESCryptoKey instances that are created without explicitly /// providing the IV value for the key. This is done internally when using the Password class' /// CreateKey(...), Encrypt(...), or Decrypt(...) methods. While this worked well enough for some /// programs, this has proven to be a flawed approach as the entry-point assembly can change. For example /// if another .NET process call Assembly.Execute() on your executable. /// /// Applications are advised that they should capture the existing value and store that in App.Config, /// and set the following prior to using this class, or the Password class. The entry-points related /// to this that have been marked Obsolete() will be removed in the long-term and by capturing this /// value and manually using it you can ensure your application will continue to function properly. /// public static byte[] ProcessDefaultIV { get { return (byte[])(_iv ?? (_iv = DefaultIV())).Clone(); } set { _iv = Check.ArraySize(value, 16, 16); } } readonly SymmetricAlgorithm _key; /// Creates a new key public AESCryptoKey() { _key = new RijndaelManaged(); _key.Padding = PaddingMode.PKCS7; _key.KeySize = 256; _key.IV = _iv ?? ProcessDefaultIV; _key.GenerateKey(); } /// Creates an object representing the specified key [Obsolete("Please use the overload that accepts IV bytes.")] public AESCryptoKey(byte[] key) { _key = new RijndaelManaged(); _key.Padding = PaddingMode.PKCS7; _key.KeySize = 256; _key.IV = _iv ?? ProcessDefaultIV; _key.Key = Check.ArraySize(key, 32, 32); } /// Creates an object representing the specified key and init vector public AESCryptoKey(byte[] key, byte[] iv) { _key = new RijndaelManaged(); _key.Padding = PaddingMode.PKCS7; _key.KeySize = 256; _key.Key = Check.ArraySize(key, 32, 32); _key.IV = Check.ArraySize(iv, 16, 16); } /// /// Serializes the KEY and IV to a single array of bytes. Use FromByteArray() to restore. /// public static AESCryptoKey FromBytes(byte[] serializedBytes) { Check.ArraySize(serializedBytes, 48, 48); byte[] key = new byte[32], iv = new byte[16]; Buffer.BlockCopy(serializedBytes, 0, key, 0, 32); Buffer.BlockCopy(serializedBytes, 32, iv, 0, 16); return new AESCryptoKey(key, iv); } /// Returns the algorithm key or throws ObjectDisposedException [System.Diagnostics.DebuggerNonUserCode] protected SymmetricAlgorithm Algorithm { get { return Assert(_key); } } /// Disposes of the key protected override void Dispose(bool disposing) { _key.Clear(); base.Dispose(disposing); } /// /// Serializes the KEY and IV to a single array of bytes. Use FromByteArray() to restore. /// public byte[] ToArray() { byte[] result = new byte[32 + 16]; Buffer.BlockCopy(Key, 0, result, 0, 32); Buffer.BlockCopy(IV, 0, result, 32, 16); return result; } /// Returns the AES 256 bit key this object was created with public byte[] Key { get { return Algorithm.Key; } } /// Returns the AES 256 bit key this object was created with public byte[] IV { get { return Algorithm.IV; } } /// Encrypts a stream of data public override Stream Encrypt(Stream stream) { try { ICryptoTransform xform = Algorithm.CreateEncryptor(); return new DisposingStream(new CryptoStream(stream, xform, CryptoStreamMode.Write)) .WithDisposeOf(xform); } catch (InvalidOperationException) { throw; } catch { throw CryptographicException(); } } /// Decrypts a stream of data public override Stream Decrypt(Stream stream) { try { ICryptoTransform xform = Algorithm.CreateDecryptor(); return new DisposingStream(new CryptoStream(stream, xform, CryptoStreamMode.Read)) .WithDisposeOf(xform); } catch (InvalidOperationException) { throw; } catch { throw CryptographicException(); } } /// Encrypts a raw data block as a set of bytes public override byte[] Encrypt(byte[] blob) { try { using (MemoryStream ms = new MemoryStream()) { using (Stream io = Encrypt(new NonClosingStream(ms))) io.Write(blob, 0, blob.Length); return ms.ToArray(); } } catch (InvalidOperationException) { throw; } catch { throw CryptographicException(); } } /// Decrypts a raw data block as a set of bytes public override byte[] Decrypt(byte[] blob) { try { using (MemoryStream ms = new MemoryStream()) { using (Stream io = Decrypt(new MemoryStream(blob))) IOStream.CopyStream(io, ms); return ms.ToArray(); } } catch (InvalidOperationException) { throw; } catch { throw CryptographicException(); } } } }