#region Copyright 2010 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.Security; using System.Security.Cryptography; using System.IO; using CSharpTest.Net.IO; namespace CSharpTest.Net.Crypto { /// /// Creates an in-memory object that can be used for salted password encryption without /// storing the password in memory (based on Rfc2898DeriveBytes, SHA1 hash of password /// is stored) /// public class PasswordKey : CryptoKey { /// Adjusts the number of repetitions used when deriving password bytes public const int DefaultIterations = 8192; readonly IPasswordDerivedBytes _derivedBytes; private Salt _salt; private byte[] _iv; /// Creates the password from the given bytes and salt public PasswordKey(IPasswordDerivedBytes derivedBytes, Salt salt) { _derivedBytes = derivedBytes; _salt = salt; _iv = null; } /// Creates the password from the given bytes and salt public PasswordKey(bool clear, byte[] bytes, Salt salt) : this(new HashDerivedBytes(clear, Check.NotEmpty(bytes), salt, DefaultIterations), salt) { } #region CTor overloads /// Creates the password from the given password bytes public PasswordKey(bool clear, byte[] bytes) : this(clear, bytes, new Salt()) { } /// Creates the password from the given password public PasswordKey(string data) : this(true, Password.Encoding.GetBytes(data)) { } /// Creates the password from the given password public PasswordKey(SecureString data) : this(true, SecureStringUtils.ToByteArray(data, Password.Encoding)) { } #endregion /// /// Returns the derived bytes algorithm for this instance or throws ObjectDisposedException /// [System.Diagnostics.DebuggerNonUserCode] protected IPasswordDerivedBytes DerivedBytes { get { return Assert(_derivedBytes); } } /// Removes the memory representation of this password and key [System.Diagnostics.DebuggerNonUserCode] protected override void Dispose(bool disposing) { _derivedBytes.Salt = new byte[8]; _derivedBytes.Reset(); _salt = null; base.Dispose(disposing); } /// Returns the key generated with the current password and salt public AESCryptoKey CreateKey() { return CreateKey(_salt, IV); } /// Returns the key generated with the current password and the provided salt public AESCryptoKey CreateKey(Salt salt) { DerivedBytes.Salt = salt.ToArray(); DerivedBytes.Reset(); byte[] key = DerivedBytes.GetBytes(32); return new AESCryptoKey(key, IV); } /// Returns the key generated with the current password and the provided salt public AESCryptoKey CreateKey(Salt salt, byte[] iv) { DerivedBytes.Salt = salt.ToArray(); DerivedBytes.Reset(); byte[] key = DerivedBytes.GetBytes(32); return new AESCryptoKey(key, iv); } /// Sets or Gets the IV used when deriving the encryption key public virtual byte[] IV { get { if (_iv != null) return (byte[])_iv.Clone(); return AESCryptoKey.ProcessDefaultIV; } set { _iv = Check.ArraySize(value, 16, 16); } } /// Sets or Gets the salt used with deriving the encryption key public virtual Salt Salt { get { return Assert(_salt); } set { _salt = Check.NotNull(value); } } /// Sets or Gets the iterations used with deriving the encryption key public virtual int IterationCount { get { return DerivedBytes.IterationCount; } set { DerivedBytes.IterationCount = Check.InRange(value, 1, int.MaxValue); } } /// Encrypts the stream with the current password and salt public override Stream Encrypt(Stream stream) { try { Salt salt = this.Salt; stream.Write(salt.ToArray(), 0, salt.Length); AESCryptoKey key = CreateKey(); return new DisposingStream(key.Encrypt(stream)) .WithDisposeOf(key); } catch (InvalidOperationException) { throw; } catch { throw CryptographicException(); } } /// Decrypts the stream with the current password and salt public override Stream Decrypt(Stream stream) { try { return Decrypt(stream, this.Salt.BitSize); } catch (InvalidOperationException) { throw; } catch { throw CryptographicException(); } } /// Decrypts the stream with the current password and salt public Stream Decrypt(Stream stream, Salt.Size szSaltSize) { try { Salt salt = new Salt(IOStream.Read(stream, (int)szSaltSize / 8), false); AESCryptoKey key = CreateKey(salt); return new DisposingStream(key.Decrypt(stream)) .WithDisposeOf(key); } catch (InvalidOperationException) { throw; } catch { throw CryptographicException(); } } /// Encrypts the bytes with the current password and salt public override byte[] Encrypt(byte[] blob) { try { using (AESCryptoKey key = CreateKey()) return new SaltedData(Salt, key.Encrypt(blob)).ToArray(); } catch (InvalidOperationException) { throw; } catch { throw CryptographicException(); } } /// Decrypts the bytes with the current password and salt public override byte[] Decrypt(byte[] blob) { try { return Decrypt(blob, this.Salt.BitSize); } catch (InvalidOperationException) { throw; } catch { throw CryptographicException(); } } /// Decrypts the bytes with the current password and salt public byte[] Decrypt(byte[] blob, Salt.Size szSaltSize) { try { using (SaltedData data = new SaltedData(blob, szSaltSize)) using (AESCryptoKey key = CreateKey(data.Salt)) return key.Decrypt(data.GetDataBytes()); } catch (InvalidOperationException) { throw; } catch { throw CryptographicException(); } } } }