/* KeePass Password Safe - The Open-Source Password Manager Copyright (C) 2003-2012 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ using System; using System.Text; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Security.Cryptography; using KeePassLib.Native; using KeePassLib.Resources; using KeePassLib.Security; using KeePassLib.Utility; namespace KeePassLib.Keys { /// /// Represents a key. A key can be build up using several user key data sources /// like a password, a key file, the currently logged on user credentials, /// the current computer ID, etc. /// public sealed class CompositeKey { private List m_vUserKeys = new List(); /// /// List of all user keys contained in the current composite key. /// public IEnumerable UserKeys { get { return m_vUserKeys; } } public uint UserKeyCount { get { return (uint)m_vUserKeys.Count; } } /// /// Construct a new, empty key object. /// public CompositeKey() { } // /// // /// Deconstructor, clears up the key. // /// // ~CompositeKey() // { // Clear(); // } // /// // /// Clears the key. This function also erases all previously stored // /// user key data objects. // /// // public void Clear() // { // foreach(IUserKey pKey in m_vUserKeys) // pKey.Clear(); // m_vUserKeys.Clear(); // } /// /// Add a user key. /// /// User key to add. public void AddUserKey(IUserKey pKey) { Debug.Assert(pKey != null); if(pKey == null) throw new ArgumentNullException("pKey"); m_vUserKeys.Add(pKey); } /// /// Remove a user key. /// /// User key to remove. /// Returns true if the key was removed successfully. public bool RemoveUserKey(IUserKey pKey) { Debug.Assert(pKey != null); if(pKey == null) throw new ArgumentNullException("pKey"); Debug.Assert(m_vUserKeys.IndexOf(pKey) >= 0); return m_vUserKeys.Remove(pKey); } /// /// Test whether the composite key contains a specific type of /// user keys (password, key file, ...). If at least one user /// key of that type is present, the function returns true. /// /// User key type. /// Returns true, if the composite key contains /// a user key of the specified type. public bool ContainsType(Type tUserKeyType) { Debug.Assert(tUserKeyType != null); if(tUserKeyType == null) throw new ArgumentNullException("tUserKeyType"); foreach(IUserKey pKey in m_vUserKeys) { if(tUserKeyType.IsInstanceOfType(pKey)) return true; } return false; } /// /// Get the first user key of a specified type. /// /// Type of the user key to get. /// Returns the first user key of the specified type /// or null if no key of that type is found. public IUserKey GetUserKey(Type tUserKeyType) { Debug.Assert(tUserKeyType != null); if(tUserKeyType == null) throw new ArgumentNullException("tUserKeyType"); foreach(IUserKey pKey in m_vUserKeys) { if(tUserKeyType.IsInstanceOfType(pKey)) return pKey; } return null; } /// /// Creates the composite key from the supplied user key sources (password, /// key file, user account, computer ID, etc.). /// private byte[] CreateRawCompositeKey32() { ValidateUserKeys(); // Concatenate user key data MemoryStream ms = new MemoryStream(); foreach(IUserKey pKey in m_vUserKeys) { ProtectedBinary b = pKey.KeyData; if(b != null) { byte[] pbKeyData = b.ReadData(); ms.Write(pbKeyData, 0, pbKeyData.Length); MemUtil.ZeroByteArray(pbKeyData); } } SHA256Managed sha256 = new SHA256Managed(); byte[] pbHash = sha256.ComputeHash(ms.ToArray()); ms.Close(); return pbHash; } public bool EqualsValue(CompositeKey ckOther) { if(ckOther == null) throw new ArgumentNullException("ckOther"); byte[] pbThis = CreateRawCompositeKey32(); byte[] pbOther = ckOther.CreateRawCompositeKey32(); bool bResult = MemUtil.ArraysEqual(pbThis, pbOther); Array.Clear(pbOther, 0, pbOther.Length); Array.Clear(pbThis, 0, pbThis.Length); return bResult; } /// /// Generate a 32-bit wide key out of the composite key. /// /// Seed used in the key transformation /// rounds. Must be a byte array containing exactly 32 bytes; must /// not be null. /// Number of key transformation rounds. /// Returns a protected binary object that contains the /// resulting 32-bit wide key. public ProtectedBinary GenerateKey32(byte[] pbKeySeed32, ulong uNumRounds) { Debug.Assert(pbKeySeed32 != null); if(pbKeySeed32 == null) throw new ArgumentNullException("pbKeySeed32"); Debug.Assert(pbKeySeed32.Length == 32); if(pbKeySeed32.Length != 32) throw new ArgumentException("pbKeySeed32"); byte[] pbRaw32 = CreateRawCompositeKey32(); if((pbRaw32 == null) || (pbRaw32.Length != 32)) { Debug.Assert(false); return null; } byte[] pbTrf32 = TransformKey(pbRaw32, pbKeySeed32, uNumRounds); if((pbTrf32 == null) || (pbTrf32.Length != 32)) { Debug.Assert(false); return null; } ProtectedBinary pbRet = new ProtectedBinary(true, pbTrf32); MemUtil.ZeroByteArray(pbTrf32); MemUtil.ZeroByteArray(pbRaw32); return pbRet; } private void ValidateUserKeys() { int nAccounts = 0; foreach(IUserKey uKey in m_vUserKeys) { if(uKey is KcpUserAccount) ++nAccounts; } if(nAccounts >= 2) { Debug.Assert(false); throw new InvalidOperationException(); } } /// /// Transform the current key uNumRounds times. /// /// The original key which will be transformed. /// This parameter won't be modified. /// Seed used for key transformations. Must not /// be null. This parameter won't be modified. /// Transformation count. /// 256-bit transformed key. private static byte[] TransformKey(byte[] pbOriginalKey32, byte[] pbKeySeed32, ulong uNumRounds) { Debug.Assert((pbOriginalKey32 != null) && (pbOriginalKey32.Length == 32)); if(pbOriginalKey32 == null) throw new ArgumentNullException("pbOriginalKey32"); if(pbOriginalKey32.Length != 32) throw new ArgumentException(); Debug.Assert((pbKeySeed32 != null) && (pbKeySeed32.Length == 32)); if(pbKeySeed32 == null) throw new ArgumentNullException("pbKeySeed32"); if(pbKeySeed32.Length != 32) throw new ArgumentException(); byte[] pbNewKey = new byte[32]; Array.Copy(pbOriginalKey32, pbNewKey, pbNewKey.Length); // Try to use the native library first if(NativeLib.TransformKey256(pbNewKey, pbKeySeed32, uNumRounds)) return (new SHA256Managed()).ComputeHash(pbNewKey); if(TransformKeyManaged(pbNewKey, pbKeySeed32, uNumRounds) == false) return null; SHA256Managed sha256 = new SHA256Managed(); return sha256.ComputeHash(pbNewKey); } public static bool TransformKeyManaged(byte[] pbNewKey32, byte[] pbKeySeed32, ulong uNumRounds) { byte[] pbIV = new byte[16]; Array.Clear(pbIV, 0, pbIV.Length); RijndaelManaged r = new RijndaelManaged(); if(r.BlockSize != 128) // AES block size { Debug.Assert(false); r.BlockSize = 128; } r.IV = pbIV; r.Mode = CipherMode.ECB; r.KeySize = 256; r.Key = pbKeySeed32; ICryptoTransform iCrypt = r.CreateEncryptor(); // !iCrypt.CanReuseTransform -- doesn't work with Mono if((iCrypt == null) || (iCrypt.InputBlockSize != 16) || (iCrypt.OutputBlockSize != 16)) { Debug.Assert(false, "Invalid ICryptoTransform."); Debug.Assert((iCrypt.InputBlockSize == 16), "Invalid input block size!"); Debug.Assert((iCrypt.OutputBlockSize == 16), "Invalid output block size!"); return false; } for(ulong i = 0; i < uNumRounds; ++i) { iCrypt.TransformBlock(pbNewKey32, 0, 16, pbNewKey32, 0); iCrypt.TransformBlock(pbNewKey32, 16, 16, pbNewKey32, 16); } return true; } /// /// Benchmark the TransformKey method. Within /// ms, random keys will be transformed /// and the number of performed transformations are returned. /// /// Test duration in ms. /// Stepping. /// should be a prime number. For fast processors /// (PCs) a value of 3001 is recommended, for slower processors (PocketPC) /// a value of 401 is recommended. /// Number of transformations performed in the specified /// amount of time. Maximum value is uint.MaxValue. public static ulong TransformKeyBenchmark(uint uMilliseconds, ulong uStep) { ulong uRounds; // Try native method if(NativeLib.TransformKeyBenchmark256(uMilliseconds, out uRounds)) return uRounds; byte[] pbIV = new byte[16]; Array.Clear(pbIV, 0, pbIV.Length); byte[] pbKey = new byte[32]; byte[] pbNewKey = new byte[32]; for(int i = 0; i < pbKey.Length; ++i) { pbKey[i] = (byte)i; pbNewKey[i] = (byte)i; } RijndaelManaged r = new RijndaelManaged(); if(r.BlockSize != 128) // AES block size { Debug.Assert(false); r.BlockSize = 128; } r.IV = pbIV; r.Mode = CipherMode.ECB; r.KeySize = 256; r.Key = pbKey; ICryptoTransform iCrypt = r.CreateEncryptor(); // !iCrypt.CanReuseTransform -- doesn't work with Mono if((iCrypt == null) || (iCrypt.InputBlockSize != 16) || (iCrypt.OutputBlockSize != 16)) { Debug.Assert(false, "Invalid ICryptoTransform."); Debug.Assert(iCrypt.InputBlockSize == 16, "Invalid input block size!"); Debug.Assert(iCrypt.OutputBlockSize == 16, "Invalid output block size!"); return PwDefs.DefaultKeyEncryptionRounds; } DateTime dtStart = DateTime.Now; TimeSpan ts; double dblReqMillis = uMilliseconds; uRounds = 0; while(true) { for(ulong j = 0; j < uStep; ++j) { iCrypt.TransformBlock(pbNewKey, 0, 16, pbNewKey, 0); iCrypt.TransformBlock(pbNewKey, 16, 16, pbNewKey, 16); } uRounds += uStep; if(uRounds < uStep) // Overflow check { uRounds = ulong.MaxValue; break; } ts = DateTime.Now - dtStart; if(ts.TotalMilliseconds > dblReqMillis) break; } return uRounds; } } public sealed class InvalidCompositeKeyException : Exception { public override string Message { get { return KLRes.InvalidCompositeKey + MessageService.NewParagraph + KLRes.InvalidCompositeKeyHint; } } /// /// Construct a new invalid composite key exception. /// public InvalidCompositeKeyException() { } } }