//****************************************************************************************************** // Cipher.cs - Gbtc // // Copyright © 2012, Grid Protection Alliance. All Rights Reserved. // // Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See // the NOTICE file distributed with this work for additional information regarding copyright ownership. // The GPA licenses this file to you under the MIT License (MIT), 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.opensource.org/licenses/MIT // // Unless agreed to in writing, the subject software distributed under the License is distributed on an // "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the // License for the specific language governing permissions and limitations. // // Code Modification History: // ---------------------------------------------------------------------------------------------------- // 11/12/2003 - J. Ritchie Carroll // Generated original version of source code. // 01/04/2006 - J. Ritchie Carroll // Migrated 2.0 version of source code from 1.1 source (GSF.Shared.Crypto). // 02/28/2007 - J. Ritchie Carroll // Changed string-based encrypt and decrypt functions to return null if // input string to be encrypted or decrypted was null or empty. // 10/11/2007 - J. Ritchie Carroll // Added Obfuscate and De-obfuscate functions that perform data obfuscation // based upon simple bit-rotation algorithms. // 12/13/2007 - Darrell Zuercher // Edited code comments. // 09/19/2008 - J. Ritchie Carroll // Converted to C# - basic encryption/decryption extends string, byte[], and Stream. // 08/10/2009 - Josh L. Patterson // Edited Comments. // 09/14/2009 - Stephen C. Wills // Added new header and license agreement. // 10/05/2009 - J. Ritchie Carroll // Switched to AES only encryption/decryption using machine specific local key cache. // 07/13/2010 - Stephen C. Wills // Added a file watcher to reload the KeyIV table when an external // process modifies the cache file. // 03/17/2011 - J. Ritchie Carroll // Modified key and initialization vector cache to be able to operate in a non-elevated mode. // 04/06/2011 - J. Ritchie Carroll // Added FlushCache() method to wait for pending key/IV cache serializations for applications // with a short life-cycle. // 04/14/2011 - Pinal C. Patel // Updated to use new serialization and deserialization methods in GSF.Serialization class. // 06/10/2011 - Pinal C. Patel // Renamed RetryDelayInterval and MaximumRetryAttempts settings persisted to the config file // to CacheRetryDelayInterval and CacheMaximumRetryAttempts for clarity. // 06/30/2011 - Stephen C. Wills - applying changes from Jian (Ryan) Zuo // Added ManagedEncryption setting to the config file to allow the user to switch to // wrappers over FIPS-compliant algorithms. // 07/05/2011 - Stephen C. Wills // Removed config file setting for FIPS compliance. Checks the registry instead. // 07/13/2011 - Stephen C. Wills // Modified check for FIPS compliance to work with Windows XP and Windows Server 2003. // 02/17/2012 - Stephen C. Wills // Added public method to manually reload the key cache. // 12/14/2012 - Starlynn Danyelle Gilliam // Modified Header. // //****************************************************************************************************** using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Security.Cryptography; using System.Text; using System.Threading; using GSF.Collections; using GSF.Configuration; using GSF.IO; #if !MONO using Microsoft.Win32; #endif namespace GSF.Security.Cryptography { #region [ Enumerations ] /// /// Cryptographic strength enumeration. /// [SuppressMessage("Microsoft.Design", "CA1027:MarkEnumsWithFlags")] public enum CipherStrength { /// Uses no encryption. None = 0, /// Uses AES 128-bit encryption. Aes128 = 128, /// Uses AES 256-bit encryption. Aes256 = 256 } #endregion /// /// Provides general use cryptographic functions. /// /// /// This class exists to simplify usage of basic cryptography functionality. /// public static partial class Cipher { // Constants private const int KeyIndex = 0; private const int IVIndex = 1; // Default key and initialization vector cache file name private const string DefaultCacheFileName = "KeyIVCache.bin"; // Default maximum retry attempts allowed for loading cryptographic key and initialization vector cache private const int DefaultMaximumRetryAttempts = 5; // Default wait interval, in milliseconds, before retrying load of cryptographic key and initialization vector cache private const double DefaultRetryDelayInterval = 1000.0D; // The standard settings category for cryptography information private const string CryptoServicesSettingsCategory = "CryptographyServices"; /// /// Represents an inter-process serializable cryptographic key and initialization vector cache. /// private class KeyIVCache : InterprocessCache { #region [ Members ] // Internal key and initialization vector table private Dictionary m_keyIVTable = new Dictionary(); private readonly object m_keyIVTableLock = new object(); // Flag to choose between AesManaged and AesCryptoServiceProvider for encryption private bool m_managedEncryption; #endregion #region [ Properties ] /// /// Gets a copy of the internal key and initialization vector table. /// private Dictionary KeyIVTable { get { Dictionary keyIVTable; // We wait until the key and IV cache is loaded before attempting to access it WaitForDataReady(); // Wait for thread level lock on key table lock (m_keyIVTableLock) { // Make a copy of the keyIV table for external use keyIVTable = new Dictionary(m_keyIVTable); } return keyIVTable; } } /// /// Gets or sets a boolean value that determines whether to use AesManaged /// or the CAPI wrapper (AesCryptoServiceProvider) for encryption. /// public bool ManagedEncryption { get { return m_managedEncryption; } set { m_managedEncryption = value; } } #endregion #region [ Methods ] /// /// Gets the crypto key and initialization vector for the given user password. /// /// User password used for key lookup. /// Specifies the desired key size. /// Crypto key, index 0, and initialization vector, index 1, for the given user password. public byte[][] GetCryptoKeyIV(string password, int keySize) { string hash = GetPasswordHash(password, keySize); byte[][] keyIV; byte[] key, iv; bool addedKey = false; // We wait until the key and IV cache is loaded before attempting to access it WaitForDataReady(); // Wait for thread level lock on key table lock (m_keyIVTableLock) { // Lookup crypto key based on password hash in persisted key table if (!m_keyIVTable.TryGetValue(hash, out keyIV)) { Aes symmetricAlgorithm; // Key for password hash doesn't exist, create a new one if (m_managedEncryption) { symmetricAlgorithm = new AesManaged(); } else { // Switch from AesManaged to AesCryptoServiceProvider complying with FIPS symmetricAlgorithm = new AesCryptoServiceProvider(); } symmetricAlgorithm.KeySize = keySize; symmetricAlgorithm.GenerateKey(); symmetricAlgorithm.GenerateIV(); key = symmetricAlgorithm.Key; iv = symmetricAlgorithm.IV; keyIV = new[] { key, iv }; // Add new crypto key to key table m_keyIVTable.Add(hash, keyIV); addedKey = true; } } // Queue up a serialization for any newly added key if (addedKey) Save(); return keyIV; } /// /// Determines if a key and initialization vector exists for the given . /// /// User password used for key lookups. /// Specifies the desired key size. /// true if a key and initialization vector exists for the given ; otherwise false. // ReSharper disable once MemberHidesStaticFromOuterClass public bool KeyIVExists(string password, int keySize) { string hash = GetPasswordHash(password, keySize); // We wait until the key and IV cache is loaded before attempting to access it WaitForDataReady(); // Wait for thread level lock on key table lock (m_keyIVTableLock) { // Lookup crypto key based on password hash in persisted key table return m_keyIVTable.ContainsKey(hash); } } /// /// Imports a key and initialization vector into the local system key cache. /// /// User password used for key lookups. /// Specifies the desired key size. /// Text based key and initialization vector to import into local key cache. /// /// This method is used to manually import a key created on another computer. /// // ReSharper disable once MemberHidesStaticFromOuterClass public void ImportKeyIV(string password, int keySize, string keyIVText) { string hash = GetPasswordHash(password, keySize); // We wait until the key and IV cache is loaded before attempting to access it WaitForDataReady(); // Wait for thread level lock on key table lock (m_keyIVTableLock) { string[] keyIV = keyIVText.Split('|'); byte[] key = Convert.FromBase64String(keyIV[KeyIndex]); byte[] iv = Convert.FromBase64String(keyIV[IVIndex]); // Assign new crypto key to key table m_keyIVTable[hash] = new[] { key, iv }; } // Queue up a serialization for this new key Save(); } /// /// Exports a key and initialization vector from the local system key cache. /// /// User password used for key lookup. /// Specifies the desired key size. /// Text based key and initialization vector exported from local key cache. /// /// This method is used to manually export a key to be installed on another computer. /// // ReSharper disable once MemberHidesStaticFromOuterClass public string ExportKeyIV(string password, int keySize) { byte[][] keyIV = GetCryptoKeyIV(password, keySize); return string.Concat(Convert.ToBase64String(keyIV[KeyIndex]), "|", Convert.ToBase64String(keyIV[IVIndex])); } /// /// Merge keys and initialization vectors from another , local cache taking precedence. /// /// Other to merge with. public void MergeLeft(KeyIVCache other) { // Merge other keys into local ones Dictionary mergedKeyIVTable = KeyIVTable.Merge(other.KeyIVTable); // Wait for thread level lock on key table lock (m_keyIVTableLock) { // Replace local key IV table with merged items m_keyIVTable = mergedKeyIVTable; } // Queue up a serialization for any newly added keys Save(); } /// /// Merge keys and initialization vectors from another , other cache taking precedence. /// /// Other to merge with. public void MergeRight(KeyIVCache other) { // Merge other keys into local ones Dictionary mergedKeyIVTable = other.KeyIVTable.Merge(KeyIVTable); // Wait for thread level lock on key table lock (m_keyIVTableLock) { // Replace local key IV table with merged items m_keyIVTable = mergedKeyIVTable; } // Queue up a serialization for any newly added keys Save(); } /// /// Initiates inter-process synchronized save of key and initialization vector table. /// public override void Save() { byte[] serializedKeyIVTable; // Wait for thread level lock on key table lock (m_keyIVTableLock) { serializedKeyIVTable = Serialization.Serialize(m_keyIVTable, SerializationFormat.Binary); } // File data is the serialized Key/IV cache, assignment will initiate auto-save if needed FileData = serializedKeyIVTable; } /// /// Handles serialization of file to disk; virtual method allows customization (e.g., pre-save encryption and/or data merge). /// /// used to serialize data. /// File data to be serialized. /// /// Consumers overriding this method should not directly call property to avoid potential dead-locks. /// protected override void SaveFileData(FileStream fileStream, byte[] fileData) { // Encrypt data local to this machine (this way user cannot copy key cache to another machine) base.SaveFileData(fileStream, ProtectedData.Protect(fileData, null, DataProtectionScope.LocalMachine)); } /// /// Handles deserialization of file from disk; virtual method allows customization (e.g., pre-load decryption and/or data merge). /// /// used to deserialize data. /// Deserialized file data. /// /// Consumers overriding this method should not directly call property to avoid potential dead-locks. /// protected override byte[] LoadFileData(FileStream fileStream) { // Decrypt data that was encrypted local to this machine byte[] serializedKeyIVTable = ProtectedData.Unprotect(fileStream.ReadStream(), null, DataProtectionScope.LocalMachine); Dictionary keyIVTable = Serialization.Deserialize>(serializedKeyIVTable, SerializationFormat.Binary); // Wait for thread level lock on key table lock (m_keyIVTableLock) { // Merge new and existing key tables since new keys may have been queued for serialization, but not saved yet m_keyIVTable = keyIVTable.Merge(m_keyIVTable); } return serializedKeyIVTable; } // Waits until the key and IV cache is loaded before attempting to access it private void WaitForDataReady() { try { // Just wrapping this method to provide a more detailed exception message if there is an issue loading cache WaitForLoad(); } catch (Exception ex) { throw new UnauthorizedAccessException("Cryptographic cipher failure: timeout while attempting to load cryptographic key cache.", ex); } } #endregion } // Primary cryptographic key and initialization vector cache. private static KeyIVCache GlobalKeyIVCache => s_keyIVCache.Value; private static Lazy s_keyIVCache; // Set default encoding base Base64 strings private static Encoding s_textEncoding; /// /// Static constructor continuation for the class. /// static partial void OnCreated() { #if MONO s_textEncoding = Encoding.Default; #else s_textEncoding = Encoding.Unicode; #endif KeyIVCache localKeyIVCache; EnabledState useFIPSModules = EnabledState.Auto; string localCacheFileName = DefaultCacheFileName; double retryDelayInterval = DefaultRetryDelayInterval; int maximumRetryAttempts = DefaultMaximumRetryAttempts; // Load cryptographic settings ConfigurationFile config = ConfigurationFile.Current; CategorizedSettingsElementCollection settings = config.Settings[CryptoServicesSettingsCategory]; settings.Add("UseFIPSModules", useFIPSModules, "Determines if security functions should use FIPS cryptographic modules. Value is one of \"Auto\", \"On\" or \"Off\". Defaults to \"Auto\" which reads FIPS setting from registry."); settings.Add("CryptoCache", localCacheFileName, "Path and file name of cryptographic key and initialization vector cache."); settings.Add("CacheRetryDelayInterval", retryDelayInterval, "Wait interval, in milliseconds, before retrying load of cryptographic key and initialization vector cache."); settings.Add("CacheMaximumRetryAttempts", maximumRetryAttempts, "Maximum retry attempts allowed for loading cryptographic key and initialization vector cache."); useFIPSModules = settings["UseFIPSModules"].ValueAs(useFIPSModules); localCacheFileName = FilePath.GetAbsolutePath(settings["CryptoCache"].ValueAs(localCacheFileName)); retryDelayInterval = settings["CacheRetryDelayInterval"].ValueAs(retryDelayInterval); maximumRetryAttempts = settings["CacheMaximumRetryAttempts"].ValueAs(maximumRetryAttempts); if (useFIPSModules != EnabledState.Auto) s_managedEncryption = useFIPSModules == EnabledState.Off; // FIPS Off means use managed encryption s_keyIVCache = new Lazy(() => { KeyIVCache globalKeyIVCache; // Initialize local cryptographic key and initialization vector cache (application may only have read-only access to this cache) localKeyIVCache = new KeyIVCache { FileName = localCacheFileName, RetryDelayInterval = retryDelayInterval, MaximumRetryAttempts = maximumRetryAttempts, ManagedEncryption = s_managedEncryption, #if DNF45 && !MONO ReloadOnChange = true, #else // Reload on change is disabled to eliminate GC handle leaks on .NET 4.0, this prevents // automatic runtime reloading of key/iv data cached by another application. ReloadOnChange = false, #endif AutoSave = false }; // Load initial keys localKeyIVCache.Load(); try { // Validate that user has write access to the local cryptographic cache folder string tempFile = FilePath.GetDirectoryName(localCacheFileName) + Guid.NewGuid() + ".tmp"; using (File.Create(tempFile)) { } if (File.Exists(tempFile)) File.Delete(tempFile); // No access issues exist, use local cache as the primary cryptographic key and initialization vector cache globalKeyIVCache = localKeyIVCache; globalKeyIVCache.AutoSave = true; localKeyIVCache = null; } catch (UnauthorizedAccessException) { // User does not have needed serialization access to common cryptographic cache folder, use a path where user will have rights string userCacheFolder = FilePath.AddPathSuffix(FilePath.GetApplicationDataFolder()); string userCacheFileName = userCacheFolder + FilePath.GetFileName(localCacheFileName); // Make sure user directory exists if (!Directory.Exists(userCacheFolder)) Directory.CreateDirectory(userCacheFolder); // Copy existing common cryptographic cache if none exists if (File.Exists(localCacheFileName) && !File.Exists(userCacheFileName)) File.Copy(localCacheFileName, userCacheFileName); // Initialize primary cryptographic key and initialization vector cache within user folder globalKeyIVCache = new KeyIVCache { FileName = userCacheFileName, RetryDelayInterval = retryDelayInterval, MaximumRetryAttempts = maximumRetryAttempts, #if DNF45 && !MONO ReloadOnChange = true, #else // Reload on change is disabled to eliminate GC handle leaks on .NET 4.0, this prevents // automatic runtime reloading of key/iv data cached by another application. ReloadOnChange = false, #endif AutoSave = true }; // Load initial keys globalKeyIVCache.Load(); // Merge new or updated keys, protected folder keys taking precedence over user keys globalKeyIVCache.MergeRight(localKeyIVCache); } if ((object)localKeyIVCache != null) localKeyIVCache.Dispose(); return globalKeyIVCache; }); } /// /// Blocks current thread and waits for any pending save of local system key cache to complete. /// /// The number of milliseconds to wait, or (-1) to wait indefinitely. /// /// /// This method only needs to be used if crypto cache changes could be pending during application shutdown (i.e., executing ciphers with /// new keys that have not been saved, using existing keys does not queue crypto cache updates) to ensure keys are flushed before exit. /// /// /// For most applications it is expected that this method would be rarely needed. However, possible usage scenarios would include:
/// /// /// /// Writing an application that establishes crypto keys where application lifetime would be very short (i.e., run, create keys, exit). /// /// /// /// /// Creating new crypto keys during application shutdown (i.e., performing ciphers with non-existing keys at shutdown). /// /// /// ///
///
public static void FlushCache(int millisecondsTimeout = Timeout.Infinite) { GlobalKeyIVCache.WaitForSave(millisecondsTimeout); } /// /// Manually loads keys into the local system key cache. /// public static void ReloadCache() { ConfigurationFile config; CategorizedSettingsElementCollection settings; string commonCacheFileName = string.Empty; double retryDelayInterval = 0.0; int maximumRetryAttempts = 0; // Load the system key cache GlobalKeyIVCache.Load(); // Load cryptographic settings config = ConfigurationFile.Current; settings = config.Settings[CryptoServicesSettingsCategory]; commonCacheFileName = FilePath.GetAbsolutePath(settings["CryptoCache"].ValueAs(commonCacheFileName)); if (commonCacheFileName != GlobalKeyIVCache.FileName) { // System key cache is not loaded from common cache folder. // We need to merge the common key cache with the system key cache. retryDelayInterval = settings["CacheRetryDelayInterval"].ValueAs(retryDelayInterval); maximumRetryAttempts = settings["CacheMaximumRetryAttempts"].ValueAs(maximumRetryAttempts); // Initialize local cryptographic key and initialization vector cache (application may only have read-only access to this cache) using (KeyIVCache commonKeyIVCache = new KeyIVCache { FileName = commonCacheFileName, RetryDelayInterval = retryDelayInterval, MaximumRetryAttempts = maximumRetryAttempts, ManagedEncryption = s_managedEncryption, ReloadOnChange = false, AutoSave = false }) { // Load initial keys commonKeyIVCache.Load(); // Merge new or updated keys, common cache folder keys taking precedence GlobalKeyIVCache.MergeRight(commonKeyIVCache); } } } /// /// Determines if a key and initialization vector exists for the given in the local system key cache. /// /// User password used for key lookups. /// Specifies the desired key size. /// true if a key and initialization vector exists for the given ; otherwise false. public static bool KeyIVExists(string password, int keySize) { return GlobalKeyIVCache.KeyIVExists(password, keySize); } /// /// Imports a key and initialization vector into the local system key cache. /// /// User password used for key lookups. /// Specifies the desired key size. /// Text based key and initialization vector to import into local key cache. /// /// This method is used to manually import a key created on another computer. /// public static void ImportKeyIV(string password, int keySize, string keyIVText) { GlobalKeyIVCache.ImportKeyIV(password, keySize, keyIVText); } /// /// Exports a key and initialization vector from the local system key cache. /// /// User password used for key lookup. /// Specifies the desired key size. /// Text based key and initialization vector exported from local key cache. /// /// This method is used to manually export a key to be installed on another computer. /// public static string ExportKeyIV(string password, int keySize) { return GlobalKeyIVCache.ExportKeyIV(password, keySize); } /// /// Gets the Base64 encoded SHA-256 hash of given user password. /// /// User password to get hash for. /// Specifies the desired category ID. /// Base64 encoded SHA-256 hash of user password. /// /// The optional will be appended to the to allow /// the same password to be used in different contexts and return different results, when useful. /// public static string GetPasswordHash(string password, int categoryID = 0) { // TODO: Switch to better hashing algorithm: // https://paragonie.com/blog/2015/04/secure-authentication-php-with-long-term-persistence#title.2 string hash; // Null password should produce a null hash if ((object)password == null) return null; // Suffix password with category ID (key size) since same password may be in use for different category IDs password += categoryID.ToString(); // Password hash doesn't exist, create one if (s_managedEncryption) { hash = Convert.ToBase64String(new SHA256Managed().ComputeHash(Encoding.Default.GetBytes(password))); } else { // Switch from SHA256Managed to SHA256CryptoServiceProvider complying with FIPS // http://msdn.microsoft.com/en-us/library/system.security.cryptography.sha256cryptoserviceprovider.aspx // http://msdn.microsoft.com/en-us/library/system.security.cryptography.sha256managed.sha256managed.aspx hash = Convert.ToBase64String(new SHA256CryptoServiceProvider().ComputeHash(Encoding.Default.GetBytes(password))); } return hash; } /// /// Returns a Base64 encoded string of the returned binary array of the encrypted data, generated with /// the given parameters. /// /// Source string to encrypt. /// User password used for key lookup. /// Cryptographic strength to use when encrypting string. /// An encrypted version of the source string. public static string Encrypt(this string source, string password, CipherStrength strength) { if (string.IsNullOrEmpty(source)) return null; if (strength == CipherStrength.None) return source; return Convert.ToBase64String(s_textEncoding.GetBytes(source).Encrypt(password, strength)); } /// /// Returns a binary array of encrypted data for the given parameters. /// /// Binary array of data to encrypt. /// User password used for key lookup. /// Cryptographic strength to use when encrypting data. /// An encrypted version of the source data. public static byte[] Encrypt(this byte[] source, string password, CipherStrength strength) { return source.Encrypt(0, source.Length, password, strength); } /// /// Returns a binary array of encrypted data for the given parameters. /// /// Binary array of data to encrypt. /// Offset into buffer. /// Number of bytes in buffer to encrypt starting from offset. /// User password used for key lookup. /// Cryptographic strength to use when encrypting data. /// An encrypted version of the source data. public static byte[] Encrypt(this byte[] source, int startIndex, int length, string password, CipherStrength strength) { if (strength == CipherStrength.None) return source; if (string.IsNullOrEmpty(password)) throw new ArgumentNullException(nameof(password)); byte[][] keyIV = GlobalKeyIVCache.GetCryptoKeyIV(password, (int)strength); return source.Encrypt(startIndex, length, keyIV[KeyIndex], keyIV[IVIndex], strength); } /// /// Returns a binary array of encrypted data for the given parameters. /// /// Binary array of data to encrypt. /// Encryption key to use to encrypt data. /// Initialization vector to use to encrypt data. /// Cryptographic strength to use when encrypting data. /// An encrypted version of the source data. public static byte[] Encrypt(this byte[] source, byte[] key, byte[] iv, CipherStrength strength) { return source.Encrypt(0, source.Length, key, iv, strength); } /// /// Returns a binary array of encrypted data for the given parameters. /// /// Binary array of data to encrypt. /// Offset into buffer. /// Number of bytes in buffer to encrypt starting from offset. /// Encryption key to use to encrypt data. /// Initialization vector to use to encrypt data. /// Cryptographic strength to use when encrypting data. /// An encrypted version of the source data. public static byte[] Encrypt(this byte[] source, int startIndex, int length, byte[] key, byte[] iv, CipherStrength strength) { if (strength == CipherStrength.None) return source; Aes symmetricAlgorithm; if (s_managedEncryption) symmetricAlgorithm = new AesManaged(); else symmetricAlgorithm = new AesCryptoServiceProvider(); symmetricAlgorithm.KeySize = (int)strength; return symmetricAlgorithm.Encrypt(source, startIndex, length, key, iv); } /// /// Returns a stream of encrypted data for the given parameters. /// /// Source stream that contains data to encrypt. /// Encryption key to use to encrypt stream. /// Initialization vector to use to encrypt stream. /// Cryptographic strength to use when encrypting stream. /// An encrypted version of the source stream. /// /// This returns a memory stream of the encrypted results, if the incoming stream is /// very large this will consume a large amount of memory. In this case use the overload /// that takes the destination stream as a parameter instead. /// public static MemoryStream Encrypt(this Stream source, byte[] key, byte[] iv, CipherStrength strength) { MemoryStream destination = new MemoryStream(); source.Encrypt(destination, key, iv, strength, null); destination.Position = 0; return destination; } /// /// Encrypts input stream onto output stream for the given parameters. /// /// Source stream that contains data to encrypt. /// Destination stream used to hold encrypted data. /// Encryption key to use to encrypt stream. /// Initialization vector to use to encrypt stream. /// Cryptographic strength to use when encrypting stream. /// Optional delegate to handle progress updates for encrypting large streams. public static void Encrypt(this Stream source, Stream destination, byte[] key, byte[] iv, CipherStrength strength, Action> progressHandler) { ProcessProgressHandler progress = null; byte[] inBuffer = new byte[Standard.BufferSize]; byte[] outBuffer, lengthBuffer; long total = 0; long length = -1; int read; // Sends initial progress event. if ((object)progressHandler != null) { try { if (source.CanSeek) length = source.Length; } catch { length = -1; } // Create a new progress handler to track encryption progress progress = new ProcessProgressHandler(progressHandler, "Encrypt", length); progress.Complete = 0; } // Reads initial buffer. read = source.Read(inBuffer, 0, Standard.BufferSize); while (read > 0) { // Encrypts buffer. outBuffer = inBuffer.BlockCopy(0, read).Encrypt(key, iv, strength); // The destination encryption stream length does not have to be same as the input stream length, so we // prepend the final size of each encrypted buffer onto the destination output stream so that we can // safely decrypt the stream in a "chunked" fashion later. lengthBuffer = BitConverter.GetBytes(outBuffer.Length); destination.Write(lengthBuffer, 0, lengthBuffer.Length); destination.Write(outBuffer, 0, outBuffer.Length); // Updates encryption progress. if ((object)progressHandler != null) { total += read; progress.Complete = total; } // Reads next buffer. read = source.Read(inBuffer, 0, Standard.BufferSize); } } /// /// Creates an encrypted file from source file data. /// /// Source file name. /// Destination file name. /// User password used for key lookup. /// Cryptographic strength to use when encrypting file. /// Optional delegate to handle progress updates for encrypting large files. public static void EncryptFile(string sourceFileName, string destinationFileName, string password, CipherStrength strength, Action> progressHandler) { using (FileStream sourceFileStream = File.Open(sourceFileName, FileMode.Open, FileAccess.Read, FileShare.Read), destFileStream = File.Create(destinationFileName)) { if (string.IsNullOrEmpty(password)) throw new ArgumentNullException(nameof(password)); byte[][] keyIV = GlobalKeyIVCache.GetCryptoKeyIV(password, (int)strength); sourceFileStream.Encrypt(destFileStream, keyIV[KeyIndex], keyIV[IVIndex], strength, progressHandler); destFileStream.Flush(); destFileStream.Close(); } } /// /// Returns a decrypted string from a Base64 encoded string of binary encrypted data from the given /// parameters. /// /// Source string to decrypt. /// User password used for key lookup. /// Cryptographic strength to use when decrypting string. /// A decrypted version of the source string. public static string Decrypt(this string source, string password, CipherStrength strength) { if (string.IsNullOrEmpty(source)) return null; if (strength == CipherStrength.None) return source; return s_textEncoding.GetString(Convert.FromBase64String(source).Decrypt(password, strength)); } /// /// Returns a binary array of decrypted data for the given parameters. /// /// Binary array of data to decrypt. /// User password used for key lookup. /// Cryptographic strength to use when decrypting data. /// A decrypted version of the source data. public static byte[] Decrypt(this byte[] source, string password, CipherStrength strength) { return source.Decrypt(0, source.Length, password, strength); } /// /// Returns a binary array of decrypted data for the given parameters. /// /// Binary array of data to decrypt. /// Offset into buffer. /// Number of bytes in buffer to decrypt starting from offset. /// User password used for key lookup. /// Cryptographic strength to use when decrypting data. /// A decrypted version of the source data. public static byte[] Decrypt(this byte[] source, int startIndex, int length, string password, CipherStrength strength) { if (strength == CipherStrength.None) return source; if (string.IsNullOrEmpty(password)) throw new ArgumentNullException(nameof(password)); byte[][] keyIV = GlobalKeyIVCache.GetCryptoKeyIV(password, (int)strength); return source.Decrypt(startIndex, length, keyIV[KeyIndex], keyIV[IVIndex], strength); } /// /// Returns a binary array of decrypted data for the given parameters. /// /// Binary array of data to decrypt. /// Encryption key to use to decrypt data. /// Initialization vector to use to decrypt data. /// Cryptographic strength to use when decrypting data. /// A decrypted version of the source data. public static byte[] Decrypt(this byte[] source, byte[] key, byte[] iv, CipherStrength strength) { return source.Decrypt(0, source.Length, key, iv, strength); } /// /// Returns a binary array of decrypted data for the given parameters. /// /// Binary array of data to decrypt. /// Offset into buffer. /// Number of bytes in buffer to decrypt starting from offset. /// Encryption key to use to decrypt data. /// Initialization vector to use to decrypt data. /// Cryptographic strength to use when decrypting data. /// A decrypted version of the source data. public static byte[] Decrypt(this byte[] source, int startIndex, int length, byte[] key, byte[] iv, CipherStrength strength) { if (strength == CipherStrength.None) return source; Aes symmetricAlgorithm; if (s_managedEncryption) symmetricAlgorithm = new AesManaged(); else symmetricAlgorithm = new AesCryptoServiceProvider(); symmetricAlgorithm.KeySize = (int)strength; return symmetricAlgorithm.Decrypt(source, startIndex, length, key, iv); } /// /// Returns a stream of decrypted data for the given parameters. /// /// Source stream that contains data to decrypt. /// Encryption key to use to decrypt stream. /// Initialization vector to use to decrypt stream. /// Cryptographic strength to use when decrypting stream. /// A decrypted version of the source stream. /// /// This returns a memory stream of the decrypted results, if the incoming stream is /// very large this will consume a large amount of memory. In this case use the overload /// that takes the destination stream as a parameter instead. /// public static MemoryStream Decrypt(this Stream source, byte[] key, byte[] iv, CipherStrength strength) { MemoryStream destination = new MemoryStream(); source.Decrypt(destination, key, iv, strength, null); destination.Position = 0; return destination; } /// /// Decrypts input stream onto output stream for the given parameters. /// /// Source stream that contains data to decrypt. /// Destination stream used to hold decrypted data. /// Encryption key to use to decrypt stream. /// Initialization vector to use to decrypt stream. /// Cryptographic strength to use when decrypting stream. /// Optional delegate to handle progress updates for decrypting large streams. public static void Decrypt(this Stream source, Stream destination, byte[] key, byte[] iv, CipherStrength strength, Action> progressHandler) { ProcessProgressHandler progress = null; byte[] inBuffer, outBuffer; byte[] lengthBuffer = BitConverter.GetBytes((int)0); long total = 0; long length = -1; int size, read; // Sends initial progress event. if ((object)progressHandler != null) { try { if (source.CanSeek) length = source.Length; } catch { length = -1; } // Create a new progress handler to track decryption progress progress = new ProcessProgressHandler(progressHandler, "Decrypt", length); progress.Complete = 0; } // When the source stream was encrypted, it was known that the encrypted stream length did not have to be same as // the input stream length. We prepended the final size of the each encrypted buffer onto the destination // output stream (now the input stream to this function), so that we could safely decrypt the stream in a // "chunked" fashion, hence the following: // Reads the size of the next buffer from the stream. read = source.Read(lengthBuffer, 0, lengthBuffer.Length); while (read > 0) { // Converts the byte array containing the buffer size into an integer. size = BitConverter.ToInt32(lengthBuffer, 0); if (size > 0) { // Creates and reads the next buffer. inBuffer = new byte[size]; read = source.Read(inBuffer, 0, size); if (read > 0) { // Decrypts buffer. outBuffer = inBuffer.Decrypt(key, iv, strength); destination.Write(outBuffer, 0, outBuffer.Length); // Updates decryption progress. if ((object)progressHandler != null) { total += read + lengthBuffer.Length; progress.Complete = total; } } } // Reads the size of the next buffer from the stream. read = source.Read(lengthBuffer, 0, lengthBuffer.Length); } } /// /// Creates a decrypted file from source file data. /// /// Source file name. /// Destination file name. /// User password used for key lookup. /// Cryptographic strength to use when decrypting file. /// Optional delegate to handle progress updates for decrypting large files. public static void DecryptFile(string sourceFileName, string destinationFileName, string password, CipherStrength strength, Action> progressHandler) { using (FileStream sourceFileStream = File.Open(sourceFileName, FileMode.Open, FileAccess.Read, FileShare.Read), destFileStream = File.Create(destinationFileName)) { if (string.IsNullOrEmpty(password)) throw new ArgumentNullException(nameof(password)); byte[][] keyIV = GlobalKeyIVCache.GetCryptoKeyIV(password, (int)strength); sourceFileStream.Decrypt(destFileStream, keyIV[KeyIndex], keyIV[IVIndex], strength, progressHandler); destFileStream.Flush(); } } } }