diff --git a/src/KeePassLib2Android/Collections/StringDictionaryEx.cs b/src/KeePassLib2Android/Collections/StringDictionaryEx.cs index 2c55b306..79e21b34 100644 --- a/src/KeePassLib2Android/Collections/StringDictionaryEx.cs +++ b/src/KeePassLib2Android/Collections/StringDictionaryEx.cs @@ -32,14 +32,14 @@ using KeePassLibSD; namespace KeePassLib.Collections { public sealed class StringDictionaryEx : IDeepCloneable, - IEnumerable> + IEnumerable>, IEquatable { - private SortedDictionary m_vDict = + private SortedDictionary m_dict = new SortedDictionary(); public int Count { - get { return m_vDict.Count; } + get { return m_dict.Count; } } public StringDictionaryEx() @@ -48,39 +48,53 @@ namespace KeePassLib.Collections IEnumerator IEnumerable.GetEnumerator() { - return m_vDict.GetEnumerator(); + return m_dict.GetEnumerator(); } public IEnumerator> GetEnumerator() { - return m_vDict.GetEnumerator(); + return m_dict.GetEnumerator(); } public StringDictionaryEx CloneDeep() { - StringDictionaryEx plNew = new StringDictionaryEx(); + StringDictionaryEx sdNew = new StringDictionaryEx(); - foreach(KeyValuePair kvpStr in m_vDict) - plNew.Set(kvpStr.Key, kvpStr.Value); + foreach(KeyValuePair kvp in m_dict) + sdNew.m_dict[kvp.Key] = kvp.Value; // Strings are immutable - return plNew; + return sdNew; + } + + public bool Equals(StringDictionaryEx sdOther) + { + if(sdOther == null) { Debug.Assert(false); return false; } + + if(m_dict.Count != sdOther.m_dict.Count) return false; + + foreach(KeyValuePair kvp in sdOther.m_dict) + { + string str = Get(kvp.Key); + if((str == null) || (str != kvp.Value)) return false; + } + + return true; } public string Get(string strName) { - Debug.Assert(strName != null); if(strName == null) throw new ArgumentNullException("strName"); + if(strName == null) { Debug.Assert(false); throw new ArgumentNullException("strName"); } string s; - if(m_vDict.TryGetValue(strName, out s)) return s; - + if(m_dict.TryGetValue(strName, out s)) return s; return null; } public bool Exists(string strName) { - Debug.Assert(strName != null); if(strName == null) throw new ArgumentNullException("strName"); + if(strName == null) { Debug.Assert(false); throw new ArgumentNullException("strName"); } - return m_vDict.ContainsKey(strName); + return m_dict.ContainsKey(strName); } /// @@ -92,25 +106,25 @@ namespace KeePassLib.Collections /// parameters is null. public void Set(string strField, string strNewValue) { - Debug.Assert(strField != null); if(strField == null) throw new ArgumentNullException("strField"); - Debug.Assert(strNewValue != null); if(strNewValue == null) throw new ArgumentNullException("strNewValue"); + if(strField == null) { Debug.Assert(false); throw new ArgumentNullException("strField"); } + if(strNewValue == null) { Debug.Assert(false); throw new ArgumentNullException("strNewValue"); } - m_vDict[strField] = strNewValue; + m_dict[strField] = strNewValue; } /// /// Delete a string. /// /// Name of the string field to delete. - /// Returns true if the field has been successfully - /// removed, otherwise the return value is false. + /// Returns true, if the field has been successfully + /// removed. Otherwise, the return value is false. /// Thrown if the input /// parameter is null. public bool Remove(string strField) { - Debug.Assert(strField != null); if(strField == null) throw new ArgumentNullException("strField"); + if(strField == null) { Debug.Assert(false); throw new ArgumentNullException("strField"); } - return m_vDict.Remove(strField); + return m_dict.Remove(strField); } } } diff --git a/src/KeePassLib2Android/Collections/VariantDictionary.cs b/src/KeePassLib2Android/Collections/VariantDictionary.cs new file mode 100644 index 00000000..8aa5a48c --- /dev/null +++ b/src/KeePassLib2Android/Collections/VariantDictionary.cs @@ -0,0 +1,415 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2016 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.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Text; + +using KeePassLib.Resources; +using KeePassLib.Utility; + +namespace KeePassLib.Collections +{ + public class VariantDictionary : ICloneable + { + private const ushort VdVersion = 0x0100; + private const ushort VdmCritical = 0xFF00; + private const ushort VdmInfo = 0x00FF; + + private Dictionary m_d = new Dictionary(); + + private enum VdType : byte + { + None = 0, + + // Byte = 0x02, + // UInt16 = 0x03, + UInt32 = 0x04, + UInt64 = 0x05, + + // Signed mask: 0x08 + Bool = 0x08, + // SByte = 0x0A, + // Int16 = 0x0B, + Int32 = 0x0C, + Int64 = 0x0D, + + // Float = 0x10, + // Double = 0x11, + // Decimal = 0x12, + + // Char = 0x17, // 16-bit Unicode character + String = 0x18, + + // Array mask: 0x40 + ByteArray = 0x42 + } + + public int Count + { + get { return m_d.Count; } + } + + public VariantDictionary() + { + Debug.Assert((VdmCritical & VdmInfo) == ushort.MinValue); + Debug.Assert((VdmCritical | VdmInfo) == ushort.MaxValue); + } + + private bool Get(string strName, out T t) + { + t = default(T); + + if(string.IsNullOrEmpty(strName)) { Debug.Assert(false); return false; } + + object o; + if(!m_d.TryGetValue(strName, out o)) return false; // No assert + + if(o == null) { Debug.Assert(false); return false; } + if(o.GetType() != typeof(T)) { Debug.Assert(false); return false; } + + t = (T)o; + return true; + } + + private void SetStruct(string strName, T t) + where T : struct + { + if(string.IsNullOrEmpty(strName)) { Debug.Assert(false); return; } + +#if DEBUG + T tEx; + Get(strName, out tEx); // Assert same type +#endif + + m_d[strName] = t; + } + + private void SetRef(string strName, T t) + where T : class + { + if(string.IsNullOrEmpty(strName)) { Debug.Assert(false); return; } + if(t == null) { Debug.Assert(false); return; } + +#if DEBUG + T tEx; + Get(strName, out tEx); // Assert same type +#endif + + m_d[strName] = t; + } + + public bool Remove(string strName) + { + if(string.IsNullOrEmpty(strName)) { Debug.Assert(false); return false; } + + return m_d.Remove(strName); + } + + public void CopyTo(VariantDictionary d) + { + if(d == null) { Debug.Assert(false); return; } + + // Do not clear the target + foreach(KeyValuePair kvp in m_d) + { + d.m_d[kvp.Key] = kvp.Value; + } + } + + public Type GetTypeOf(string strName) + { + if(string.IsNullOrEmpty(strName)) { Debug.Assert(false); return null; } + + object o; + m_d.TryGetValue(strName, out o); + if(o == null) return null; // No assert + + return o.GetType(); + } + + public uint GetUInt32(string strName, uint uDefault) + { + uint u; + if(Get(strName, out u)) return u; + return uDefault; + } + + public void SetUInt32(string strName, uint uValue) + { + SetStruct(strName, uValue); + } + + public ulong GetUInt64(string strName, ulong uDefault) + { + ulong u; + if(Get(strName, out u)) return u; + return uDefault; + } + + public void SetUInt64(string strName, ulong uValue) + { + SetStruct(strName, uValue); + } + + public bool GetBool(string strName, bool bDefault) + { + bool b; + if(Get(strName, out b)) return b; + return bDefault; + } + + public void SetBool(string strName, bool bValue) + { + SetStruct(strName, bValue); + } + + public int GetInt32(string strName, int iDefault) + { + int i; + if(Get(strName, out i)) return i; + return iDefault; + } + + public void SetInt32(string strName, int iValue) + { + SetStruct(strName, iValue); + } + + public long GetInt64(string strName, long lDefault) + { + long l; + if(Get(strName, out l)) return l; + return lDefault; + } + + public void SetInt64(string strName, long lValue) + { + SetStruct(strName, lValue); + } + + public string GetString(string strName) + { + string str; + Get(strName, out str); + return str; + } + + public void SetString(string strName, string strValue) + { + SetRef(strName, strValue); + } + + public byte[] GetByteArray(string strName) + { + byte[] pb; + Get(strName, out pb); + return pb; + } + + public void SetByteArray(string strName, byte[] pbValue) + { + SetRef(strName, pbValue); + } + + /// + /// Create a deep copy. + /// + public virtual object Clone() + { + VariantDictionary vdNew = new VariantDictionary(); + + foreach(KeyValuePair kvp in m_d) + { + object o = kvp.Value; + if(o == null) { Debug.Assert(false); continue; } + + Type t = o.GetType(); + if(t == typeof(byte[])) + { + byte[] p = (byte[])o; + byte[] pNew = new byte[p.Length]; + if(p.Length > 0) Array.Copy(p, pNew, p.Length); + + o = pNew; + } + + vdNew.m_d[kvp.Key] = o; + } + + return vdNew; + } + + public static byte[] Serialize(VariantDictionary p) + { + if(p == null) { Debug.Assert(false); return null; } + + byte[] pbRet; + using(MemoryStream ms = new MemoryStream()) + { + MemUtil.Write(ms, MemUtil.UInt16ToBytes(VdVersion)); + + foreach(KeyValuePair kvp in p.m_d) + { + string strName = kvp.Key; + if(string.IsNullOrEmpty(strName)) { Debug.Assert(false); continue; } + byte[] pbName = StrUtil.Utf8.GetBytes(strName); + + object o = kvp.Value; + if(o == null) { Debug.Assert(false); continue; } + + Type t = o.GetType(); + VdType vt = VdType.None; + byte[] pbValue = null; + if(t == typeof(uint)) + { + vt = VdType.UInt32; + pbValue = MemUtil.UInt32ToBytes((uint)o); + } + else if(t == typeof(ulong)) + { + vt = VdType.UInt64; + pbValue = MemUtil.UInt64ToBytes((ulong)o); + } + else if(t == typeof(bool)) + { + vt = VdType.Bool; + pbValue = new byte[1]; + pbValue[0] = ((bool)o ? (byte)1 : (byte)0); + } + else if(t == typeof(int)) + { + vt = VdType.Int32; + pbValue = MemUtil.Int32ToBytes((int)o); + } + else if(t == typeof(long)) + { + vt = VdType.Int64; + pbValue = MemUtil.Int64ToBytes((long)o); + } + else if(t == typeof(string)) + { + vt = VdType.String; + pbValue = StrUtil.Utf8.GetBytes((string)o); + } + else if(t == typeof(byte[])) + { + vt = VdType.ByteArray; + pbValue = (byte[])o; + } + else { Debug.Assert(false); continue; } // Unknown type + + ms.WriteByte((byte)vt); + MemUtil.Write(ms, MemUtil.Int32ToBytes(pbName.Length)); + MemUtil.Write(ms, pbName); + MemUtil.Write(ms, MemUtil.Int32ToBytes(pbValue.Length)); + MemUtil.Write(ms, pbValue); + } + + ms.WriteByte((byte)VdType.None); + pbRet = ms.ToArray(); + } + + return pbRet; + } + + public static VariantDictionary Deserialize(byte[] pb) + { + if(pb == null) { Debug.Assert(false); return null; } + + VariantDictionary d = new VariantDictionary(); + using(MemoryStream ms = new MemoryStream(pb, false)) + { + ushort uVersion = MemUtil.BytesToUInt16(MemUtil.Read(ms, 2)); + if((uVersion & VdmCritical) > (VdVersion & VdmCritical)) + throw new FormatException(KLRes.FileNewVerReq); + + while(true) + { + int iType = ms.ReadByte(); + if(iType < 0) throw new EndOfStreamException(KLRes.FileCorrupted); + byte btType = (byte)iType; + if(btType == (byte)VdType.None) break; + + int cbName = MemUtil.BytesToInt32(MemUtil.Read(ms, 4)); + byte[] pbName = MemUtil.Read(ms, cbName); + if(pbName.Length != cbName) + throw new EndOfStreamException(KLRes.FileCorrupted); + string strName = StrUtil.Utf8.GetString(pbName); + + int cbValue = MemUtil.BytesToInt32(MemUtil.Read(ms, 4)); + byte[] pbValue = MemUtil.Read(ms, cbValue); + if(pbValue.Length != cbValue) + throw new EndOfStreamException(KLRes.FileCorrupted); + + switch(btType) + { + case (byte)VdType.UInt32: + if(cbValue == 4) + d.SetUInt32(strName, MemUtil.BytesToUInt32(pbValue)); + else { Debug.Assert(false); } + break; + + case (byte)VdType.UInt64: + if(cbValue == 8) + d.SetUInt64(strName, MemUtil.BytesToUInt64(pbValue)); + else { Debug.Assert(false); } + break; + + case (byte)VdType.Bool: + if(cbValue == 1) + d.SetBool(strName, (pbValue[0] != 0)); + else { Debug.Assert(false); } + break; + + case (byte)VdType.Int32: + if(cbValue == 4) + d.SetInt32(strName, MemUtil.BytesToInt32(pbValue)); + else { Debug.Assert(false); } + break; + + case (byte)VdType.Int64: + if(cbValue == 8) + d.SetInt64(strName, MemUtil.BytesToInt64(pbValue)); + else { Debug.Assert(false); } + break; + + case (byte)VdType.String: + d.SetString(strName, StrUtil.Utf8.GetString(pbValue)); + break; + + case (byte)VdType.ByteArray: + d.SetByteArray(strName, pbValue); + break; + + default: + Debug.Assert(false); // Unknown type + break; + } + } + + Debug.Assert(ms.ReadByte() < 0); + } + + return d; + } + } +} diff --git a/src/KeePassLib2Android/Cryptography/Cipher/ChaCha20Cipher.cs b/src/KeePassLib2Android/Cryptography/Cipher/ChaCha20Cipher.cs new file mode 100644 index 00000000..ad0c93b4 --- /dev/null +++ b/src/KeePassLib2Android/Cryptography/Cipher/ChaCha20Cipher.cs @@ -0,0 +1,251 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2016 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.Collections.Generic; +using System.Diagnostics; +using System.IO; + +using KeePassLib.Resources; +using KeePassLib.Utility; + +namespace KeePassLib.Cryptography.Cipher +{ + /// + /// Implementation of the ChaCha20 cipher with a 96-bit nonce, + /// as specified in RFC 7539. + /// https://tools.ietf.org/html/rfc7539 + /// + public sealed class ChaCha20Cipher : CtrBlockCipher + { + private uint[] m_s = new uint[16]; // State + private uint[] m_x = new uint[16]; // Working buffer + + private bool m_bLargeCounter; // See constructor documentation + + private static readonly uint[] g_sigma = new uint[4] { + 0x61707865, 0x3320646E, 0x79622D32, 0x6B206574 + }; + + private const string StrNameRfc = "ChaCha20 (RFC 7539)"; + + public override int BlockSize + { + get { return 64; } + } + + public ChaCha20Cipher(byte[] pbKey32, byte[] pbIV12) : + this(pbKey32, pbIV12, false) + { + } + + /// + /// Constructor. + /// + /// Key (32 bytes). + /// Nonce (12 bytes). + /// If false, the RFC 7539 version + /// of ChaCha20 is used. In this case, only 256 GB of data can be + /// encrypted securely (because the block counter is a 32-bit variable); + /// an attempt to encrypt more data throws an exception. + /// If is true, the 32-bit + /// counter overflows to another 32-bit variable (i.e. the counter + /// effectively is a 64-bit variable), like in the original ChaCha20 + /// specification by D. J. Bernstein (which has a 64-bit counter and a + /// 64-bit nonce). To be compatible with this version, the 64-bit nonce + /// must be stored in the last 8 bytes of + /// and the first 4 bytes must be 0. + /// If the IV was generated randomly, a 12-byte IV and a large counter + /// can be used to securely encrypt more than 256 GB of data (but note + /// this is incompatible with RFC 7539 and the original specification). + public ChaCha20Cipher(byte[] pbKey32, byte[] pbIV12, bool bLargeCounter) : + base() + { + if(pbKey32 == null) throw new ArgumentNullException("pbKey32"); + if(pbKey32.Length != 32) throw new ArgumentOutOfRangeException("pbKey32"); + if(pbIV12 == null) throw new ArgumentNullException("pbIV12"); + if(pbIV12.Length != 12) throw new ArgumentOutOfRangeException("pbIV12"); + + m_bLargeCounter = bLargeCounter; + + // Key setup + m_s[4] = MemUtil.BytesToUInt32(pbKey32, 0); + m_s[5] = MemUtil.BytesToUInt32(pbKey32, 4); + m_s[6] = MemUtil.BytesToUInt32(pbKey32, 8); + m_s[7] = MemUtil.BytesToUInt32(pbKey32, 12); + m_s[8] = MemUtil.BytesToUInt32(pbKey32, 16); + m_s[9] = MemUtil.BytesToUInt32(pbKey32, 20); + m_s[10] = MemUtil.BytesToUInt32(pbKey32, 24); + m_s[11] = MemUtil.BytesToUInt32(pbKey32, 28); + m_s[0] = g_sigma[0]; + m_s[1] = g_sigma[1]; + m_s[2] = g_sigma[2]; + m_s[3] = g_sigma[3]; + + // IV setup + m_s[12] = 0; // Counter + m_s[13] = MemUtil.BytesToUInt32(pbIV12, 0); + m_s[14] = MemUtil.BytesToUInt32(pbIV12, 4); + m_s[15] = MemUtil.BytesToUInt32(pbIV12, 8); + } + + protected override void Dispose(bool bDisposing) + { + MemUtil.ZeroArray(m_s); + MemUtil.ZeroArray(m_x); + + base.Dispose(bDisposing); + } + + protected override void NextBlock(byte[] pBlock) + { + if(pBlock == null) throw new ArgumentNullException("pBlock"); + if(pBlock.Length != 64) throw new ArgumentOutOfRangeException("pBlock"); + + // x is a local alias for the working buffer; with this, + // the compiler/runtime might remove some checks + uint[] x = m_x; + if(x == null) throw new InvalidOperationException(); + if(x.Length < 16) throw new InvalidOperationException(); + + uint[] s = m_s; + if(s == null) throw new InvalidOperationException(); + if(s.Length < 16) throw new InvalidOperationException(); + + Array.Copy(s, x, 16); + + unchecked + { + // 10 * 8 quarter rounds = 20 rounds + for(int i = 0; i < 10; ++i) + { + // Column quarter rounds + x[ 0] += x[ 4]; + x[12] = MemUtil.RotateLeft32(x[12] ^ x[ 0], 16); + x[ 8] += x[12]; + x[ 4] = MemUtil.RotateLeft32(x[ 4] ^ x[ 8], 12); + x[ 0] += x[ 4]; + x[12] = MemUtil.RotateLeft32(x[12] ^ x[ 0], 8); + x[ 8] += x[12]; + x[ 4] = MemUtil.RotateLeft32(x[ 4] ^ x[ 8], 7); + + x[ 1] += x[ 5]; + x[13] = MemUtil.RotateLeft32(x[13] ^ x[ 1], 16); + x[ 9] += x[13]; + x[ 5] = MemUtil.RotateLeft32(x[ 5] ^ x[ 9], 12); + x[ 1] += x[ 5]; + x[13] = MemUtil.RotateLeft32(x[13] ^ x[ 1], 8); + x[ 9] += x[13]; + x[ 5] = MemUtil.RotateLeft32(x[ 5] ^ x[ 9], 7); + + x[ 2] += x[ 6]; + x[14] = MemUtil.RotateLeft32(x[14] ^ x[ 2], 16); + x[10] += x[14]; + x[ 6] = MemUtil.RotateLeft32(x[ 6] ^ x[10], 12); + x[ 2] += x[ 6]; + x[14] = MemUtil.RotateLeft32(x[14] ^ x[ 2], 8); + x[10] += x[14]; + x[ 6] = MemUtil.RotateLeft32(x[ 6] ^ x[10], 7); + + x[ 3] += x[ 7]; + x[15] = MemUtil.RotateLeft32(x[15] ^ x[ 3], 16); + x[11] += x[15]; + x[ 7] = MemUtil.RotateLeft32(x[ 7] ^ x[11], 12); + x[ 3] += x[ 7]; + x[15] = MemUtil.RotateLeft32(x[15] ^ x[ 3], 8); + x[11] += x[15]; + x[ 7] = MemUtil.RotateLeft32(x[ 7] ^ x[11], 7); + + // Diagonal quarter rounds + x[ 0] += x[ 5]; + x[15] = MemUtil.RotateLeft32(x[15] ^ x[ 0], 16); + x[10] += x[15]; + x[ 5] = MemUtil.RotateLeft32(x[ 5] ^ x[10], 12); + x[ 0] += x[ 5]; + x[15] = MemUtil.RotateLeft32(x[15] ^ x[ 0], 8); + x[10] += x[15]; + x[ 5] = MemUtil.RotateLeft32(x[ 5] ^ x[10], 7); + + x[ 1] += x[ 6]; + x[12] = MemUtil.RotateLeft32(x[12] ^ x[ 1], 16); + x[11] += x[12]; + x[ 6] = MemUtil.RotateLeft32(x[ 6] ^ x[11], 12); + x[ 1] += x[ 6]; + x[12] = MemUtil.RotateLeft32(x[12] ^ x[ 1], 8); + x[11] += x[12]; + x[ 6] = MemUtil.RotateLeft32(x[ 6] ^ x[11], 7); + + x[ 2] += x[ 7]; + x[13] = MemUtil.RotateLeft32(x[13] ^ x[ 2], 16); + x[ 8] += x[13]; + x[ 7] = MemUtil.RotateLeft32(x[ 7] ^ x[ 8], 12); + x[ 2] += x[ 7]; + x[13] = MemUtil.RotateLeft32(x[13] ^ x[ 2], 8); + x[ 8] += x[13]; + x[ 7] = MemUtil.RotateLeft32(x[ 7] ^ x[ 8], 7); + + x[ 3] += x[ 4]; + x[14] = MemUtil.RotateLeft32(x[14] ^ x[ 3], 16); + x[ 9] += x[14]; + x[ 4] = MemUtil.RotateLeft32(x[ 4] ^ x[ 9], 12); + x[ 3] += x[ 4]; + x[14] = MemUtil.RotateLeft32(x[14] ^ x[ 3], 8); + x[ 9] += x[14]; + x[ 4] = MemUtil.RotateLeft32(x[ 4] ^ x[ 9], 7); + } + + for(int i = 0; i < 16; ++i) x[i] += s[i]; + + for(int i = 0; i < 16; ++i) + { + int i4 = i << 2; + uint xi = x[i]; + + pBlock[i4] = (byte)xi; + pBlock[i4 + 1] = (byte)(xi >> 8); + pBlock[i4 + 2] = (byte)(xi >> 16); + pBlock[i4 + 3] = (byte)(xi >> 24); + } + + ++s[12]; + if(s[12] == 0) + { + if(!m_bLargeCounter) + throw new InvalidOperationException( + KLRes.EncDataTooLarge.Replace(@"{PARAM}", StrNameRfc)); + ++s[13]; // Increment high half of large counter + } + } + } + + public long Seek(long lOffset, SeekOrigin so) + { + if(so != SeekOrigin.Begin) throw new NotSupportedException(); + + if((lOffset < 0) || ((lOffset & 63) != 0) || + ((lOffset >> 6) > (long)uint.MaxValue)) + throw new ArgumentOutOfRangeException("lOffset"); + + m_s[12] = (uint)(lOffset >> 6); + InvalidateBlock(); + + return lOffset; + } + } +} diff --git a/src/KeePassLib2Android/Cryptography/Cipher/ChaCha20Engine.cs b/src/KeePassLib2Android/Cryptography/Cipher/ChaCha20Engine.cs new file mode 100644 index 00000000..2fa6fe1d --- /dev/null +++ b/src/KeePassLib2Android/Cryptography/Cipher/ChaCha20Engine.cs @@ -0,0 +1,174 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2016 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.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Text; + +using KeePassLib.Resources; + +namespace KeePassLib.Cryptography.Cipher +{ + public sealed class ChaCha20Engine : ICipherEngine2 + { + private PwUuid m_uuid = new PwUuid(new byte[] { + 0xD6, 0x03, 0x8A, 0x2B, 0x8B, 0x6F, 0x4C, 0xB5, + 0xA5, 0x24, 0x33, 0x9A, 0x31, 0xDB, 0xB5, 0x9A + }); + + public PwUuid CipherUuid + { + get { return m_uuid; } + } + + public string DisplayName + { + get + { + return ("ChaCha20 (" + KLRes.KeyBits.Replace(@"{PARAM}", + "256") + ", RFC 7539)"); + } + } + + public int KeyLength + { + get { return 32; } + } + + public int IVLength + { + get { return 12; } // 96 bits + } + + public Stream EncryptStream(Stream sPlainText, byte[] pbKey, byte[] pbIV) + { + return new ChaCha20Stream(sPlainText, true, pbKey, pbIV); + } + + public Stream DecryptStream(Stream sEncrypted, byte[] pbKey, byte[] pbIV) + { + return new ChaCha20Stream(sEncrypted, false, pbKey, pbIV); + } + } + + internal sealed class ChaCha20Stream : Stream + { + private Stream m_sBase; + private readonly bool m_bWriting; + private ChaCha20Cipher m_c; + + private byte[] m_pbBuffer = null; + + public override bool CanRead + { + get { return !m_bWriting; } + } + + public override bool CanSeek + { + get { return false; } + } + + public override bool CanWrite + { + get { return m_bWriting; } + } + + public override long Length + { + get { Debug.Assert(false); throw new NotSupportedException(); } + } + + public override long Position + { + get { Debug.Assert(false); throw new NotSupportedException(); } + set { Debug.Assert(false); throw new NotSupportedException(); } + } + + public ChaCha20Stream(Stream sBase, bool bWriting, byte[] pbKey32, + byte[] pbIV12) + { + if(sBase == null) throw new ArgumentNullException("sBase"); + + m_sBase = sBase; + m_bWriting = bWriting; + m_c = new ChaCha20Cipher(pbKey32, pbIV12); + } + + protected override void Dispose(bool bDisposing) + { + if(!bDisposing) return; + + if(m_sBase != null) + { + m_c.Dispose(); + m_c = null; + + m_sBase.Close(); + m_sBase = null; + } + + m_pbBuffer = null; + } + + public override void Flush() + { + Debug.Assert(m_sBase != null); + if(m_bWriting && (m_sBase != null)) m_sBase.Flush(); + } + + public override long Seek(long lOffset, SeekOrigin soOrigin) + { + Debug.Assert(false); + throw new NotImplementedException(); + } + + public override void SetLength(long lValue) + { + Debug.Assert(false); + throw new NotImplementedException(); + } + + public override int Read(byte[] pbBuffer, int iOffset, int nCount) + { + if(m_bWriting) throw new InvalidOperationException(); + + int cbRead = m_sBase.Read(pbBuffer, iOffset, nCount); + m_c.Decrypt(pbBuffer, iOffset, cbRead); + return cbRead; + } + + public override void Write(byte[] pbBuffer, int iOffset, int nCount) + { + if(nCount < 0) throw new ArgumentOutOfRangeException("nCount"); + if(nCount == 0) return; + + if(!m_bWriting) throw new InvalidOperationException(); + + if((m_pbBuffer == null) || (m_pbBuffer.Length < nCount)) + m_pbBuffer = new byte[nCount]; + Array.Copy(pbBuffer, iOffset, m_pbBuffer, 0, nCount); + + m_c.Encrypt(m_pbBuffer, 0, nCount); + m_sBase.Write(m_pbBuffer, 0, nCount); + } + } +} diff --git a/src/KeePassLib2Android/Cryptography/Cipher/CipherPool.cs b/src/KeePassLib2Android/Cryptography/Cipher/CipherPool.cs index c7668ac7..836a157c 100644 --- a/src/KeePassLib2Android/Cryptography/Cipher/CipherPool.cs +++ b/src/KeePassLib2Android/Cryptography/Cipher/CipherPool.cs @@ -40,12 +40,17 @@ namespace KeePassLib.Cryptography.Cipher { get { - if(m_poolGlobal != null) return m_poolGlobal; + CipherPool cp = m_poolGlobal; + if(cp == null) + { + cp = new CipherPool(); + cp.AddCipher(new StandardAesEngine()); + cp.AddCipher(new ChaCha20Engine()); - m_poolGlobal = new CipherPool(); - m_poolGlobal.AddCipher(new StandardAesEngine()); + m_poolGlobal = cp; + } - return m_poolGlobal; + return cp; } } diff --git a/src/KeePassLib2Android/Cryptography/Cipher/CtrBlockCipher.cs b/src/KeePassLib2Android/Cryptography/Cipher/CtrBlockCipher.cs new file mode 100644 index 00000000..87ec3daa --- /dev/null +++ b/src/KeePassLib2Android/Cryptography/Cipher/CtrBlockCipher.cs @@ -0,0 +1,101 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2016 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.Collections.Generic; +using System.Diagnostics; +using System.Text; + +using KeePassLib.Utility; + +namespace KeePassLib.Cryptography.Cipher +{ + public abstract class CtrBlockCipher : IDisposable + { + private byte[] m_pBlock; + private int m_iBlockPos; + + public abstract int BlockSize + { + get; + } + + public CtrBlockCipher() + { + int cb = this.BlockSize; + if(cb <= 0) throw new InvalidOperationException("this.BlockSize"); + + m_pBlock = new byte[cb]; + m_iBlockPos = cb; + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool bDisposing) + { + MemUtil.ZeroByteArray(m_pBlock); + m_iBlockPos = m_pBlock.Length; + } + + protected void InvalidateBlock() + { + m_iBlockPos = m_pBlock.Length; + } + + protected abstract void NextBlock(byte[] pBlock); + + public void Encrypt(byte[] m, int iOffset, int cb) + { + if(m == null) throw new ArgumentNullException("m"); + if(iOffset < 0) throw new ArgumentOutOfRangeException("iOffset"); + if(cb < 0) throw new ArgumentOutOfRangeException("cb"); + if(iOffset > (m.Length - cb)) throw new ArgumentOutOfRangeException("cb"); + + int cbBlock = m_pBlock.Length; + + while(cb > 0) + { + Debug.Assert(m_iBlockPos <= cbBlock); + if(m_iBlockPos == cbBlock) + { + NextBlock(m_pBlock); + m_iBlockPos = 0; + } + + int cbCopy = Math.Min(cbBlock - m_iBlockPos, cb); + Debug.Assert(cbCopy > 0); + + MemUtil.XorArray(m_pBlock, m_iBlockPos, m, iOffset, cbCopy); + + m_iBlockPos += cbCopy; + iOffset += cbCopy; + cb -= cbCopy; + } + } + + public void Decrypt(byte[] m, int iOffset, int cb) + { + Encrypt(m, iOffset, cb); + } + } +} diff --git a/src/KeePassLib2Android/Cryptography/Cipher/ICipherEngine.cs b/src/KeePassLib2Android/Cryptography/Cipher/ICipherEngine.cs index 734368a0..39dd696c 100644 --- a/src/KeePassLib2Android/Cryptography/Cipher/ICipherEngine.cs +++ b/src/KeePassLib2Android/Cryptography/Cipher/ICipherEngine.cs @@ -63,4 +63,25 @@ namespace KeePassLib.Cryptography.Cipher /// Stream, from which the decrypted data can be read. Stream DecryptStream(Stream sEncrypted, byte[] pbKey, byte[] pbIV); } + + public interface ICipherEngine2 : ICipherEngine + { + /// + /// Length of an encryption key in bytes. + /// The base ICipherEngine assumes 32. + /// + int KeyLength + { + get; + } + + /// + /// Length of the initialization vector in bytes. + /// The base ICipherEngine assumes 16. + /// + int IVLength + { + get; + } + } } diff --git a/src/KeePassLib2Android/Cryptography/Cipher/Salsa20Cipher.cs b/src/KeePassLib2Android/Cryptography/Cipher/Salsa20Cipher.cs index 534adfa2..55be9786 100644 --- a/src/KeePassLib2Android/Cryptography/Cipher/Salsa20Cipher.cs +++ b/src/KeePassLib2Android/Cryptography/Cipher/Salsa20Cipher.cs @@ -17,182 +17,145 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ -// Implementation of the Salsa20 cipher, based on the eSTREAM submission. +// Implementation of the Salsa20 cipher, based on the eSTREAM +// submission by D. J. Bernstein. using System; +using System.Collections.Generic; using System.Diagnostics; using KeePassLib.Utility; namespace KeePassLib.Cryptography.Cipher { - public sealed class Salsa20Cipher : IDisposable + public sealed class Salsa20Cipher : CtrBlockCipher { - private uint[] m_state = new uint[16]; + private uint[] m_s = new uint[16]; // State private uint[] m_x = new uint[16]; // Working buffer - private byte[] m_output = new byte[64]; - private int m_outputPos = 64; - - private static readonly uint[] m_sigma = new uint[4] { + private static readonly uint[] g_sigma = new uint[4] { 0x61707865, 0x3320646E, 0x79622D32, 0x6B206574 }; - public Salsa20Cipher(byte[] pbKey32, byte[] pbIV8) + public override int BlockSize { - KeySetup(pbKey32); - IvSetup(pbIV8); + get { return 64; } } - ~Salsa20Cipher() + public Salsa20Cipher(byte[] pbKey32, byte[] pbIV8) : base() { - Dispose(false); + if(pbKey32 == null) throw new ArgumentNullException("pbKey32"); + if(pbKey32.Length != 32) throw new ArgumentOutOfRangeException("pbKey32"); + if(pbIV8 == null) throw new ArgumentNullException("pbIV8"); + if(pbIV8.Length != 8) throw new ArgumentOutOfRangeException("pbIV8"); + + // Key setup + m_s[1] = MemUtil.BytesToUInt32(pbKey32, 0); + m_s[2] = MemUtil.BytesToUInt32(pbKey32, 4); + m_s[3] = MemUtil.BytesToUInt32(pbKey32, 8); + m_s[4] = MemUtil.BytesToUInt32(pbKey32, 12); + m_s[11] = MemUtil.BytesToUInt32(pbKey32, 16); + m_s[12] = MemUtil.BytesToUInt32(pbKey32, 20); + m_s[13] = MemUtil.BytesToUInt32(pbKey32, 24); + m_s[14] = MemUtil.BytesToUInt32(pbKey32, 28); + m_s[0] = g_sigma[0]; + m_s[5] = g_sigma[1]; + m_s[10] = g_sigma[2]; + m_s[15] = g_sigma[3]; + + // IV setup + m_s[6] = MemUtil.BytesToUInt32(pbIV8, 0); + m_s[7] = MemUtil.BytesToUInt32(pbIV8, 4); + m_s[8] = 0; // Counter, low + m_s[9] = 0; // Counter, high } - public void Dispose() + protected override void Dispose(bool bDisposing) { - Dispose(true); - GC.SuppressFinalize(this); + MemUtil.ZeroArray(m_s); + MemUtil.ZeroArray(m_x); + + base.Dispose(bDisposing); } - private void Dispose(bool bDisposing) + protected override void NextBlock(byte[] pBlock) { - // Clear sensitive data - Array.Clear(m_state, 0, m_state.Length); - Array.Clear(m_x, 0, m_x.Length); - } + if(pBlock == null) throw new ArgumentNullException("pBlock"); + if(pBlock.Length != 64) throw new ArgumentOutOfRangeException("pBlock"); - private void NextOutput() - { - uint[] x = m_x; // Local alias for working buffer - - // Compiler/runtime might remove array bound checks after this + // x is a local alias for the working buffer; with this, + // the compiler/runtime might remove some checks + uint[] x = m_x; + if(x == null) throw new InvalidOperationException(); if(x.Length < 16) throw new InvalidOperationException(); - Array.Copy(m_state, x, 16); + uint[] s = m_s; + if(s == null) throw new InvalidOperationException(); + if(s.Length < 16) throw new InvalidOperationException(); + + Array.Copy(s, x, 16); unchecked { - for(int i = 0; i < 10; ++i) // (int i = 20; i > 0; i -= 2) + // 10 * 8 quarter rounds = 20 rounds + for(int i = 0; i < 10; ++i) { - x[ 4] ^= Rotl32(x[ 0] + x[12], 7); - x[ 8] ^= Rotl32(x[ 4] + x[ 0], 9); - x[12] ^= Rotl32(x[ 8] + x[ 4], 13); - x[ 0] ^= Rotl32(x[12] + x[ 8], 18); - x[ 9] ^= Rotl32(x[ 5] + x[ 1], 7); - x[13] ^= Rotl32(x[ 9] + x[ 5], 9); - x[ 1] ^= Rotl32(x[13] + x[ 9], 13); - x[ 5] ^= Rotl32(x[ 1] + x[13], 18); - x[14] ^= Rotl32(x[10] + x[ 6], 7); - x[ 2] ^= Rotl32(x[14] + x[10], 9); - x[ 6] ^= Rotl32(x[ 2] + x[14], 13); - x[10] ^= Rotl32(x[ 6] + x[ 2], 18); - x[ 3] ^= Rotl32(x[15] + x[11], 7); - x[ 7] ^= Rotl32(x[ 3] + x[15], 9); - x[11] ^= Rotl32(x[ 7] + x[ 3], 13); - x[15] ^= Rotl32(x[11] + x[ 7], 18); - x[ 1] ^= Rotl32(x[ 0] + x[ 3], 7); - x[ 2] ^= Rotl32(x[ 1] + x[ 0], 9); - x[ 3] ^= Rotl32(x[ 2] + x[ 1], 13); - x[ 0] ^= Rotl32(x[ 3] + x[ 2], 18); - x[ 6] ^= Rotl32(x[ 5] + x[ 4], 7); - x[ 7] ^= Rotl32(x[ 6] + x[ 5], 9); - x[ 4] ^= Rotl32(x[ 7] + x[ 6], 13); - x[ 5] ^= Rotl32(x[ 4] + x[ 7], 18); - x[11] ^= Rotl32(x[10] + x[ 9], 7); - x[ 8] ^= Rotl32(x[11] + x[10], 9); - x[ 9] ^= Rotl32(x[ 8] + x[11], 13); - x[10] ^= Rotl32(x[ 9] + x[ 8], 18); - x[12] ^= Rotl32(x[15] + x[14], 7); - x[13] ^= Rotl32(x[12] + x[15], 9); - x[14] ^= Rotl32(x[13] + x[12], 13); - x[15] ^= Rotl32(x[14] + x[13], 18); + x[ 4] ^= MemUtil.RotateLeft32(x[ 0] + x[12], 7); + x[ 8] ^= MemUtil.RotateLeft32(x[ 4] + x[ 0], 9); + x[12] ^= MemUtil.RotateLeft32(x[ 8] + x[ 4], 13); + x[ 0] ^= MemUtil.RotateLeft32(x[12] + x[ 8], 18); + + x[ 9] ^= MemUtil.RotateLeft32(x[ 5] + x[ 1], 7); + x[13] ^= MemUtil.RotateLeft32(x[ 9] + x[ 5], 9); + x[ 1] ^= MemUtil.RotateLeft32(x[13] + x[ 9], 13); + x[ 5] ^= MemUtil.RotateLeft32(x[ 1] + x[13], 18); + + x[14] ^= MemUtil.RotateLeft32(x[10] + x[ 6], 7); + x[ 2] ^= MemUtil.RotateLeft32(x[14] + x[10], 9); + x[ 6] ^= MemUtil.RotateLeft32(x[ 2] + x[14], 13); + x[10] ^= MemUtil.RotateLeft32(x[ 6] + x[ 2], 18); + + x[ 3] ^= MemUtil.RotateLeft32(x[15] + x[11], 7); + x[ 7] ^= MemUtil.RotateLeft32(x[ 3] + x[15], 9); + x[11] ^= MemUtil.RotateLeft32(x[ 7] + x[ 3], 13); + x[15] ^= MemUtil.RotateLeft32(x[11] + x[ 7], 18); + + x[ 1] ^= MemUtil.RotateLeft32(x[ 0] + x[ 3], 7); + x[ 2] ^= MemUtil.RotateLeft32(x[ 1] + x[ 0], 9); + x[ 3] ^= MemUtil.RotateLeft32(x[ 2] + x[ 1], 13); + x[ 0] ^= MemUtil.RotateLeft32(x[ 3] + x[ 2], 18); + + x[ 6] ^= MemUtil.RotateLeft32(x[ 5] + x[ 4], 7); + x[ 7] ^= MemUtil.RotateLeft32(x[ 6] + x[ 5], 9); + x[ 4] ^= MemUtil.RotateLeft32(x[ 7] + x[ 6], 13); + x[ 5] ^= MemUtil.RotateLeft32(x[ 4] + x[ 7], 18); + + x[11] ^= MemUtil.RotateLeft32(x[10] + x[ 9], 7); + x[ 8] ^= MemUtil.RotateLeft32(x[11] + x[10], 9); + x[ 9] ^= MemUtil.RotateLeft32(x[ 8] + x[11], 13); + x[10] ^= MemUtil.RotateLeft32(x[ 9] + x[ 8], 18); + + x[12] ^= MemUtil.RotateLeft32(x[15] + x[14], 7); + x[13] ^= MemUtil.RotateLeft32(x[12] + x[15], 9); + x[14] ^= MemUtil.RotateLeft32(x[13] + x[12], 13); + x[15] ^= MemUtil.RotateLeft32(x[14] + x[13], 18); } - for(int i = 0; i < 16; ++i) - x[i] += m_state[i]; + for(int i = 0; i < 16; ++i) x[i] += s[i]; for(int i = 0; i < 16; ++i) { - m_output[i << 2] = (byte)x[i]; - m_output[(i << 2) + 1] = (byte)(x[i] >> 8); - m_output[(i << 2) + 2] = (byte)(x[i] >> 16); - m_output[(i << 2) + 3] = (byte)(x[i] >> 24); + int i4 = i << 2; + uint xi = x[i]; + + pBlock[i4] = (byte)xi; + pBlock[i4 + 1] = (byte)(xi >> 8); + pBlock[i4 + 2] = (byte)(xi >> 16); + pBlock[i4 + 3] = (byte)(xi >> 24); } - m_outputPos = 0; - ++m_state[8]; - if(m_state[8] == 0) ++m_state[9]; - } - } - - private static uint Rotl32(uint x, int b) - { - unchecked - { - return ((x << b) | (x >> (32 - b))); - } - } - - private static uint U8To32Little(byte[] pb, int iOffset) - { - unchecked - { - return ((uint)pb[iOffset] | ((uint)pb[iOffset + 1] << 8) | - ((uint)pb[iOffset + 2] << 16) | ((uint)pb[iOffset + 3] << 24)); - } - } - - private void KeySetup(byte[] k) - { - if(k == null) throw new ArgumentNullException("k"); - if(k.Length != 32) throw new ArgumentException(); - - m_state[1] = U8To32Little(k, 0); - m_state[2] = U8To32Little(k, 4); - m_state[3] = U8To32Little(k, 8); - m_state[4] = U8To32Little(k, 12); - m_state[11] = U8To32Little(k, 16); - m_state[12] = U8To32Little(k, 20); - m_state[13] = U8To32Little(k, 24); - m_state[14] = U8To32Little(k, 28); - m_state[0] = m_sigma[0]; - m_state[5] = m_sigma[1]; - m_state[10] = m_sigma[2]; - m_state[15] = m_sigma[3]; - } - - private void IvSetup(byte[] pbIV) - { - if(pbIV == null) throw new ArgumentNullException("pbIV"); - if(pbIV.Length != 8) throw new ArgumentException(); - - m_state[6] = U8To32Little(pbIV, 0); - m_state[7] = U8To32Little(pbIV, 4); - m_state[8] = 0; - m_state[9] = 0; - } - - public void Encrypt(byte[] m, int nByteCount, bool bXor) - { - if(m == null) throw new ArgumentNullException("m"); - if(nByteCount > m.Length) throw new ArgumentException(); - - int nBytesRem = nByteCount, nOffset = 0; - while(nBytesRem > 0) - { - Debug.Assert((m_outputPos >= 0) && (m_outputPos <= 64)); - if(m_outputPos == 64) NextOutput(); - Debug.Assert(m_outputPos < 64); - - int nCopy = Math.Min(64 - m_outputPos, nBytesRem); - - if(bXor) MemUtil.XorArray(m_output, m_outputPos, m, nOffset, nCopy); - else Array.Copy(m_output, m_outputPos, m, nOffset, nCopy); - - m_outputPos += nCopy; - nBytesRem -= nCopy; - nOffset += nCopy; + ++s[8]; + if(s[8] == 0) ++s[9]; } } } diff --git a/src/KeePassLib2Android/Cryptography/Cipher/StandardAesEngine.cs b/src/KeePassLib2Android/Cryptography/Cipher/StandardAesEngine.cs index 4b4095e3..eebd1248 100644 --- a/src/KeePassLib2Android/Cryptography/Cipher/StandardAesEngine.cs +++ b/src/KeePassLib2Android/Cryptography/Cipher/StandardAesEngine.cs @@ -42,7 +42,7 @@ namespace KeePassLib.Cryptography.Cipher private const PaddingMode m_rCipherPadding = PaddingMode.PKCS7; #endif - private static PwUuid m_uuidAes = null; + private static PwUuid g_uuidAes = null; /// /// UUID of the cipher engine. This ID uniquely identifies the @@ -52,12 +52,16 @@ namespace KeePassLib.Cryptography.Cipher { get { - if(m_uuidAes == null) - m_uuidAes = new PwUuid(new byte[]{ + PwUuid pu = g_uuidAes; + if(pu == null) + { + pu = new PwUuid(new byte[] { 0x31, 0xC1, 0xF2, 0xE6, 0xBF, 0x71, 0x43, 0x50, 0xBE, 0x58, 0x05, 0x21, 0x6A, 0xFC, 0x5A, 0xFF }); + g_uuidAes = pu; + } - return m_uuidAes; + return pu; } } @@ -72,7 +76,14 @@ namespace KeePassLib.Cryptography.Cipher /// /// Get a displayable name describing this cipher engine. /// - public string DisplayName { get { return KLRes.EncAlgorithmAes; } } + public string DisplayName + { + get + { + return ("AES/Rijndael (" + KLRes.KeyBits.Replace(@"{PARAM}", + "256") + ", FIPS 197)"); + } + } private static void ValidateArguments(Stream stream, bool bEncrypt, byte[] pbKey, byte[] pbIV) { diff --git a/src/KeePassLib2Android/Cryptography/CryptoRandom.cs b/src/KeePassLib2Android/Cryptography/CryptoRandom.cs index a8b22618..837bbb8c 100644 --- a/src/KeePassLib2Android/Cryptography/CryptoRandom.cs +++ b/src/KeePassLib2Android/Cryptography/CryptoRandom.cs @@ -40,14 +40,14 @@ namespace KeePassLib.Cryptography public sealed class CryptoRandom { private byte[] m_pbEntropyPool = new byte[64]; - private uint m_uCounter; + private ulong m_uCounter; private RNGCryptoServiceProvider m_rng = new RNGCryptoServiceProvider(); private ulong m_uGeneratedBytesCount = 0; private static object g_oSyncRoot = new object(); private object m_oSyncRoot = new object(); - private static CryptoRandom m_pInstance = null; + private static CryptoRandom g_pInstance = null; public static CryptoRandom Instance { get @@ -55,11 +55,11 @@ namespace KeePassLib.Cryptography CryptoRandom cr; lock(g_oSyncRoot) { - cr = m_pInstance; + cr = g_pInstance; if(cr == null) { cr = new CryptoRandom(); - m_pInstance = cr; + g_pInstance = cr; } } @@ -90,10 +90,12 @@ namespace KeePassLib.Cryptography private CryptoRandom() { - Random r = new Random(); - m_uCounter = (uint)r.Next(); + Random rWeak = new Random(); + byte[] pb = new byte[8]; + rWeak.NextBytes(pb); + m_uCounter = MemUtil.BytesToUInt64(pb); - AddEntropy(GetSystemData(r)); + AddEntropy(GetSystemData(rWeak)); AddEntropy(GetCspData()); } @@ -109,32 +111,40 @@ namespace KeePassLib.Cryptography if(pbEntropy.Length == 0) { Debug.Assert(false); return; } byte[] pbNewData = pbEntropy; - if(pbEntropy.Length >= 64) + if(pbEntropy.Length > 64) { #if KeePassLibSD - SHA256Managed shaNew = new SHA256Managed(); + using(SHA256Managed shaNew = new SHA256Managed()) #else - SHA512Managed shaNew = new SHA512Managed(); + using(SHA512Managed shaNew = new SHA512Managed()) #endif - pbNewData = shaNew.ComputeHash(pbEntropy); + { + pbNewData = shaNew.ComputeHash(pbEntropy); + } } - MemoryStream ms = new MemoryStream(); lock(m_oSyncRoot) { - ms.Write(m_pbEntropyPool, 0, m_pbEntropyPool.Length); - ms.Write(pbNewData, 0, pbNewData.Length); + int cbPool = m_pbEntropyPool.Length; + int cbNew = pbNewData.Length; + + byte[] pbCmp = new byte[cbPool + cbNew]; + Array.Copy(m_pbEntropyPool, pbCmp, cbPool); + Array.Copy(pbNewData, 0, pbCmp, cbPool, cbNew); + + MemUtil.ZeroByteArray(m_pbEntropyPool); - byte[] pbFinal = ms.ToArray(); #if KeePassLibSD - SHA256Managed shaPool = new SHA256Managed(); + using(SHA256Managed shaPool = new SHA256Managed()) #else - Debug.Assert(pbFinal.Length == (64 + pbNewData.Length)); - SHA512Managed shaPool = new SHA512Managed(); + using(SHA512Managed shaPool = new SHA512Managed()) #endif - m_pbEntropyPool = shaPool.ComputeHash(pbFinal); + { + m_pbEntropyPool = shaPool.ComputeHash(pbCmp); + } + + MemUtil.ZeroByteArray(pbCmp); } - ms.Close(); } private static byte[] GetSystemData(Random rWeak) @@ -142,11 +152,11 @@ namespace KeePassLib.Cryptography MemoryStream ms = new MemoryStream(); byte[] pb; - pb = MemUtil.UInt32ToBytes((uint)Environment.TickCount); - ms.Write(pb, 0, pb.Length); + pb = MemUtil.Int32ToBytes(Environment.TickCount); + MemUtil.Write(ms, pb); - pb = TimeUtil.PackTime(DateTime.Now); - ms.Write(pb, 0, pb.Length); + pb = MemUtil.Int64ToBytes(DateTime.UtcNow.ToBinary()); + MemUtil.Write(ms, pb); #if !KeePassLibSD // In try-catch for systems without GUI; @@ -154,79 +164,79 @@ namespace KeePassLib.Cryptography try { Point pt = Cursor.Position; - pb = MemUtil.UInt32ToBytes((uint)pt.X); - ms.Write(pb, 0, pb.Length); - pb = MemUtil.UInt32ToBytes((uint)pt.Y); - ms.Write(pb, 0, pb.Length); + pb = MemUtil.Int32ToBytes(pt.X); + MemUtil.Write(ms, pb); + pb = MemUtil.Int32ToBytes(pt.Y); + MemUtil.Write(ms, pb); } catch(Exception) { } #endif - pb = MemUtil.UInt32ToBytes((uint)rWeak.Next()); - ms.Write(pb, 0, pb.Length); + pb = MemUtil.Int32ToBytes(rWeak.Next()); + MemUtil.Write(ms, pb); pb = MemUtil.UInt32ToBytes((uint)NativeLib.GetPlatformID()); - ms.Write(pb, 0, pb.Length); + MemUtil.Write(ms, pb); try { - pb = MemUtil.UInt32ToBytes((uint)Environment.ProcessorCount); - ms.Write(pb, 0, pb.Length); + pb = MemUtil.Int32ToBytes(Environment.ProcessorCount); + MemUtil.Write(ms, pb); #if KeePassUAP Version v = EnvironmentExt.OSVersion.Version; #else Version v = Environment.OSVersion.Version; #endif - pb = MemUtil.UInt32ToBytes((uint)v.GetHashCode()); - ms.Write(pb, 0, pb.Length); + pb = MemUtil.Int32ToBytes(v.GetHashCode()); + MemUtil.Write(ms, pb); #if !KeePassUAP - pb = MemUtil.UInt64ToBytes((ulong)Environment.WorkingSet); - ms.Write(pb, 0, pb.Length); + pb = MemUtil.Int64ToBytes(Environment.WorkingSet); + MemUtil.Write(ms, pb); #endif } catch(Exception) { Debug.Assert(false); } #if KeePassUAP pb = DiagnosticsExt.GetProcessEntropy(); - ms.Write(pb, 0, pb.Length); + MemUtil.Write(ms, pb); #elif !KeePassLibSD Process p = null; try { p = Process.GetCurrentProcess(); - pb = MemUtil.UInt64ToBytes((ulong)p.Handle.ToInt64()); - ms.Write(pb, 0, pb.Length); - pb = MemUtil.UInt32ToBytes((uint)p.HandleCount); - ms.Write(pb, 0, pb.Length); - pb = MemUtil.UInt32ToBytes((uint)p.Id); - ms.Write(pb, 0, pb.Length); - pb = MemUtil.UInt64ToBytes((ulong)p.NonpagedSystemMemorySize64); - ms.Write(pb, 0, pb.Length); - pb = MemUtil.UInt64ToBytes((ulong)p.PagedMemorySize64); - ms.Write(pb, 0, pb.Length); - pb = MemUtil.UInt64ToBytes((ulong)p.PagedSystemMemorySize64); - ms.Write(pb, 0, pb.Length); - pb = MemUtil.UInt64ToBytes((ulong)p.PeakPagedMemorySize64); - ms.Write(pb, 0, pb.Length); - pb = MemUtil.UInt64ToBytes((ulong)p.PeakVirtualMemorySize64); - ms.Write(pb, 0, pb.Length); - pb = MemUtil.UInt64ToBytes((ulong)p.PeakWorkingSet64); - ms.Write(pb, 0, pb.Length); - pb = MemUtil.UInt64ToBytes((ulong)p.PrivateMemorySize64); - ms.Write(pb, 0, pb.Length); - pb = MemUtil.UInt64ToBytes((ulong)p.StartTime.ToBinary()); - ms.Write(pb, 0, pb.Length); - pb = MemUtil.UInt64ToBytes((ulong)p.VirtualMemorySize64); - ms.Write(pb, 0, pb.Length); - pb = MemUtil.UInt64ToBytes((ulong)p.WorkingSet64); - ms.Write(pb, 0, pb.Length); + pb = MemUtil.Int64ToBytes(p.Handle.ToInt64()); + MemUtil.Write(ms, pb); + pb = MemUtil.Int32ToBytes(p.HandleCount); + MemUtil.Write(ms, pb); + pb = MemUtil.Int32ToBytes(p.Id); + MemUtil.Write(ms, pb); + pb = MemUtil.Int64ToBytes(p.NonpagedSystemMemorySize64); + MemUtil.Write(ms, pb); + pb = MemUtil.Int64ToBytes(p.PagedMemorySize64); + MemUtil.Write(ms, pb); + pb = MemUtil.Int64ToBytes(p.PagedSystemMemorySize64); + MemUtil.Write(ms, pb); + pb = MemUtil.Int64ToBytes(p.PeakPagedMemorySize64); + MemUtil.Write(ms, pb); + pb = MemUtil.Int64ToBytes(p.PeakVirtualMemorySize64); + MemUtil.Write(ms, pb); + pb = MemUtil.Int64ToBytes(p.PeakWorkingSet64); + MemUtil.Write(ms, pb); + pb = MemUtil.Int64ToBytes(p.PrivateMemorySize64); + MemUtil.Write(ms, pb); + pb = MemUtil.Int64ToBytes(p.StartTime.ToBinary()); + MemUtil.Write(ms, pb); + pb = MemUtil.Int64ToBytes(p.VirtualMemorySize64); + MemUtil.Write(ms, pb); + pb = MemUtil.Int64ToBytes(p.WorkingSet64); + MemUtil.Write(ms, pb); // Not supported in Mono 1.2.6: // pb = MemUtil.UInt32ToBytes((uint)p.SessionId); - // ms.Write(pb, 0, pb.Length); + // MemUtil.Write(ms, pb); } catch(Exception) { Debug.Assert(NativeLib.IsUnix()); } finally @@ -237,7 +247,7 @@ namespace KeePassLib.Cryptography #endif pb = Guid.NewGuid().ToByteArray(); - ms.Write(pb, 0, pb.Length); + MemUtil.Write(ms, pb); byte[] pbAll = ms.ToArray(); ms.Close(); @@ -256,28 +266,31 @@ namespace KeePassLib.Cryptography if(this.GenerateRandom256Pre != null) this.GenerateRandom256Pre(this, EventArgs.Empty); - byte[] pbFinal; + byte[] pbCmp; lock(m_oSyncRoot) { - unchecked { m_uCounter += 386047; } // Prime number - byte[] pbCounter = MemUtil.UInt32ToBytes(m_uCounter); + m_uCounter += 0x74D8B29E4D38E161UL; // Prime number + byte[] pbCounter = MemUtil.UInt64ToBytes(m_uCounter); byte[] pbCspRandom = GetCspData(); - MemoryStream ms = new MemoryStream(); - ms.Write(m_pbEntropyPool, 0, m_pbEntropyPool.Length); - ms.Write(pbCounter, 0, pbCounter.Length); - ms.Write(pbCspRandom, 0, pbCspRandom.Length); - pbFinal = ms.ToArray(); - Debug.Assert(pbFinal.Length == (m_pbEntropyPool.Length + - pbCounter.Length + pbCspRandom.Length)); - ms.Close(); + int cbPool = m_pbEntropyPool.Length; + int cbCtr = pbCounter.Length; + int cbCsp = pbCspRandom.Length; + + pbCmp = new byte[cbPool + cbCtr + cbCsp]; + Array.Copy(m_pbEntropyPool, pbCmp, cbPool); + Array.Copy(pbCounter, 0, pbCmp, cbPool, cbCtr); + Array.Copy(pbCspRandom, 0, pbCmp, cbPool + cbCtr, cbCsp); + + MemUtil.ZeroByteArray(pbCspRandom); m_uGeneratedBytesCount += 32; } - SHA256Managed sha256 = new SHA256Managed(); - return sha256.ComputeHash(pbFinal); + byte[] pbRet = CryptoUtil.HashSha256(pbCmp); + MemUtil.ZeroByteArray(pbCmp); + return pbRet; } /// @@ -289,29 +302,32 @@ namespace KeePassLib.Cryptography /// random bytes. public byte[] GetRandomBytes(uint uRequestedBytes) { - if(uRequestedBytes == 0) return new byte[0]; // Allow zero-length array + if(uRequestedBytes == 0) return MemUtil.EmptyByteArray; + if(uRequestedBytes > (uint)int.MaxValue) + { + Debug.Assert(false); + throw new ArgumentOutOfRangeException("uRequestedBytes"); + } - byte[] pbRes = new byte[uRequestedBytes]; - long lPos = 0; + int cbRem = (int)uRequestedBytes; + byte[] pbRes = new byte[cbRem]; + int iPos = 0; - while(uRequestedBytes != 0) + while(cbRem != 0) { byte[] pbRandom256 = GenerateRandom256(); Debug.Assert(pbRandom256.Length == 32); - long lCopy = (long)((uRequestedBytes < 32) ? uRequestedBytes : 32); + int cbCopy = Math.Min(cbRem, 32); + Array.Copy(pbRandom256, 0, pbRes, iPos, cbCopy); -#if (!KeePassLibSD && !KeePassUAP) - Array.Copy(pbRandom256, 0, pbRes, lPos, lCopy); -#else - Array.Copy(pbRandom256, 0, pbRes, (int)lPos, (int)lCopy); -#endif + MemUtil.ZeroByteArray(pbRandom256); - lPos += lCopy; - uRequestedBytes -= (uint)lCopy; + iPos += cbCopy; + cbRem -= cbCopy; } - Debug.Assert((int)lPos == pbRes.Length); + Debug.Assert(iPos == pbRes.Length); return pbRes; } } diff --git a/src/KeePassLib2Android/Cryptography/CryptoRandomStream.cs b/src/KeePassLib2Android/Cryptography/CryptoRandomStream.cs index 44d9c63a..726edb15 100644 --- a/src/KeePassLib2Android/Cryptography/CryptoRandomStream.cs +++ b/src/KeePassLib2Android/Cryptography/CryptoRandomStream.cs @@ -25,6 +25,7 @@ using System.Security.Cryptography; #endif using KeePassLib.Cryptography.Cipher; +using KeePassLib.Utility; namespace KeePassLib.Cryptography { @@ -40,6 +41,7 @@ namespace KeePassLib.Cryptography /// /// A variant of the ARCFour algorithm (RC4 incompatible). + /// Insecure; for backward compatibility only. /// ArcFourVariant = 1, @@ -48,7 +50,12 @@ namespace KeePassLib.Cryptography /// Salsa20 = 2, - Count = 3 + /// + /// ChaCha20 stream cipher algorithm. + /// + ChaCha20 = 3, + + Count = 4 } /// @@ -59,45 +66,68 @@ namespace KeePassLib.Cryptography /// public sealed class CryptoRandomStream { - private CrsAlgorithm m_crsAlgorithm; + private readonly CrsAlgorithm m_crsAlgorithm; private byte[] m_pbState = null; private byte m_i = 0; private byte m_j = 0; private Salsa20Cipher m_salsa20 = null; + private ChaCha20Cipher m_chacha20 = null; /// /// Construct a new cryptographically secure random stream object. /// - /// Algorithm to use. + /// Algorithm to use. /// Initialization key. Must not be null and /// must contain at least 1 byte. - /// Thrown if the - /// parameter is null. - /// Thrown if the - /// parameter contains no bytes or the - /// algorithm is unknown. - public CryptoRandomStream(CrsAlgorithm genAlgorithm, byte[] pbKey) + public CryptoRandomStream(CrsAlgorithm a, byte[] pbKey) { - m_crsAlgorithm = genAlgorithm; + if(pbKey == null) { Debug.Assert(false); throw new ArgumentNullException("pbKey"); } - Debug.Assert(pbKey != null); if(pbKey == null) throw new ArgumentNullException("pbKey"); + int cbKey = pbKey.Length; + if(cbKey <= 0) + { + Debug.Assert(false); // Need at least one byte + throw new ArgumentOutOfRangeException("pbKey"); + } - uint uKeyLen = (uint)pbKey.Length; - Debug.Assert(uKeyLen != 0); if(uKeyLen == 0) throw new ArgumentException(); + m_crsAlgorithm = a; - if(genAlgorithm == CrsAlgorithm.ArcFourVariant) + if(a == CrsAlgorithm.ChaCha20) + { + byte[] pbKey32 = new byte[32]; + byte[] pbIV12 = new byte[12]; + + using(SHA512Managed h = new SHA512Managed()) + { + byte[] pbHash = h.ComputeHash(pbKey); + Array.Copy(pbHash, pbKey32, 32); + Array.Copy(pbHash, 32, pbIV12, 0, 12); + MemUtil.ZeroByteArray(pbHash); + } + + m_chacha20 = new ChaCha20Cipher(pbKey32, pbIV12, true); + } + else if(a == CrsAlgorithm.Salsa20) + { + byte[] pbKey32 = CryptoUtil.HashSha256(pbKey); + byte[] pbIV8 = new byte[8] { 0xE8, 0x30, 0x09, 0x4B, + 0x97, 0x20, 0x5D, 0x2A }; // Unique constant + + m_salsa20 = new Salsa20Cipher(pbKey32, pbIV8); + } + else if(a == CrsAlgorithm.ArcFourVariant) { // Fill the state linearly m_pbState = new byte[256]; - for(uint w = 0; w < 256; ++w) m_pbState[w] = (byte)w; + for(int w = 0; w < 256; ++w) m_pbState[w] = (byte)w; unchecked { byte j = 0, t; - uint inxKey = 0; - for(uint w = 0; w < 256; ++w) // Key setup + int inxKey = 0; + for(int w = 0; w < 256; ++w) // Key setup { j += (byte)(m_pbState[w] + pbKey[inxKey]); @@ -106,25 +136,16 @@ namespace KeePassLib.Cryptography m_pbState[j] = t; ++inxKey; - if(inxKey >= uKeyLen) inxKey = 0; + if(inxKey >= cbKey) inxKey = 0; } } GetRandomBytes(512); // Increases security, see cryptanalysis } - else if(genAlgorithm == CrsAlgorithm.Salsa20) - { - SHA256Managed sha256 = new SHA256Managed(); - byte[] pbKey32 = sha256.ComputeHash(pbKey); - byte[] pbIV = new byte[8] { 0xE8, 0x30, 0x09, 0x4B, - 0x97, 0x20, 0x5D, 0x2A }; // Unique constant - - m_salsa20 = new Salsa20Cipher(pbKey32, pbIV); - } else // Unknown algorithm { Debug.Assert(false); - throw new ArgumentException(); + throw new ArgumentOutOfRangeException("a"); } } @@ -135,15 +156,23 @@ namespace KeePassLib.Cryptography /// Returns random bytes. public byte[] GetRandomBytes(uint uRequestedCount) { - if(uRequestedCount == 0) return new byte[0]; + if(uRequestedCount == 0) return MemUtil.EmptyByteArray; - byte[] pbRet = new byte[uRequestedCount]; + if(uRequestedCount > (uint)int.MaxValue) + throw new ArgumentOutOfRangeException("uRequestedCount"); + int cb = (int)uRequestedCount; - if(m_crsAlgorithm == CrsAlgorithm.ArcFourVariant) + byte[] pbRet = new byte[cb]; + + if(m_crsAlgorithm == CrsAlgorithm.ChaCha20) + m_chacha20.Encrypt(pbRet, 0, cb); + else if(m_crsAlgorithm == CrsAlgorithm.Salsa20) + m_salsa20.Encrypt(pbRet, 0, cb); + else if(m_crsAlgorithm == CrsAlgorithm.ArcFourVariant) { unchecked { - for(uint w = 0; w < uRequestedCount; ++w) + for(int w = 0; w < cb; ++w) { ++m_i; m_j += m_pbState[m_i]; @@ -157,8 +186,6 @@ namespace KeePassLib.Cryptography } } } - else if(m_crsAlgorithm == CrsAlgorithm.Salsa20) - m_salsa20.Encrypt(pbRet, pbRet.Length, false); else { Debug.Assert(false); } return pbRet; @@ -167,14 +194,7 @@ namespace KeePassLib.Cryptography public ulong GetRandomUInt64() { byte[] pb = GetRandomBytes(8); - - unchecked - { - return ((ulong)pb[0]) | ((ulong)pb[1] << 8) | - ((ulong)pb[2] << 16) | ((ulong)pb[3] << 24) | - ((ulong)pb[4] << 32) | ((ulong)pb[5] << 40) | - ((ulong)pb[6] << 48) | ((ulong)pb[7] << 56); - } + return MemUtil.BytesToUInt64(pb); } #if CRSBENCHMARK diff --git a/src/KeePassLib2Android/Cryptography/CryptoUtil.cs b/src/KeePassLib2Android/Cryptography/CryptoUtil.cs new file mode 100644 index 00000000..9cb7bcd6 --- /dev/null +++ b/src/KeePassLib2Android/Cryptography/CryptoUtil.cs @@ -0,0 +1,126 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2016 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.Collections.Generic; +using System.Diagnostics; +using System.Security.Cryptography; +using System.Text; + +using KeePassLib.Utility; + +namespace KeePassLib.Cryptography +{ + public static class CryptoUtil + { + public static byte[] HashSha256(byte[] pbData) + { + if(pbData == null) throw new ArgumentNullException("pbData"); + + return HashSha256(pbData, 0, pbData.Length); + } + + public static byte[] HashSha256(byte[] pbData, int iOffset, int cbCount) + { + if(pbData == null) throw new ArgumentNullException("pbData"); + +#if DEBUG + byte[] pbCopy = new byte[pbData.Length]; + Array.Copy(pbData, pbCopy, pbData.Length); +#endif + + byte[] pbHash; + using(SHA256Managed h = new SHA256Managed()) + { + pbHash = h.ComputeHash(pbData, iOffset, cbCount); + } + +#if DEBUG + // Ensure the data has not been modified + Debug.Assert(MemUtil.ArraysEqual(pbData, pbCopy)); + + Debug.Assert((pbHash != null) && (pbHash.Length == 32)); + byte[] pbZero = new byte[32]; + Debug.Assert(!MemUtil.ArraysEqual(pbHash, pbZero)); +#endif + + return pbHash; + } + + /// + /// Create a cryptographic key of length + /// (in bytes) from . + /// + public static byte[] ResizeKey(byte[] pbIn, int iInOffset, + int cbIn, int cbOut) + { + if(pbIn == null) throw new ArgumentNullException("pbIn"); + if(cbOut < 0) throw new ArgumentOutOfRangeException("cbOut"); + + if(cbOut == 0) return MemUtil.EmptyByteArray; + + byte[] pbHash; + if(cbOut <= 32) pbHash = HashSha256(pbIn, iInOffset, cbIn); + else + { + using(SHA512Managed h = new SHA512Managed()) + { + pbHash = h.ComputeHash(pbIn, iInOffset, cbIn); + } + } + + if(cbOut == pbHash.Length) return pbHash; + + byte[] pbRet = new byte[cbOut]; + if(cbOut < pbHash.Length) + Array.Copy(pbHash, pbRet, cbOut); + else + { + int iPos = 0; + ulong r = 0; + while(iPos < cbOut) + { + Debug.Assert(pbHash.Length == 64); + using(HMACSHA256 h = new HMACSHA256(pbHash)) + { + byte[] pbR = MemUtil.UInt64ToBytes(r); + byte[] pbPart = h.ComputeHash(pbR); + + int cbCopy = Math.Min(cbOut - iPos, pbPart.Length); + Debug.Assert(cbCopy > 0); + + Array.Copy(pbPart, 0, pbRet, iPos, cbCopy); + iPos += cbCopy; + ++r; + + MemUtil.ZeroByteArray(pbPart); + } + } + Debug.Assert(iPos == cbOut); + } + +#if DEBUG + byte[] pbZero = new byte[pbHash.Length]; + Debug.Assert(!MemUtil.ArraysEqual(pbHash, pbZero)); +#endif + MemUtil.ZeroByteArray(pbHash); + return pbRet; + } + } +} diff --git a/src/KeePassLib2Android/Cryptography/Hash/Blake2b.cs b/src/KeePassLib2Android/Cryptography/Hash/Blake2b.cs new file mode 100644 index 00000000..1a446aaa --- /dev/null +++ b/src/KeePassLib2Android/Cryptography/Hash/Blake2b.cs @@ -0,0 +1,229 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2016 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 +*/ + +// This implementation is based on the official reference C +// implementation by Samuel Neves (CC0 1.0 Universal). + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Security.Cryptography; +using System.Text; + +using KeePassLib.Utility; + +namespace KeePassLib.Cryptography.Hash +{ + public sealed class Blake2b : HashAlgorithm + { + private const int NbRounds = 12; + private const int NbBlockBytes = 128; + private const int NbMaxOutBytes = 64; + + private static readonly ulong[] g_vIV = new ulong[8] { + 0x6A09E667F3BCC908UL, 0xBB67AE8584CAA73BUL, + 0x3C6EF372FE94F82BUL, 0xA54FF53A5F1D36F1UL, + 0x510E527FADE682D1UL, 0x9B05688C2B3E6C1FUL, + 0x1F83D9ABFB41BD6BUL, 0x5BE0CD19137E2179UL + }; + + private static readonly int[] g_vSigma = new int[NbRounds * 16] { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3, + 11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4, + 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8, + 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13, + 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9, + 12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11, + 13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10, + 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5, + 10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 + }; + + private readonly int m_cbHashLength; + + private ulong[] m_h = new ulong[8]; + private ulong[] m_t = new ulong[2]; + private ulong[] m_f = new ulong[2]; + private byte[] m_buf = new byte[NbBlockBytes]; + private int m_cbBuf = 0; + + private ulong[] m_m = new ulong[16]; + private ulong[] m_v = new ulong[16]; + + public Blake2b() + { + m_cbHashLength = NbMaxOutBytes; + this.HashSizeValue = NbMaxOutBytes * 8; // Bits + + Initialize(); + } + + public Blake2b(int cbHashLength) + { + if((cbHashLength < 0) || (cbHashLength > NbMaxOutBytes)) + throw new ArgumentOutOfRangeException("cbHashLength"); + + m_cbHashLength = cbHashLength; + this.HashSizeValue = cbHashLength * 8; // Bits + + Initialize(); + } + + public override void Initialize() + { + Debug.Assert(m_h.Length == g_vIV.Length); + Array.Copy(g_vIV, m_h, m_h.Length); + + // Fan-out = 1, depth = 1 + m_h[0] ^= 0x0000000001010000UL ^ (ulong)m_cbHashLength; + + Array.Clear(m_t, 0, m_t.Length); + Array.Clear(m_f, 0, m_f.Length); + Array.Clear(m_buf, 0, m_buf.Length); + m_cbBuf = 0; + + Array.Clear(m_m, 0, m_m.Length); + Array.Clear(m_v, 0, m_v.Length); + } + + private static void G(ulong[] v, ulong[] m, int r16, int i, + int a, int b, int c, int d) + { + int p = r16 + i; + + v[a] += v[b] + m[g_vSigma[p]]; + v[d] = MemUtil.RotateRight64(v[d] ^ v[a], 32); + v[c] += v[d]; + v[b] = MemUtil.RotateRight64(v[b] ^ v[c], 24); + v[a] += v[b] + m[g_vSigma[p + 1]]; + v[d] = MemUtil.RotateRight64(v[d] ^ v[a], 16); + v[c] += v[d]; + v[b] = MemUtil.RotateRight64(v[b] ^ v[c], 63); + } + + private void Compress(byte[] pb, int iOffset) + { + ulong[] v = m_v; + ulong[] m = m_m; + ulong[] h = m_h; + + for(int i = 0; i < 16; ++i) + m[i] = MemUtil.BytesToUInt64(pb, iOffset + (i << 3)); + + Array.Copy(h, v, 8); + v[8] = g_vIV[0]; + v[9] = g_vIV[1]; + v[10] = g_vIV[2]; + v[11] = g_vIV[3]; + v[12] = g_vIV[4] ^ m_t[0]; + v[13] = g_vIV[5] ^ m_t[1]; + v[14] = g_vIV[6] ^ m_f[0]; + v[15] = g_vIV[7] ^ m_f[1]; + + for(int r = 0; r < NbRounds; ++r) + { + int r16 = r << 4; + + G(v, m, r16, 0, 0, 4, 8, 12); + G(v, m, r16, 2, 1, 5, 9, 13); + G(v, m, r16, 4, 2, 6, 10, 14); + G(v, m, r16, 6, 3, 7, 11, 15); + G(v, m, r16, 8, 0, 5, 10, 15); + G(v, m, r16, 10, 1, 6, 11, 12); + G(v, m, r16, 12, 2, 7, 8, 13); + G(v, m, r16, 14, 3, 4, 9, 14); + } + + for(int i = 0; i < 8; ++i) + h[i] ^= v[i] ^ v[i + 8]; + } + + private void IncrementCounter(ulong cb) + { + m_t[0] += cb; + if(m_t[0] < cb) ++m_t[1]; + } + + protected override void HashCore(byte[] array, int ibStart, int cbSize) + { + Debug.Assert(m_f[0] == 0); + + if((m_cbBuf + cbSize) > NbBlockBytes) // Not '>=' (buffer must not be empty) + { + int cbFill = NbBlockBytes - m_cbBuf; + if(cbFill > 0) Array.Copy(array, ibStart, m_buf, m_cbBuf, cbFill); + + IncrementCounter((ulong)NbBlockBytes); + Compress(m_buf, 0); + + m_cbBuf = 0; + cbSize -= cbFill; + ibStart += cbFill; + + while(cbSize > NbBlockBytes) // Not '>=' (buffer must not be empty) + { + IncrementCounter((ulong)NbBlockBytes); + Compress(array, ibStart); + + cbSize -= NbBlockBytes; + ibStart += NbBlockBytes; + } + } + + if(cbSize > 0) + { + Debug.Assert((m_cbBuf + cbSize) <= NbBlockBytes); + + Array.Copy(array, ibStart, m_buf, m_cbBuf, cbSize); + m_cbBuf += cbSize; + } + } + + protected override byte[] HashFinal() + { + if(m_f[0] != 0) { Debug.Assert(false); throw new InvalidOperationException(); } + Debug.Assert(((m_t[1] == 0) && (m_t[0] == 0)) || + (m_cbBuf > 0)); // Buffer must not be empty for last block processing + + m_f[0] = ulong.MaxValue; // Indicate last block + + int cbFill = NbBlockBytes - m_cbBuf; + if(cbFill > 0) Array.Clear(m_buf, m_cbBuf, cbFill); + + IncrementCounter((ulong)m_cbBuf); + Compress(m_buf, 0); + + byte[] pbHash = new byte[NbMaxOutBytes]; + for(int i = 0; i < m_h.Length; ++i) + MemUtil.UInt64ToBytesEx(m_h[i], pbHash, i << 3); + + if(m_cbHashLength == NbMaxOutBytes) return pbHash; + Debug.Assert(m_cbHashLength < NbMaxOutBytes); + + byte[] pbShort = new byte[m_cbHashLength]; + if(m_cbHashLength > 0) + Array.Copy(pbHash, pbShort, m_cbHashLength); + MemUtil.ZeroByteArray(pbHash); + return pbShort; + } + } +} diff --git a/src/KeePassLib2Android/Cryptography/HashingStreamEx.cs b/src/KeePassLib2Android/Cryptography/HashingStreamEx.cs index ec7279c1..27446d69 100644 --- a/src/KeePassLib2Android/Cryptography/HashingStreamEx.cs +++ b/src/KeePassLib2Android/Cryptography/HashingStreamEx.cs @@ -34,7 +34,7 @@ namespace KeePassLib.Cryptography public sealed class HashingStreamEx : Stream { private Stream m_sBaseStream; - private bool m_bWriting; + private readonly bool m_bWriting; private HashAlgorithm m_hash; private byte[] m_pbFinalHash = null; @@ -67,7 +67,7 @@ namespace KeePassLib.Cryptography public override long Position { get { return m_sBaseStream.Position; } - set { throw new NotSupportedException(); } + set { Debug.Assert(false); throw new NotSupportedException(); } } public HashingStreamEx(Stream sBaseStream, bool bWriting, HashAlgorithm hashAlgorithm) @@ -114,7 +114,7 @@ namespace KeePassLib.Cryptography { try { - m_hash.TransformFinalBlock(new byte[0], 0, 0); + m_hash.TransformFinalBlock(MemUtil.EmptyByteArray, 0, 0); m_pbFinalHash = m_hash.Hash; } diff --git a/src/KeePassLib2Android/Cryptography/KeyDerivation/AesKdf.cs b/src/KeePassLib2Android/Cryptography/KeyDerivation/AesKdf.cs new file mode 100644 index 00000000..b0644c4a --- /dev/null +++ b/src/KeePassLib2Android/Cryptography/KeyDerivation/AesKdf.cs @@ -0,0 +1,270 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2016 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.Collections.Generic; +using System.Diagnostics; +using System.Text; + +#if KeePassUAP +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Engines; +using Org.BouncyCastle.Crypto.Parameters; +#else +using System.Security.Cryptography; +#endif + +using KeePassLib.Cryptography; +using KeePassLib.Native; +using KeePassLib.Utility; + +namespace KeePassLib.Cryptography.KeyDerivation +{ + public sealed class AesKdf : KdfEngine + { + private static readonly PwUuid g_uuid = new PwUuid(new byte[] { + 0xC9, 0xD9, 0xF3, 0x9A, 0x62, 0x8A, 0x44, 0x60, + 0xBF, 0x74, 0x0D, 0x08, 0xC1, 0x8A, 0x4F, 0xEA }); + + public const string ParamRounds = "R"; // UInt64 + public const string ParamSeed = "S"; // Byte[32] + + public override PwUuid Uuid + { + get { return g_uuid; } + } + + public override string Name + { + get { return "AES-KDF"; } + } + + public AesKdf() + { + } + + public override KdfParameters GetDefaultParameters() + { + KdfParameters p = base.GetDefaultParameters(); + p.SetUInt64(ParamRounds, PwDefs.DefaultKeyEncryptionRounds); + return p; + } + + public override void Randomize(KdfParameters p) + { + if(p == null) { Debug.Assert(false); return; } + Debug.Assert(g_uuid.Equals(p.KdfUuid)); + + byte[] pbSeed = CryptoRandom.Instance.GetRandomBytes(32); + p.SetByteArray(ParamSeed, pbSeed); + } + + public override byte[] Transform(byte[] pbMsg, KdfParameters p) + { + if(pbMsg == null) throw new ArgumentNullException("pbMsg"); + if(p == null) throw new ArgumentNullException("p"); + + Type tRounds = p.GetTypeOf(ParamRounds); + if(tRounds == null) throw new ArgumentNullException("p.Rounds"); + if(tRounds != typeof(ulong)) throw new ArgumentOutOfRangeException("p.Rounds"); + ulong uRounds = p.GetUInt64(ParamRounds, 0); + + byte[] pbSeed = p.GetByteArray(ParamSeed); + if(pbSeed == null) throw new ArgumentNullException("p.Seed"); + + if(pbMsg.Length != 32) + { + Debug.Assert(false); + pbMsg = CryptoUtil.HashSha256(pbMsg); + } + + if(pbSeed.Length != 32) + { + Debug.Assert(false); + pbSeed = CryptoUtil.HashSha256(pbSeed); + } + + return TransformKey(pbMsg, pbSeed, uRounds); + } + + 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 + { + // Try to use the native library first + if(NativeLib.TransformKey256(pbNewKey, pbKeySeed32, uNumRounds)) + return CryptoUtil.HashSha256(pbNewKey); + + if(TransformKeyManaged(pbNewKey, pbKeySeed32, uNumRounds)) + return CryptoUtil.HashSha256(pbNewKey); + } + finally { MemUtil.ZeroByteArray(pbNewKey); } + + return null; + } + + internal static bool TransformKeyManaged(byte[] pbNewKey32, byte[] pbKeySeed32, + ulong uNumRounds) + { +#if KeePassUAP + KeyParameter kp = new KeyParameter(pbKeySeed32); + AesEngine aes = new AesEngine(); + aes.Init(true, kp); + + for(ulong i = 0; i < uNumRounds; ++i) + { + aes.ProcessBlock(pbNewKey32, 0, pbNewKey32, 0); + aes.ProcessBlock(pbNewKey32, 16, pbNewKey32, 16); + } +#else + 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); + } +#endif + + return true; + } + + public override KdfParameters GetBestParameters(uint uMilliseconds) + { + const ulong uStep = 3001; + ulong uRounds; + + KdfParameters p = GetDefaultParameters(); + + // Try native method + if(NativeLib.TransformKeyBenchmark256(uMilliseconds, out uRounds)) + { + p.SetUInt64(ParamRounds, uRounds); + return p; + } + + 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; + } + +#if KeePassUAP + KeyParameter kp = new KeyParameter(pbKey); + AesEngine aes = new AesEngine(); + aes.Init(true, kp); +#else + 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 = 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!"); + + p.SetUInt64(ParamRounds, PwDefs.DefaultKeyEncryptionRounds); + return p; + } +#endif + + uRounds = 0; + int tStart = Environment.TickCount; + while(true) + { + for(ulong j = 0; j < uStep; ++j) + { +#if KeePassUAP + aes.ProcessBlock(pbNewKey, 0, pbNewKey, 0); + aes.ProcessBlock(pbNewKey, 16, pbNewKey, 16); +#else + iCrypt.TransformBlock(pbNewKey, 0, 16, pbNewKey, 0); + iCrypt.TransformBlock(pbNewKey, 16, 16, pbNewKey, 16); +#endif + } + + uRounds += uStep; + if(uRounds < uStep) // Overflow check + { + uRounds = ulong.MaxValue; + break; + } + + uint tElapsed = (uint)(Environment.TickCount - tStart); + if(tElapsed > uMilliseconds) break; + } + + p.SetUInt64(ParamRounds, uRounds); + return p; + } + } +} diff --git a/src/KeePassLib2Android/Cryptography/KeyDerivation/Argon2Kdf.Core.cs b/src/KeePassLib2Android/Cryptography/KeyDerivation/Argon2Kdf.Core.cs new file mode 100644 index 00000000..bd90c427 --- /dev/null +++ b/src/KeePassLib2Android/Cryptography/KeyDerivation/Argon2Kdf.Core.cs @@ -0,0 +1,610 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2016 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 +*/ + +// This implementation is based on the official reference C +// implementation by Daniel Dinu and Dmitry Khovratovich (CC0 1.0). + +// Relative iterations (* = B2ROUND_ARRAYS \\ G_INLINED): +// * | false true +// ------+----------- +// false | 8885 9618 +// true | 9009 9636 +#define ARGON2_B2ROUND_ARRAYS +#define ARGON2_G_INLINED + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading; + +using KeePassLib.Cryptography.Hash; +using KeePassLib.Utility; + +namespace KeePassLib.Cryptography.KeyDerivation +{ + public sealed partial class Argon2Kdf : KdfEngine + { + private const ulong NbBlockSize = 1024; + private const ulong NbBlockSizeInQW = NbBlockSize / 8UL; + private const ulong NbSyncPoints = 4; + + private const int NbPreHashDigestLength = 64; + private const int NbPreHashSeedLength = NbPreHashDigestLength + 8; + +#if ARGON2_B2ROUND_ARRAYS + private static int[][] g_vFBCols = null; + private static int[][] g_vFBRows = null; +#endif + + private sealed class Argon2Ctx + { + public uint Version = 0; + + public ulong Lanes = 0; + public ulong TCost = 0; + public ulong MCost = 0; + public ulong MemoryBlocks = 0; + public ulong SegmentLength = 0; + public ulong LaneLength = 0; + + public ulong[] Mem = null; + } + + private sealed class Argon2ThreadInfo + { + public Argon2Ctx Context = null; + public ManualResetEvent Finished = new ManualResetEvent(false); + + public ulong Pass = 0; + public ulong Lane = 0; + public ulong Slice = 0; + public ulong Index = 0; + } + + private static byte[] Argon2d(byte[] pbMsg, byte[] pbSalt, uint uParallel, + ulong uMem, ulong uIt, int cbOut, uint uVersion, byte[] pbSecretKey, + byte[] pbAssocData) + { + pbSecretKey = (pbSecretKey ?? MemUtil.EmptyByteArray); + pbAssocData = (pbAssocData ?? MemUtil.EmptyByteArray); + +#if ARGON2_B2ROUND_ARRAYS + InitB2RoundIndexArrays(); +#endif + + Argon2Ctx ctx = new Argon2Ctx(); + ctx.Version = uVersion; + + ctx.Lanes = uParallel; + ctx.TCost = uIt; + ctx.MCost = uMem / NbBlockSize; + ctx.MemoryBlocks = Math.Max(ctx.MCost, 2UL * NbSyncPoints * ctx.Lanes); + + ctx.SegmentLength = ctx.MemoryBlocks / (ctx.Lanes * NbSyncPoints); + ctx.MemoryBlocks = ctx.SegmentLength * ctx.Lanes * NbSyncPoints; + + ctx.LaneLength = ctx.SegmentLength * NbSyncPoints; + + Debug.Assert(NbBlockSize == (NbBlockSizeInQW * + (ulong)Marshal.SizeOf(typeof(ulong)))); + ctx.Mem = new ulong[ctx.MemoryBlocks * NbBlockSizeInQW]; + + Blake2b h = new Blake2b(); + + // Initial hash + Debug.Assert(h.HashSize == (NbPreHashDigestLength * 8)); + byte[] pbBuf = new byte[4]; + MemUtil.UInt32ToBytesEx(uParallel, pbBuf, 0); + h.TransformBlock(pbBuf, 0, pbBuf.Length, pbBuf, 0); + MemUtil.UInt32ToBytesEx((uint)cbOut, pbBuf, 0); + h.TransformBlock(pbBuf, 0, pbBuf.Length, pbBuf, 0); + MemUtil.UInt32ToBytesEx((uint)ctx.MCost, pbBuf, 0); + h.TransformBlock(pbBuf, 0, pbBuf.Length, pbBuf, 0); + MemUtil.UInt32ToBytesEx((uint)uIt, pbBuf, 0); + h.TransformBlock(pbBuf, 0, pbBuf.Length, pbBuf, 0); + MemUtil.UInt32ToBytesEx(uVersion, pbBuf, 0); + h.TransformBlock(pbBuf, 0, pbBuf.Length, pbBuf, 0); + MemUtil.UInt32ToBytesEx(0, pbBuf, 0); // Argon2d type = 0 + h.TransformBlock(pbBuf, 0, pbBuf.Length, pbBuf, 0); + MemUtil.UInt32ToBytesEx((uint)pbMsg.Length, pbBuf, 0); + h.TransformBlock(pbBuf, 0, pbBuf.Length, pbBuf, 0); + h.TransformBlock(pbMsg, 0, pbMsg.Length, pbMsg, 0); + MemUtil.UInt32ToBytesEx((uint)pbSalt.Length, pbBuf, 0); + h.TransformBlock(pbBuf, 0, pbBuf.Length, pbBuf, 0); + h.TransformBlock(pbSalt, 0, pbSalt.Length, pbSalt, 0); + MemUtil.UInt32ToBytesEx((uint)pbSecretKey.Length, pbBuf, 0); + h.TransformBlock(pbBuf, 0, pbBuf.Length, pbBuf, 0); + h.TransformBlock(pbSecretKey, 0, pbSecretKey.Length, pbSecretKey, 0); + MemUtil.UInt32ToBytesEx((uint)pbAssocData.Length, pbBuf, 0); + h.TransformBlock(pbBuf, 0, pbBuf.Length, pbBuf, 0); + h.TransformBlock(pbAssocData, 0, pbAssocData.Length, pbAssocData, 0); + h.TransformFinalBlock(MemUtil.EmptyByteArray, 0, 0); + byte[] pbH0 = h.Hash; + Debug.Assert(pbH0.Length == 64); + + byte[] pbBlockHash = new byte[NbPreHashSeedLength]; + Array.Copy(pbH0, pbBlockHash, pbH0.Length); + MemUtil.ZeroByteArray(pbH0); + + FillFirstBlocks(ctx, pbBlockHash, h); + MemUtil.ZeroByteArray(pbBlockHash); + + FillMemoryBlocks(ctx); + + byte[] pbOut = FinalHash(ctx, cbOut, h); + + h.Clear(); + MemUtil.ZeroArray(ctx.Mem); + return pbOut; + } + + private static void LoadBlock(ulong[] pqDst, ulong uDstOffset, byte[] pbIn) + { + // for(ulong i = 0; i < NbBlockSizeInQW; ++i) + // pqDst[uDstOffset + i] = MemUtil.BytesToUInt64(pbIn, (int)(i << 3)); + + Debug.Assert((uDstOffset + NbBlockSizeInQW - 1UL) <= (ulong)int.MaxValue); + int iDstOffset = (int)uDstOffset; + for(int i = 0; i < (int)NbBlockSizeInQW; ++i) + pqDst[iDstOffset + i] = MemUtil.BytesToUInt64(pbIn, i << 3); + } + + private static void StoreBlock(byte[] pbDst, ulong[] pqSrc) + { + for(int i = 0; i < (int)NbBlockSizeInQW; ++i) + MemUtil.UInt64ToBytesEx(pqSrc[i], pbDst, i << 3); + } + + private static void CopyBlock(ulong[] vDst, ulong uDstOffset, ulong[] vSrc, + ulong uSrcOffset) + { + // for(ulong i = 0; i < NbBlockSizeInQW; ++i) + // vDst[uDstOffset + i] = vSrc[uSrcOffset + i]; + + // Debug.Assert((uDstOffset + NbBlockSizeInQW - 1UL) <= (ulong)int.MaxValue); + // Debug.Assert((uSrcOffset + NbBlockSizeInQW - 1UL) <= (ulong)int.MaxValue); + // int iDstOffset = (int)uDstOffset; + // int iSrcOffset = (int)uSrcOffset; + // for(int i = 0; i < (int)NbBlockSizeInQW; ++i) + // vDst[iDstOffset + i] = vSrc[iSrcOffset + i]; + + Array.Copy(vSrc, (long)uSrcOffset, vDst, (long)uDstOffset, + (long)NbBlockSizeInQW); + } + + private static void XorBlock(ulong[] vDst, ulong uDstOffset, ulong[] vSrc, + ulong uSrcOffset) + { + // for(ulong i = 0; i < NbBlockSizeInQW; ++i) + // vDst[uDstOffset + i] ^= vSrc[uSrcOffset + i]; + + Debug.Assert((uDstOffset + NbBlockSizeInQW - 1UL) <= (ulong)int.MaxValue); + Debug.Assert((uSrcOffset + NbBlockSizeInQW - 1UL) <= (ulong)int.MaxValue); + int iDstOffset = (int)uDstOffset; + int iSrcOffset = (int)uSrcOffset; + for(int i = 0; i < (int)NbBlockSizeInQW; ++i) + vDst[iDstOffset + i] ^= vSrc[iSrcOffset + i]; + } + + private static void Blake2bLong(byte[] pbOut, int cbOut, + byte[] pbIn, int cbIn, Blake2b h) + { + Debug.Assert((h != null) && (h.HashSize == (64 * 8))); + + byte[] pbOutLen = new byte[4]; + MemUtil.UInt32ToBytesEx((uint)cbOut, pbOutLen, 0); + + if(cbOut <= 64) + { + Blake2b hOut = ((cbOut == 64) ? h : new Blake2b(cbOut)); + if(cbOut == 64) hOut.Initialize(); + + hOut.TransformBlock(pbOutLen, 0, pbOutLen.Length, pbOutLen, 0); + hOut.TransformBlock(pbIn, 0, cbIn, pbIn, 0); + hOut.TransformFinalBlock(MemUtil.EmptyByteArray, 0, 0); + + Array.Copy(hOut.Hash, pbOut, cbOut); + + if(cbOut < 64) hOut.Clear(); + return; + } + + h.Initialize(); + h.TransformBlock(pbOutLen, 0, pbOutLen.Length, pbOutLen, 0); + h.TransformBlock(pbIn, 0, cbIn, pbIn, 0); + h.TransformFinalBlock(MemUtil.EmptyByteArray, 0, 0); + + byte[] pbOutBuffer = new byte[64]; + Array.Copy(h.Hash, pbOutBuffer, pbOutBuffer.Length); + + int ibOut = 64 / 2; + Array.Copy(pbOutBuffer, pbOut, ibOut); + int cbToProduce = cbOut - ibOut; + + h.Initialize(); + while(cbToProduce > 64) + { + byte[] pbHash = h.ComputeHash(pbOutBuffer); + Array.Copy(pbHash, pbOutBuffer, 64); + + Array.Copy(pbHash, 0, pbOut, ibOut, 64 / 2); + ibOut += 64 / 2; + cbToProduce -= 64 / 2; + + MemUtil.ZeroByteArray(pbHash); + } + + using(Blake2b hOut = new Blake2b(cbToProduce)) + { + byte[] pbHash = hOut.ComputeHash(pbOutBuffer); + Array.Copy(pbHash, 0, pbOut, ibOut, cbToProduce); + + MemUtil.ZeroByteArray(pbHash); + } + + MemUtil.ZeroByteArray(pbOutBuffer); + } + +#if !ARGON2_G_INLINED + private static ulong BlaMka(ulong x, ulong y) + { + ulong xy = (x & 0xFFFFFFFFUL) * (y & 0xFFFFFFFFUL); + return (x + y + (xy << 1)); + } + + private static void G(ulong[] v, int a, int b, int c, int d) + { + ulong va = v[a], vb = v[b], vc = v[c], vd = v[d]; + + va = BlaMka(va, vb); + vd = MemUtil.RotateRight64(vd ^ va, 32); + vc = BlaMka(vc, vd); + vb = MemUtil.RotateRight64(vb ^ vc, 24); + va = BlaMka(va, vb); + vd = MemUtil.RotateRight64(vd ^ va, 16); + vc = BlaMka(vc, vd); + vb = MemUtil.RotateRight64(vb ^ vc, 63); + + v[a] = va; + v[b] = vb; + v[c] = vc; + v[d] = vd; + } +#else + private static void G(ulong[] v, int a, int b, int c, int d) + { + ulong va = v[a], vb = v[b], vc = v[c], vd = v[d]; + + ulong xy = (va & 0xFFFFFFFFUL) * (vb & 0xFFFFFFFFUL); + va += vb + (xy << 1); + + vd = MemUtil.RotateRight64(vd ^ va, 32); + + xy = (vc & 0xFFFFFFFFUL) * (vd & 0xFFFFFFFFUL); + vc += vd + (xy << 1); + + vb = MemUtil.RotateRight64(vb ^ vc, 24); + + xy = (va & 0xFFFFFFFFUL) * (vb & 0xFFFFFFFFUL); + va += vb + (xy << 1); + + vd = MemUtil.RotateRight64(vd ^ va, 16); + + xy = (vc & 0xFFFFFFFFUL) * (vd & 0xFFFFFFFFUL); + vc += vd + (xy << 1); + + vb = MemUtil.RotateRight64(vb ^ vc, 63); + + v[a] = va; + v[b] = vb; + v[c] = vc; + v[d] = vd; + } +#endif + +#if ARGON2_B2ROUND_ARRAYS + private static void Blake2RoundNoMsg(ulong[] pbR, int[] v) + { + G(pbR, v[0], v[4], v[8], v[12]); + G(pbR, v[1], v[5], v[9], v[13]); + G(pbR, v[2], v[6], v[10], v[14]); + G(pbR, v[3], v[7], v[11], v[15]); + G(pbR, v[0], v[5], v[10], v[15]); + G(pbR, v[1], v[6], v[11], v[12]); + G(pbR, v[2], v[7], v[8], v[13]); + G(pbR, v[3], v[4], v[9], v[14]); + } +#else + private static void Blake2RoundNoMsgCols16i(ulong[] pbR, int i) + { + G(pbR, i, i + 4, i + 8, i + 12); + G(pbR, i + 1, i + 5, i + 9, i + 13); + G(pbR, i + 2, i + 6, i + 10, i + 14); + G(pbR, i + 3, i + 7, i + 11, i + 15); + G(pbR, i, i + 5, i + 10, i + 15); + G(pbR, i + 1, i + 6, i + 11, i + 12); + G(pbR, i + 2, i + 7, i + 8, i + 13); + G(pbR, i + 3, i + 4, i + 9, i + 14); + } + + private static void Blake2RoundNoMsgRows2i(ulong[] pbR, int i) + { + G(pbR, i, i + 32, i + 64, i + 96); + G(pbR, i + 1, i + 33, i + 65, i + 97); + G(pbR, i + 16, i + 48, i + 80, i + 112); + G(pbR, i + 17, i + 49, i + 81, i + 113); + G(pbR, i, i + 33, i + 80, i + 113); + G(pbR, i + 1, i + 48, i + 81, i + 96); + G(pbR, i + 16, i + 49, i + 64, i + 97); + G(pbR, i + 17, i + 32, i + 65, i + 112); + } +#endif + + private static void FillFirstBlocks(Argon2Ctx ctx, byte[] pbBlockHash, + Blake2b h) + { + byte[] pbBlock = new byte[NbBlockSize]; + + for(ulong l = 0; l < ctx.Lanes; ++l) + { + MemUtil.UInt32ToBytesEx(0, pbBlockHash, NbPreHashDigestLength); + MemUtil.UInt32ToBytesEx((uint)l, pbBlockHash, NbPreHashDigestLength + 4); + + Blake2bLong(pbBlock, (int)NbBlockSize, pbBlockHash, + NbPreHashSeedLength, h); + LoadBlock(ctx.Mem, l * ctx.LaneLength * NbBlockSizeInQW, pbBlock); + + MemUtil.UInt32ToBytesEx(1, pbBlockHash, NbPreHashDigestLength); + + Blake2bLong(pbBlock, (int)NbBlockSize, pbBlockHash, + NbPreHashSeedLength, h); + LoadBlock(ctx.Mem, (l * ctx.LaneLength + 1UL) * NbBlockSizeInQW, pbBlock); + } + + MemUtil.ZeroByteArray(pbBlock); + } + + private static ulong IndexAlpha(Argon2Ctx ctx, Argon2ThreadInfo ti, + uint uPseudoRand, bool bSameLane) + { + ulong uRefAreaSize; + if(ti.Pass == 0) + { + if(ti.Slice == 0) + { + Debug.Assert(ti.Index > 0); + uRefAreaSize = ti.Index - 1UL; + } + else + { + if(bSameLane) + uRefAreaSize = ti.Slice * ctx.SegmentLength + + ti.Index - 1UL; + else + uRefAreaSize = ti.Slice * ctx.SegmentLength - + ((ti.Index == 0UL) ? 1UL : 0UL); + } + } + else + { + if(bSameLane) + uRefAreaSize = ctx.LaneLength - ctx.SegmentLength + + ti.Index - 1UL; + else + uRefAreaSize = ctx.LaneLength - ctx.SegmentLength - + ((ti.Index == 0) ? 1UL : 0UL); + } + Debug.Assert(uRefAreaSize <= (ulong)uint.MaxValue); + + ulong uRelPos = uPseudoRand; + uRelPos = (uRelPos * uRelPos) >> 32; + uRelPos = uRefAreaSize - 1UL - ((uRefAreaSize * uRelPos) >> 32); + + ulong uStart = 0; + if(ti.Pass != 0) + uStart = (((ti.Slice + 1UL) == NbSyncPoints) ? 0UL : + ((ti.Slice + 1UL) * ctx.SegmentLength)); + Debug.Assert(uStart <= (ulong)uint.MaxValue); + + Debug.Assert(ctx.LaneLength <= (ulong)uint.MaxValue); + return ((uStart + uRelPos) % ctx.LaneLength); + } + + private static void FillMemoryBlocks(Argon2Ctx ctx) + { + int np = (int)ctx.Lanes; + Argon2ThreadInfo[] v = new Argon2ThreadInfo[np]; + + for(ulong r = 0; r < ctx.TCost; ++r) + { + for(ulong s = 0; s < NbSyncPoints; ++s) + { + for(int l = 0; l < np; ++l) + { + Argon2ThreadInfo ti = new Argon2ThreadInfo(); + ti.Context = ctx; + + ti.Pass = r; + ti.Lane = (ulong)l; + ti.Slice = s; + + if(!ThreadPool.QueueUserWorkItem(FillSegmentThr, ti)) + { + Debug.Assert(false); + throw new OutOfMemoryException(); + } + + v[l] = ti; + } + + for(int l = 0; l < np; ++l) + v[l].Finished.WaitOne(); + } + } + } + + private static void FillSegmentThr(object o) + { + Argon2ThreadInfo ti = (o as Argon2ThreadInfo); + if(ti == null) { Debug.Assert(false); return; } + + try + { + Argon2Ctx ctx = ti.Context; + if(ctx == null) { Debug.Assert(false); return; } + + Debug.Assert(ctx.Version >= MinVersion); + bool bCanXor = (ctx.Version >= 0x13U); + + ulong uStart = 0; + if((ti.Pass == 0) && (ti.Slice == 0)) uStart = 2; + + ulong uCur = (ti.Lane * ctx.LaneLength) + (ti.Slice * + ctx.SegmentLength) + uStart; + + ulong uPrev = (((uCur % ctx.LaneLength) == 0) ? + (uCur + ctx.LaneLength - 1UL) : (uCur - 1UL)); + + ulong[] pbR = new ulong[NbBlockSizeInQW]; + ulong[] pbTmp = new ulong[NbBlockSizeInQW]; + + for(ulong i = uStart; i < ctx.SegmentLength; ++i) + { + if((uCur % ctx.LaneLength) == 1) + uPrev = uCur - 1UL; + + ulong uPseudoRand = ctx.Mem[uPrev * NbBlockSizeInQW]; + ulong uRefLane = (uPseudoRand >> 32) % ctx.Lanes; + if((ti.Pass == 0) && (ti.Slice == 0)) + uRefLane = ti.Lane; + + ti.Index = i; + ulong uRefIndex = IndexAlpha(ctx, ti, (uint)uPseudoRand, + (uRefLane == ti.Lane)); + + ulong uRefBlockIndex = (ctx.LaneLength * uRefLane + + uRefIndex) * NbBlockSizeInQW; + ulong uCurBlockIndex = uCur * NbBlockSizeInQW; + + FillBlock(ctx.Mem, uPrev * NbBlockSizeInQW, uRefBlockIndex, + uCurBlockIndex, ((ti.Pass != 0) && bCanXor), pbR, pbTmp); + + ++uCur; + ++uPrev; + } + + MemUtil.ZeroArray(pbR); + MemUtil.ZeroArray(pbTmp); + } + catch(Exception) { Debug.Assert(false); } + + try { ti.Finished.Set(); } + catch(Exception) { Debug.Assert(false); } + } + +#if ARGON2_B2ROUND_ARRAYS + private static void InitB2RoundIndexArrays() + { + int[][] vCols = g_vFBCols; + if(vCols == null) + { + vCols = new int[8][]; + Debug.Assert(vCols.Length == 8); + int e = 0; + for(int i = 0; i < 8; ++i) + { + vCols[i] = new int[16]; + for(int j = 0; j < 16; ++j) + { + vCols[i][j] = e; + ++e; + } + } + + g_vFBCols = vCols; + } + + int[][] vRows = g_vFBRows; + if(vRows == null) + { + vRows = new int[8][]; + for(int i = 0; i < 8; ++i) + { + vRows[i] = new int[16]; + for(int j = 0; j < 16; ++j) + { + int jh = j / 2; + vRows[i][j] = (2 * i) + (16 * jh) + (j & 1); + } + } + + g_vFBRows = vRows; + } + } +#endif + + private static void FillBlock(ulong[] pMem, ulong uPrev, ulong uRef, + ulong uNext, bool bXor, ulong[] pbR, ulong[] pbTmp) + { + CopyBlock(pbR, 0, pMem, uRef); + XorBlock(pbR, 0, pMem, uPrev); + CopyBlock(pbTmp, 0, pbR, 0); + if(bXor) XorBlock(pbTmp, 0, pMem, uNext); + +#if ARGON2_B2ROUND_ARRAYS + int[][] vCols = g_vFBCols; + int[][] vRows = g_vFBRows; + for(int i = 0; i < 8; ++i) + Blake2RoundNoMsg(pbR, vCols[i]); + for(int i = 0; i < 8; ++i) + Blake2RoundNoMsg(pbR, vRows[i]); +#else + for(int i = 0; i < (8 * 16); i += 16) + Blake2RoundNoMsgCols16i(pbR, i); + for(int i = 0; i < (8 * 2); i += 2) + Blake2RoundNoMsgRows2i(pbR, i); +#endif + + CopyBlock(pMem, uNext, pbTmp, 0); + XorBlock(pMem, uNext, pbR, 0); + } + + private static byte[] FinalHash(Argon2Ctx ctx, int cbOut, Blake2b h) + { + ulong[] pqBlockHash = new ulong[NbBlockSizeInQW]; + CopyBlock(pqBlockHash, 0, ctx.Mem, (ctx.LaneLength - 1UL) * + NbBlockSizeInQW); + for(ulong l = 1; l < ctx.Lanes; ++l) + XorBlock(pqBlockHash, 0, ctx.Mem, (l * ctx.LaneLength + + ctx.LaneLength - 1UL) * NbBlockSizeInQW); + + byte[] pbBlockHashBytes = new byte[NbBlockSize]; + StoreBlock(pbBlockHashBytes, pqBlockHash); + + byte[] pbOut = new byte[cbOut]; + Blake2bLong(pbOut, cbOut, pbBlockHashBytes, (int)NbBlockSize, h); + + MemUtil.ZeroArray(pqBlockHash); + MemUtil.ZeroByteArray(pbBlockHashBytes); + return pbOut; + } + } +} diff --git a/src/KeePassLib2Android/Cryptography/KeyDerivation/Argon2Kdf.cs b/src/KeePassLib2Android/Cryptography/KeyDerivation/Argon2Kdf.cs new file mode 100644 index 00000000..69416ad2 --- /dev/null +++ b/src/KeePassLib2Android/Cryptography/KeyDerivation/Argon2Kdf.cs @@ -0,0 +1,144 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2016 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.Collections.Generic; +using System.Diagnostics; +using System.Text; + +namespace KeePassLib.Cryptography.KeyDerivation +{ + public sealed partial class Argon2Kdf : KdfEngine + { + private static readonly PwUuid g_uuid = new PwUuid(new byte[] { + 0xEF, 0x63, 0x6D, 0xDF, 0x8C, 0x29, 0x44, 0x4B, + 0x91, 0xF7, 0xA9, 0xA4, 0x03, 0xE3, 0x0A, 0x0C }); + + public const string ParamSalt = "S"; // Byte[] + public const string ParamParallelism = "P"; // UInt32 + public const string ParamMemory = "M"; // UInt64 + public const string ParamIterations = "I"; // UInt64 + public const string ParamVersion = "V"; // UInt32 + public const string ParamSecretKey = "K"; // Byte[] + public const string ParamAssocData = "A"; // Byte[] + + private const uint MinVersion = 0x10; + private const uint MaxVersion = 0x13; + + private const int MinSalt = 8; + private const int MaxSalt = int.MaxValue; // .NET limit; 2^32 - 1 in spec + + internal const ulong MinIterations = 1; + internal const ulong MaxIterations = uint.MaxValue; + + internal const ulong MinMemory = 1024 * 8; // For parallelism = 1 + // internal const ulong MaxMemory = (ulong)uint.MaxValue * 1024UL; // Spec + internal const ulong MaxMemory = int.MaxValue; // .NET limit + + internal const uint MinParallelism = 1; + internal const uint MaxParallelism = (1 << 24) - 1; + + internal const ulong DefaultIterations = 2; + internal const ulong DefaultMemory = 1024 * 1024; // 1 MB + internal const uint DefaultParallelism = 2; + + public override PwUuid Uuid + { + get { return g_uuid; } + } + + public override string Name + { + get { return "Argon2"; } + } + + public Argon2Kdf() + { + } + + public override KdfParameters GetDefaultParameters() + { + KdfParameters p = base.GetDefaultParameters(); + + p.SetUInt32(ParamVersion, MaxVersion); + + p.SetUInt64(ParamIterations, DefaultIterations); + p.SetUInt64(ParamMemory, DefaultMemory); + p.SetUInt32(ParamParallelism, DefaultParallelism); + + return p; + } + + public override void Randomize(KdfParameters p) + { + if(p == null) { Debug.Assert(false); return; } + Debug.Assert(g_uuid.Equals(p.KdfUuid)); + + byte[] pb = CryptoRandom.Instance.GetRandomBytes(32); + p.SetByteArray(ParamSalt, pb); + } + + public override byte[] Transform(byte[] pbMsg, KdfParameters p) + { + if(pbMsg == null) throw new ArgumentNullException("pbMsg"); + if(p == null) throw new ArgumentNullException("p"); + + byte[] pbSalt = p.GetByteArray(ParamSalt); + if(pbSalt == null) + throw new ArgumentNullException("p.Salt"); + if((pbSalt.Length < MinSalt) || (pbSalt.Length > MaxSalt)) + throw new ArgumentOutOfRangeException("p.Salt"); + + uint uPar = p.GetUInt32(ParamParallelism, 0); + if((uPar < MinParallelism) || (uPar > MaxParallelism)) + throw new ArgumentOutOfRangeException("p.Parallelism"); + + ulong uMem = p.GetUInt64(ParamMemory, 0); + if((uMem < MinMemory) || (uMem > MaxMemory)) + throw new ArgumentOutOfRangeException("p.Memory"); + + ulong uIt = p.GetUInt64(ParamIterations, 0); + if((uIt < MinIterations) || (uIt > MaxIterations)) + throw new ArgumentOutOfRangeException("p.Iterations"); + + uint v = p.GetUInt32(ParamVersion, 0); + if((v < MinVersion) || (v > MaxVersion)) + throw new ArgumentOutOfRangeException("p.Version"); + + byte[] pbSecretKey = p.GetByteArray(ParamSecretKey); + byte[] pbAssocData = p.GetByteArray(ParamAssocData); + + byte[] pbRet = Argon2d(pbMsg, pbSalt, uPar, uMem, uIt, + 32, v, pbSecretKey, pbAssocData); + + if(uMem > (100UL * 1024UL * 1024UL)) GC.Collect(); + return pbRet; + } + + public override KdfParameters GetBestParameters(uint uMilliseconds) + { + KdfParameters p = GetDefaultParameters(); + Randomize(p); + + MaximizeParamUInt64(p, ParamIterations, MinIterations, + MaxIterations, uMilliseconds, true); + return p; + } + } +} diff --git a/src/KeePassLib2Android/Cryptography/KeyDerivation/KdfEngine.cs b/src/KeePassLib2Android/Cryptography/KeyDerivation/KdfEngine.cs new file mode 100644 index 00000000..74d0dd25 --- /dev/null +++ b/src/KeePassLib2Android/Cryptography/KeyDerivation/KdfEngine.cs @@ -0,0 +1,142 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2016 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.Collections.Generic; +using System.Diagnostics; +using System.Text; + +namespace KeePassLib.Cryptography.KeyDerivation +{ + public abstract class KdfEngine + { + public abstract PwUuid Uuid + { + get; + } + + public abstract string Name + { + get; + } + + public virtual KdfParameters GetDefaultParameters() + { + return new KdfParameters(this.Uuid); + } + + /// + /// Generate random seeds and store them in . + /// + public virtual void Randomize(KdfParameters p) + { + Debug.Assert(p != null); + Debug.Assert(p.KdfUuid.Equals(this.Uuid)); + } + + public abstract byte[] Transform(byte[] pbMsg, KdfParameters p); + + public virtual KdfParameters GetBestParameters(uint uMilliseconds) + { + throw new NotImplementedException(); + } + + protected void MaximizeParamUInt64(KdfParameters p, string strName, + ulong uMin, ulong uMax, uint uMilliseconds, bool bInterpSearch) + { + if(p == null) { Debug.Assert(false); return; } + if(string.IsNullOrEmpty(strName)) { Debug.Assert(false); return; } + if(uMin > uMax) { Debug.Assert(false); return; } + + if(uMax > (ulong.MaxValue >> 1)) + { + Debug.Assert(false); + uMax = ulong.MaxValue >> 1; + + if(uMin > uMax) { p.SetUInt64(strName, uMin); return; } + } + + byte[] pbMsg = new byte[32]; + for(int i = 0; i < pbMsg.Length; ++i) pbMsg[i] = (byte)i; + + ulong uLow = uMin; + ulong uHigh = uMin + 1UL; + long tLow = 0; + long tHigh = 0; + long tTarget = (long)uMilliseconds; + + // Determine range + while(uHigh <= uMax) + { + p.SetUInt64(strName, uHigh); + + // GC.Collect(); + Stopwatch sw = Stopwatch.StartNew(); + Transform(pbMsg, p); + sw.Stop(); + + tHigh = sw.ElapsedMilliseconds; + if(tHigh > tTarget) break; + + uLow = uHigh; + tLow = tHigh; + uHigh <<= 1; + } + if(uHigh > uMax) { uHigh = uMax; tHigh = 0; } + if(uLow > uHigh) uLow = uHigh; // Skips to end + + // Find optimal number of iterations + while((uHigh - uLow) >= 2UL) + { + ulong u = (uHigh + uLow) >> 1; // Binary search + // Interpolation search, if possible + if(bInterpSearch && (tLow > 0) && (tHigh > tTarget) && + (tLow <= tTarget)) + { + u = uLow + (((uHigh - uLow) * (ulong)(tTarget - tLow)) / + (ulong)(tHigh - tLow)); + if((u >= uLow) && (u <= uHigh)) + { + u = Math.Max(u, uLow + 1UL); + u = Math.Min(u, uHigh - 1UL); + } + else + { + Debug.Assert(false); + u = (uHigh + uLow) >> 1; + } + } + + p.SetUInt64(strName, u); + + // GC.Collect(); + Stopwatch sw = Stopwatch.StartNew(); + Transform(pbMsg, p); + sw.Stop(); + + long t = sw.ElapsedMilliseconds; + if(t == tTarget) { uLow = u; break; } + else if(t > tTarget) { uHigh = u; tHigh = t; } + else { uLow = u; tLow = t; } + } + + p.SetUInt64(strName, uLow); + } + } +} diff --git a/src/KeePassLib2Android/Cryptography/KeyDerivation/KdfParameters.cs b/src/KeePassLib2Android/Cryptography/KeyDerivation/KdfParameters.cs new file mode 100644 index 00000000..3723b78f --- /dev/null +++ b/src/KeePassLib2Android/Cryptography/KeyDerivation/KdfParameters.cs @@ -0,0 +1,80 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2016 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.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Text; + +using KeePassLib.Collections; +using KeePassLib.Utility; + +namespace KeePassLib.Cryptography.KeyDerivation +{ + public sealed class KdfParameters : VariantDictionary + { + private const string ParamUuid = @"$UUID"; + + private readonly PwUuid m_puKdf; + public PwUuid KdfUuid + { + get { return m_puKdf; } + } + + public KdfParameters(PwUuid puKdf) + { + if(puKdf == null) throw new ArgumentNullException("puKdf"); + + m_puKdf = puKdf; + SetByteArray(ParamUuid, puKdf.UuidBytes); + } + + /// + /// Unsupported. + /// + public override object Clone() + { + throw new NotSupportedException(); + } + + public static byte[] SerializeExt(KdfParameters p) + { + return VariantDictionary.Serialize(p); + } + + public static KdfParameters DeserializeExt(byte[] pb) + { + VariantDictionary d = VariantDictionary.Deserialize(pb); + if(d == null) { Debug.Assert(false); return null; } + + byte[] pbUuid = d.GetByteArray(ParamUuid); + if((pbUuid == null) || (pbUuid.Length != (int)PwUuid.UuidSize)) + { + Debug.Assert(false); + return null; + } + + PwUuid pu = new PwUuid(pbUuid); + KdfParameters p = new KdfParameters(pu); + d.CopyTo(p); + return p; + } + } +} diff --git a/src/KeePassLib2Android/Cryptography/KeyDerivation/KdfPool.cs b/src/KeePassLib2Android/Cryptography/KeyDerivation/KdfPool.cs new file mode 100644 index 00000000..08979e23 --- /dev/null +++ b/src/KeePassLib2Android/Cryptography/KeyDerivation/KdfPool.cs @@ -0,0 +1,96 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2016 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.Collections.Generic; +using System.Diagnostics; +using System.Text; + +using KeePassLib.Utility; + +namespace KeePassLib.Cryptography.KeyDerivation +{ + public static class KdfPool + { + private static List g_l = new List(); + + public static IEnumerable Engines + { + get + { + EnsureInitialized(); + return g_l; + } + } + + private static void EnsureInitialized() + { + if(g_l.Count > 0) return; + + g_l.Add(new AesKdf()); + g_l.Add(new Argon2Kdf()); + } + + internal static KdfParameters GetDefaultParameters() + { + EnsureInitialized(); + return g_l[0].GetDefaultParameters(); + } + + public static KdfEngine Get(PwUuid pu) + { + if(pu == null) { Debug.Assert(false); return null; } + + EnsureInitialized(); + + foreach(KdfEngine kdf in g_l) + { + if(pu.Equals(kdf.Uuid)) return kdf; + } + + return null; + } + + public static KdfEngine Get(string strName) + { + if(string.IsNullOrEmpty(strName)) { Debug.Assert(false); return null; } + + EnsureInitialized(); + + foreach(KdfEngine kdf in g_l) + { + if(strName.Equals(kdf.Name, StrUtil.CaseIgnoreCmp)) return kdf; + } + + return null; + } + + public static void Add(KdfEngine kdf) + { + if(kdf == null) { Debug.Assert(false); return; } + + EnsureInitialized(); + + if(Get(kdf.Uuid) != null) { Debug.Assert(false); return; } + if(Get(kdf.Name) != null) { Debug.Assert(false); return; } + + g_l.Add(kdf); + } + } +} diff --git a/src/KeePassLib2Android/Cryptography/PasswordGenerator/PwGenerator.cs b/src/KeePassLib2Android/Cryptography/PasswordGenerator/PwGenerator.cs index 285bfee9..b810d92e 100644 --- a/src/KeePassLib2Android/Cryptography/PasswordGenerator/PwGenerator.cs +++ b/src/KeePassLib2Android/Cryptography/PasswordGenerator/PwGenerator.cs @@ -19,10 +19,12 @@ using System; using System.Collections.Generic; -using System.Text; using System.Diagnostics; +using System.Security.Cryptography; +using System.Text; using KeePassLib.Security; +using KeePassLib.Utility; namespace KeePassLib.Cryptography.PasswordGenerator { @@ -62,16 +64,20 @@ namespace KeePassLib.Cryptography.PasswordGenerator private static CryptoRandomStream CreateCryptoStream(byte[] pbAdditionalEntropy) { - byte[] pbKey = CryptoRandom.Instance.GetRandomBytes(256); + byte[] pbKey = CryptoRandom.Instance.GetRandomBytes(128); // Mix in additional entropy + Debug.Assert(pbKey.Length >= 64); if((pbAdditionalEntropy != null) && (pbAdditionalEntropy.Length > 0)) { - for(int nKeyPos = 0; nKeyPos < pbKey.Length; ++nKeyPos) - pbKey[nKeyPos] ^= pbAdditionalEntropy[nKeyPos % pbAdditionalEntropy.Length]; + using(SHA512Managed h = new SHA512Managed()) + { + byte[] pbHash = h.ComputeHash(pbAdditionalEntropy); + MemUtil.XorArray(pbHash, 0, pbKey, 0, pbHash.Length); + } } - return new CryptoRandomStream(CrsAlgorithm.Salsa20, pbKey); + return new CryptoRandomStream(CrsAlgorithm.ChaCha20, pbKey); } internal static char GenerateCharacter(PwProfile pwProfile, diff --git a/src/KeePassLib2Android/Cryptography/SelfTest.cs b/src/KeePassLib2Android/Cryptography/SelfTest.cs index 069d3c34..4e09636f 100644 --- a/src/KeePassLib2Android/Cryptography/SelfTest.cs +++ b/src/KeePassLib2Android/Cryptography/SelfTest.cs @@ -21,6 +21,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; +using System.IO; using System.Security; using System.Text; @@ -33,6 +34,8 @@ using System.Security.Cryptography; #endif using KeePassLib.Cryptography.Cipher; +using KeePassLib.Cryptography.Hash; +using KeePassLib.Cryptography.KeyDerivation; using KeePassLib.Keys; using KeePassLib.Native; using KeePassLib.Resources; @@ -45,17 +48,6 @@ using KeePassLib.Utility; namespace KeePassLib.Cryptography { - /* /// - /// Return values of the SelfTest.Perform method. - /// - public enum SelfTestResult - { - Success = 0, - RijndaelEcbError = 1, - Salsa20Error = 2, - NativeKeyTransformationError = 3 - } */ - /// /// Class containing self-test methods. /// @@ -70,7 +62,11 @@ namespace KeePassLib.Cryptography TestRijndael(); TestSalsa20(); - + TestChaCha20(); + TestBlake2b(); + TestArgon2(); + TestHmac(); + TestNativeKeyTransform(); TestHmacOtp(); @@ -92,14 +88,14 @@ namespace KeePassLib.Cryptography internal static void TestFipsComplianceProblems() { #if !KeePassUAP - try { new RijndaelManaged(); } + try { using(RijndaelManaged r = new RijndaelManaged()) { } } catch(Exception exAes) { throw new SecurityException("AES/Rijndael: " + exAes.Message); } #endif - try { new SHA256Managed(); } + try { using(SHA256Managed h = new SHA256Managed()) { } } catch(Exception exSha256) { throw new SecurityException("SHA-256: " + exSha256.Message); @@ -126,7 +122,7 @@ namespace KeePassLib.Cryptography AesEngine r = new AesEngine(); r.Init(true, new KeyParameter(pbTestKey)); if(r.GetBlockSize() != pbTestData.Length) - throw new SecurityException(KLRes.EncAlgorithmAes + " (BS)."); + throw new SecurityException("AES (BC)"); r.ProcessBlock(pbTestData, 0, pbTestData, 0); #else RijndaelManaged r = new RijndaelManaged(); @@ -147,13 +143,14 @@ namespace KeePassLib.Cryptography #endif if(!MemUtil.ArraysEqual(pbTestData, pbReferenceCT)) - throw new SecurityException(KLRes.EncAlgorithmAes + "."); + throw new SecurityException("AES"); } private static void TestSalsa20() { +#if DEBUG // Test values from official set 6, vector 3 - byte[] pbKey= new byte[32] { + byte[] pbKey = new byte[32] { 0x0F, 0x62, 0xB5, 0x08, 0x5B, 0xAE, 0x01, 0x54, 0xA7, 0xFA, 0x4D, 0xA0, 0xF3, 0x46, 0x99, 0xEC, 0x3F, 0x92, 0xE5, 0x38, 0x8B, 0xDE, 0x31, 0x84, @@ -168,12 +165,11 @@ namespace KeePassLib.Cryptography byte[] pb = new byte[16]; Salsa20Cipher c = new Salsa20Cipher(pbKey, pbIV); - c.Encrypt(pb, pb.Length, false); + c.Encrypt(pb, 0, pb.Length); if(!MemUtil.ArraysEqual(pb, pbExpected)) throw new SecurityException("Salsa20-1"); -#if DEBUG - // Extended test in debug mode + // Extended test byte[] pbExpected2 = new byte[16] { 0xAB, 0xF3, 0x9A, 0x21, 0x0E, 0xEE, 0x89, 0x59, 0x8B, 0x71, 0x33, 0x37, 0x70, 0x56, 0xC2, 0xFE @@ -185,13 +181,14 @@ namespace KeePassLib.Cryptography Random r = new Random(); int nPos = Salsa20ToPos(c, r, pb.Length, 65536); - c.Encrypt(pb, pb.Length, false); + Array.Clear(pb, 0, pb.Length); + c.Encrypt(pb, 0, pb.Length); if(!MemUtil.ArraysEqual(pb, pbExpected2)) throw new SecurityException("Salsa20-2"); nPos = Salsa20ToPos(c, r, nPos + pb.Length, 131008); Array.Clear(pb, 0, pb.Length); - c.Encrypt(pb, pb.Length, true); + c.Encrypt(pb, 0, pb.Length); if(!MemUtil.ArraysEqual(pb, pbExpected3)) throw new SecurityException("Salsa20-3"); @@ -200,8 +197,8 @@ namespace KeePassLib.Cryptography for(int i = 0; i < nRounds; ++i) { byte[] z = new byte[32]; - c = new Salsa20Cipher(z, BitConverter.GetBytes((long)i)); - c.Encrypt(z, z.Length, true); + c = new Salsa20Cipher(z, MemUtil.Int64ToBytes(i)); + c.Encrypt(z, 0, z.Length); d[MemUtil.ByteArrayToHexString(z)] = true; } if(d.Count != nRounds) throw new SecurityException("Salsa20-4"); @@ -218,7 +215,7 @@ namespace KeePassLib.Cryptography { int x = r.Next(1, 513); int nGen = Math.Min(nTargetPos - nPos, x); - c.Encrypt(pb, nGen, r.Next(0, 2) == 0); + c.Encrypt(pb, 0, nGen); nPos += nGen; } @@ -226,6 +223,475 @@ namespace KeePassLib.Cryptography } #endif + private static void TestChaCha20() + { + // ====================================================== + // Test vector from RFC 7539, section 2.3.2 + + byte[] pbKey = new byte[32]; + for(int i = 0; i < 32; ++i) pbKey[i] = (byte)i; + + byte[] pbIV = new byte[12]; + pbIV[3] = 0x09; + pbIV[7] = 0x4A; + + byte[] pbExpc = new byte[64] { + 0x10, 0xF1, 0xE7, 0xE4, 0xD1, 0x3B, 0x59, 0x15, + 0x50, 0x0F, 0xDD, 0x1F, 0xA3, 0x20, 0x71, 0xC4, + 0xC7, 0xD1, 0xF4, 0xC7, 0x33, 0xC0, 0x68, 0x03, + 0x04, 0x22, 0xAA, 0x9A, 0xC3, 0xD4, 0x6C, 0x4E, + 0xD2, 0x82, 0x64, 0x46, 0x07, 0x9F, 0xAA, 0x09, + 0x14, 0xC2, 0xD7, 0x05, 0xD9, 0x8B, 0x02, 0xA2, + 0xB5, 0x12, 0x9C, 0xD1, 0xDE, 0x16, 0x4E, 0xB9, + 0xCB, 0xD0, 0x83, 0xE8, 0xA2, 0x50, 0x3C, 0x4E + }; + + byte[] pb = new byte[64]; + + using(ChaCha20Cipher c = new ChaCha20Cipher(pbKey, pbIV)) + { + c.Seek(64, SeekOrigin.Begin); // Skip first block + c.Encrypt(pb, 0, pb.Length); + + if(!MemUtil.ArraysEqual(pb, pbExpc)) + throw new SecurityException("ChaCha20-1"); + } + +#if DEBUG + // ====================================================== + // Test vector from RFC 7539, section 2.4.2 + + pbIV[3] = 0; + + pb = StrUtil.Utf8.GetBytes("Ladies and Gentlemen of the clas" + + @"s of '99: If I could offer you only one tip for " + + @"the future, sunscreen would be it."); + + pbExpc = new byte[] { + 0x6E, 0x2E, 0x35, 0x9A, 0x25, 0x68, 0xF9, 0x80, + 0x41, 0xBA, 0x07, 0x28, 0xDD, 0x0D, 0x69, 0x81, + 0xE9, 0x7E, 0x7A, 0xEC, 0x1D, 0x43, 0x60, 0xC2, + 0x0A, 0x27, 0xAF, 0xCC, 0xFD, 0x9F, 0xAE, 0x0B, + 0xF9, 0x1B, 0x65, 0xC5, 0x52, 0x47, 0x33, 0xAB, + 0x8F, 0x59, 0x3D, 0xAB, 0xCD, 0x62, 0xB3, 0x57, + 0x16, 0x39, 0xD6, 0x24, 0xE6, 0x51, 0x52, 0xAB, + 0x8F, 0x53, 0x0C, 0x35, 0x9F, 0x08, 0x61, 0xD8, + 0x07, 0xCA, 0x0D, 0xBF, 0x50, 0x0D, 0x6A, 0x61, + 0x56, 0xA3, 0x8E, 0x08, 0x8A, 0x22, 0xB6, 0x5E, + 0x52, 0xBC, 0x51, 0x4D, 0x16, 0xCC, 0xF8, 0x06, + 0x81, 0x8C, 0xE9, 0x1A, 0xB7, 0x79, 0x37, 0x36, + 0x5A, 0xF9, 0x0B, 0xBF, 0x74, 0xA3, 0x5B, 0xE6, + 0xB4, 0x0B, 0x8E, 0xED, 0xF2, 0x78, 0x5E, 0x42, + 0x87, 0x4D + }; + + byte[] pb64 = new byte[64]; + + using(ChaCha20Cipher c = new ChaCha20Cipher(pbKey, pbIV)) + { + c.Encrypt(pb64, 0, pb64.Length); // Skip first block + c.Encrypt(pb, 0, pb.Length); + + if(!MemUtil.ArraysEqual(pb, pbExpc)) + throw new SecurityException("ChaCha20-2"); + } + + // ====================================================== + // Test vector from RFC 7539, appendix A.2 #2 + + Array.Clear(pbKey, 0, pbKey.Length); + pbKey[31] = 1; + + Array.Clear(pbIV, 0, pbIV.Length); + pbIV[11] = 2; + + pb = StrUtil.Utf8.GetBytes("Any submission to the IETF inten" + + "ded by the Contributor for publication as all or" + + " part of an IETF Internet-Draft or RFC and any s" + + "tatement made within the context of an IETF acti" + + "vity is considered an \"IETF Contribution\". Such " + + "statements include oral statements in IETF sessi" + + "ons, as well as written and electronic communica" + + "tions made at any time or place, which are addressed to"); + + pbExpc = MemUtil.HexStringToByteArray( + "A3FBF07DF3FA2FDE4F376CA23E82737041605D9F4F4F57BD8CFF2C1D4B7955EC" + + "2A97948BD3722915C8F3D337F7D370050E9E96D647B7C39F56E031CA5EB6250D" + + "4042E02785ECECFA4B4BB5E8EAD0440E20B6E8DB09D881A7C6132F420E527950" + + "42BDFA7773D8A9051447B3291CE1411C680465552AA6C405B7764D5E87BEA85A" + + "D00F8449ED8F72D0D662AB052691CA66424BC86D2DF80EA41F43ABF937D3259D" + + "C4B2D0DFB48A6C9139DDD7F76966E928E635553BA76C5C879D7B35D49EB2E62B" + + "0871CDAC638939E25E8A1E0EF9D5280FA8CA328B351C3C765989CBCF3DAA8B6C" + + "CC3AAF9F3979C92B3720FC88DC95ED84A1BE059C6499B9FDA236E7E818B04B0B" + + "C39C1E876B193BFE5569753F88128CC08AAA9B63D1A16F80EF2554D7189C411F" + + "5869CA52C5B83FA36FF216B9C1D30062BEBCFD2DC5BCE0911934FDA79A86F6E6" + + "98CED759C3FF9B6477338F3DA4F9CD8514EA9982CCAFB341B2384DD902F3D1AB" + + "7AC61DD29C6F21BA5B862F3730E37CFDC4FD806C22F221"); + + Random r = new Random(); + using(MemoryStream msEnc = new MemoryStream()) + { + using(ChaCha20Stream c = new ChaCha20Stream(msEnc, true, pbKey, pbIV)) + { + r.NextBytes(pb64); + c.Write(pb64, 0, pb64.Length); // Skip first block + + int p = 0; + while(p < pb.Length) + { + int cb = r.Next(1, pb.Length - p + 1); + c.Write(pb, p, cb); + p += cb; + } + Debug.Assert(p == pb.Length); + } + + byte[] pbEnc0 = msEnc.ToArray(); + byte[] pbEnc = MemUtil.Mid(pbEnc0, 64, pbEnc0.Length - 64); + if(!MemUtil.ArraysEqual(pbEnc, pbExpc)) + throw new SecurityException("ChaCha20-3"); + + using(MemoryStream msCT = new MemoryStream(pbEnc0, false)) + { + using(ChaCha20Stream cDec = new ChaCha20Stream(msCT, false, + pbKey, pbIV)) + { + byte[] pbPT = MemUtil.Read(cDec, pbEnc0.Length); + if(cDec.ReadByte() >= 0) + throw new SecurityException("ChaCha20-4"); + if(!MemUtil.ArraysEqual(MemUtil.Mid(pbPT, 0, 64), pb64)) + throw new SecurityException("ChaCha20-5"); + if(!MemUtil.ArraysEqual(MemUtil.Mid(pbPT, 64, pbEnc.Length), pb)) + throw new SecurityException("ChaCha20-6"); + } + } + } + + // ====================================================== + // Test vector TC8 from RFC draft by J. Strombergson: + // https://tools.ietf.org/html/draft-strombergson-chacha-test-vectors-01 + + pbKey = new byte[32] { + 0xC4, 0x6E, 0xC1, 0xB1, 0x8C, 0xE8, 0xA8, 0x78, + 0x72, 0x5A, 0x37, 0xE7, 0x80, 0xDF, 0xB7, 0x35, + 0x1F, 0x68, 0xED, 0x2E, 0x19, 0x4C, 0x79, 0xFB, + 0xC6, 0xAE, 0xBE, 0xE1, 0xA6, 0x67, 0x97, 0x5D + }; + + // The first 4 bytes are set to zero and a large counter + // is used; this makes the RFC 7539 version of ChaCha20 + // compatible with the original specification by + // D. J. Bernstein. + pbIV = new byte[12] { 0x00, 0x00, 0x00, 0x00, + 0x1A, 0xDA, 0x31, 0xD5, 0xCF, 0x68, 0x82, 0x21 + }; + + pb = new byte[128]; + + pbExpc = new byte[128] { + 0xF6, 0x3A, 0x89, 0xB7, 0x5C, 0x22, 0x71, 0xF9, + 0x36, 0x88, 0x16, 0x54, 0x2B, 0xA5, 0x2F, 0x06, + 0xED, 0x49, 0x24, 0x17, 0x92, 0x30, 0x2B, 0x00, + 0xB5, 0xE8, 0xF8, 0x0A, 0xE9, 0xA4, 0x73, 0xAF, + 0xC2, 0x5B, 0x21, 0x8F, 0x51, 0x9A, 0xF0, 0xFD, + 0xD4, 0x06, 0x36, 0x2E, 0x8D, 0x69, 0xDE, 0x7F, + 0x54, 0xC6, 0x04, 0xA6, 0xE0, 0x0F, 0x35, 0x3F, + 0x11, 0x0F, 0x77, 0x1B, 0xDC, 0xA8, 0xAB, 0x92, + + 0xE5, 0xFB, 0xC3, 0x4E, 0x60, 0xA1, 0xD9, 0xA9, + 0xDB, 0x17, 0x34, 0x5B, 0x0A, 0x40, 0x27, 0x36, + 0x85, 0x3B, 0xF9, 0x10, 0xB0, 0x60, 0xBD, 0xF1, + 0xF8, 0x97, 0xB6, 0x29, 0x0F, 0x01, 0xD1, 0x38, + 0xAE, 0x2C, 0x4C, 0x90, 0x22, 0x5B, 0xA9, 0xEA, + 0x14, 0xD5, 0x18, 0xF5, 0x59, 0x29, 0xDE, 0xA0, + 0x98, 0xCA, 0x7A, 0x6C, 0xCF, 0xE6, 0x12, 0x27, + 0x05, 0x3C, 0x84, 0xE4, 0x9A, 0x4A, 0x33, 0x32 + }; + + using(ChaCha20Cipher c = new ChaCha20Cipher(pbKey, pbIV, true)) + { + c.Decrypt(pb, 0, pb.Length); + + if(!MemUtil.ArraysEqual(pb, pbExpc)) + throw new SecurityException("ChaCha20-7"); + } +#endif + } + + private static void TestBlake2b() + { +#if DEBUG + Blake2b h = new Blake2b(); + + // ====================================================== + // From https://tools.ietf.org/html/rfc7693 + + byte[] pbData = StrUtil.Utf8.GetBytes("abc"); + byte[] pbExpc = new byte[64] { + 0xBA, 0x80, 0xA5, 0x3F, 0x98, 0x1C, 0x4D, 0x0D, + 0x6A, 0x27, 0x97, 0xB6, 0x9F, 0x12, 0xF6, 0xE9, + 0x4C, 0x21, 0x2F, 0x14, 0x68, 0x5A, 0xC4, 0xB7, + 0x4B, 0x12, 0xBB, 0x6F, 0xDB, 0xFF, 0xA2, 0xD1, + 0x7D, 0x87, 0xC5, 0x39, 0x2A, 0xAB, 0x79, 0x2D, + 0xC2, 0x52, 0xD5, 0xDE, 0x45, 0x33, 0xCC, 0x95, + 0x18, 0xD3, 0x8A, 0xA8, 0xDB, 0xF1, 0x92, 0x5A, + 0xB9, 0x23, 0x86, 0xED, 0xD4, 0x00, 0x99, 0x23 + }; + + byte[] pbC = h.ComputeHash(pbData); + if(!MemUtil.ArraysEqual(pbC, pbExpc)) + throw new SecurityException("Blake2b-1"); + + // ====================================================== + // Computed using the official b2sum tool + + pbExpc = new byte[64] { + 0x78, 0x6A, 0x02, 0xF7, 0x42, 0x01, 0x59, 0x03, + 0xC6, 0xC6, 0xFD, 0x85, 0x25, 0x52, 0xD2, 0x72, + 0x91, 0x2F, 0x47, 0x40, 0xE1, 0x58, 0x47, 0x61, + 0x8A, 0x86, 0xE2, 0x17, 0xF7, 0x1F, 0x54, 0x19, + 0xD2, 0x5E, 0x10, 0x31, 0xAF, 0xEE, 0x58, 0x53, + 0x13, 0x89, 0x64, 0x44, 0x93, 0x4E, 0xB0, 0x4B, + 0x90, 0x3A, 0x68, 0x5B, 0x14, 0x48, 0xB7, 0x55, + 0xD5, 0x6F, 0x70, 0x1A, 0xFE, 0x9B, 0xE2, 0xCE + }; + + pbC = h.ComputeHash(MemUtil.EmptyByteArray); + if(!MemUtil.ArraysEqual(pbC, pbExpc)) + throw new SecurityException("Blake2b-2"); + + // ====================================================== + // Computed using the official b2sum tool + + string strS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.:,;_-\r\n"; + StringBuilder sb = new StringBuilder(); + for(int i = 0; i < 1000; ++i) sb.Append(strS); + pbData = StrUtil.Utf8.GetBytes(sb.ToString()); + + pbExpc = new byte[64] { + 0x59, 0x69, 0x8D, 0x3B, 0x83, 0xF4, 0x02, 0x4E, + 0xD8, 0x99, 0x26, 0x0E, 0xF4, 0xE5, 0x9F, 0x20, + 0xDC, 0x31, 0xEE, 0x5B, 0x45, 0xEA, 0xBB, 0xFC, + 0x1C, 0x0A, 0x8E, 0xED, 0xAA, 0x7A, 0xFF, 0x50, + 0x82, 0xA5, 0x8F, 0xBC, 0x4A, 0x46, 0xFC, 0xC5, + 0xEF, 0x44, 0x4E, 0x89, 0x80, 0x7D, 0x3F, 0x1C, + 0xC1, 0x94, 0x45, 0xBB, 0xC0, 0x2C, 0x95, 0xAA, + 0x3F, 0x08, 0x8A, 0x93, 0xF8, 0x75, 0x91, 0xB0 + }; + + Random r = new Random(); + int p = 0; + while(p < pbData.Length) + { + int cb = r.Next(1, pbData.Length - p + 1); + h.TransformBlock(pbData, p, cb, pbData, p); + p += cb; + } + Debug.Assert(p == pbData.Length); + + h.TransformFinalBlock(MemUtil.EmptyByteArray, 0, 0); + + if(!MemUtil.ArraysEqual(h.Hash, pbExpc)) + throw new SecurityException("Blake2b-3"); + + h.Clear(); +#endif + } + + private static void TestArgon2() + { +#if DEBUG + Argon2Kdf kdf = new Argon2Kdf(); + + // ====================================================== + // From the official Argon2 1.3 reference code package + // (test vector for Argon2d 1.3); also on + // https://tools.ietf.org/html/draft-irtf-cfrg-argon2-00 + + KdfParameters p = kdf.GetDefaultParameters(); + kdf.Randomize(p); + + Debug.Assert(p.GetUInt32(Argon2Kdf.ParamVersion, 0) == 0x13U); + + byte[] pbMsg = new byte[32]; + for(int i = 0; i < pbMsg.Length; ++i) pbMsg[i] = 1; + + p.SetUInt64(Argon2Kdf.ParamMemory, 32 * 1024); + p.SetUInt64(Argon2Kdf.ParamIterations, 3); + p.SetUInt32(Argon2Kdf.ParamParallelism, 4); + + byte[] pbSalt = new byte[16]; + for(int i = 0; i < pbSalt.Length; ++i) pbSalt[i] = 2; + p.SetByteArray(Argon2Kdf.ParamSalt, pbSalt); + + byte[] pbKey = new byte[8]; + for(int i = 0; i < pbKey.Length; ++i) pbKey[i] = 3; + p.SetByteArray(Argon2Kdf.ParamSecretKey, pbKey); + + byte[] pbAssoc = new byte[12]; + for(int i = 0; i < pbAssoc.Length; ++i) pbAssoc[i] = 4; + p.SetByteArray(Argon2Kdf.ParamAssocData, pbAssoc); + + byte[] pbExpc = new byte[32] { + 0x51, 0x2B, 0x39, 0x1B, 0x6F, 0x11, 0x62, 0x97, + 0x53, 0x71, 0xD3, 0x09, 0x19, 0x73, 0x42, 0x94, + 0xF8, 0x68, 0xE3, 0xBE, 0x39, 0x84, 0xF3, 0xC1, + 0xA1, 0x3A, 0x4D, 0xB9, 0xFA, 0xBE, 0x4A, 0xCB + }; + + byte[] pb = kdf.Transform(pbMsg, p); + + if(!MemUtil.ArraysEqual(pb, pbExpc)) + throw new SecurityException("Argon2-1"); + + // ====================================================== + // From the official Argon2 1.3 reference code package + // (test vector for Argon2d 1.0) + + p.SetUInt32(Argon2Kdf.ParamVersion, 0x10); + + pbExpc = new byte[32] { + 0x96, 0xA9, 0xD4, 0xE5, 0xA1, 0x73, 0x40, 0x92, + 0xC8, 0x5E, 0x29, 0xF4, 0x10, 0xA4, 0x59, 0x14, + 0xA5, 0xDD, 0x1F, 0x5C, 0xBF, 0x08, 0xB2, 0x67, + 0x0D, 0xA6, 0x8A, 0x02, 0x85, 0xAB, 0xF3, 0x2B + }; + + pb = kdf.Transform(pbMsg, p); + + if(!MemUtil.ArraysEqual(pb, pbExpc)) + throw new SecurityException("Argon2-2"); + + // ====================================================== + // From the official 'phc-winner-argon2-20151206.zip' + // (test vector for Argon2d 1.0) + + p.SetUInt64(Argon2Kdf.ParamMemory, 16 * 1024); + + pbExpc = new byte[32] { + 0x57, 0xB0, 0x61, 0x3B, 0xFD, 0xD4, 0x13, 0x1A, + 0x0C, 0x34, 0x88, 0x34, 0xC6, 0x72, 0x9C, 0x2C, + 0x72, 0x29, 0x92, 0x1E, 0x6B, 0xBA, 0x37, 0x66, + 0x5D, 0x97, 0x8C, 0x4F, 0xE7, 0x17, 0x5E, 0xD2 + }; + + pb = kdf.Transform(pbMsg, p); + + if(!MemUtil.ArraysEqual(pb, pbExpc)) + throw new SecurityException("Argon2-3"); + +#if SELFTEST_ARGON2_LONG + // ====================================================== + // Computed using the official 'argon2' application + // (test vectors for Argon2d 1.3) + + p = kdf.GetDefaultParameters(); + + pbMsg = StrUtil.Utf8.GetBytes("ABC1234"); + + p.SetUInt64(Argon2Kdf.ParamMemory, (1 << 11) * 1024); // 2 MB + p.SetUInt64(Argon2Kdf.ParamIterations, 2); + p.SetUInt32(Argon2Kdf.ParamParallelism, 2); + + pbSalt = StrUtil.Utf8.GetBytes("somesalt"); + p.SetByteArray(Argon2Kdf.ParamSalt, pbSalt); + + pbExpc = new byte[32] { + 0x29, 0xCB, 0xD3, 0xA1, 0x93, 0x76, 0xF7, 0xA2, + 0xFC, 0xDF, 0xB0, 0x68, 0xAC, 0x0B, 0x99, 0xBA, + 0x40, 0xAC, 0x09, 0x01, 0x73, 0x42, 0xCE, 0xF1, + 0x29, 0xCC, 0xA1, 0x4F, 0xE1, 0xC1, 0xB7, 0xA3 + }; + + pb = kdf.Transform(pbMsg, p); + + if(!MemUtil.ArraysEqual(pb, pbExpc)) + throw new SecurityException("Argon2-4"); + + p.SetUInt64(Argon2Kdf.ParamMemory, (1 << 10) * 1024); // 1 MB + p.SetUInt64(Argon2Kdf.ParamIterations, 3); + + pbExpc = new byte[32] { + 0x7A, 0xBE, 0x1C, 0x1C, 0x8D, 0x7F, 0xD6, 0xDC, + 0x7C, 0x94, 0x06, 0x3E, 0xD8, 0xBC, 0xD8, 0x1C, + 0x2F, 0x87, 0x84, 0x99, 0x12, 0x83, 0xFE, 0x76, + 0x00, 0x64, 0xC4, 0x58, 0xA4, 0xDA, 0x35, 0x70 + }; + + pb = kdf.Transform(pbMsg, p); + + if(!MemUtil.ArraysEqual(pb, pbExpc)) + throw new SecurityException("Argon2-5"); + +#if SELFTEST_ARGON2_LONGER + p.SetUInt64(Argon2Kdf.ParamMemory, (1 << 20) * 1024); // 1 GB + p.SetUInt64(Argon2Kdf.ParamIterations, 2); + p.SetUInt32(Argon2Kdf.ParamParallelism, 3); + + pbExpc = new byte[32] { + 0xE6, 0xE7, 0xCB, 0xF5, 0x5A, 0x06, 0x93, 0x05, + 0x32, 0xBA, 0x86, 0xC6, 0x1F, 0x45, 0x17, 0x99, + 0x65, 0x41, 0x77, 0xF9, 0x30, 0x55, 0x9A, 0xE8, + 0x3D, 0x21, 0x48, 0xC6, 0x2D, 0x0C, 0x49, 0x11 + }; + + pb = kdf.Transform(pbMsg, p); + + if(!MemUtil.ArraysEqual(pb, pbExpc)) + throw new SecurityException("Argon2-6"); +#endif // SELFTEST_ARGON2_LONGER +#endif // SELFTEST_ARGON2_LONG +#endif // DEBUG + } + + private static void TestHmac() + { +#if DEBUG + // Test vectors from RFC 4231 + + byte[] pbKey = new byte[20]; + for(int i = 0; i < pbKey.Length; ++i) pbKey[i] = 0x0B; + byte[] pbMsg = StrUtil.Utf8.GetBytes("Hi There"); + byte[] pbExpc = new byte[32] { + 0xB0, 0x34, 0x4C, 0x61, 0xD8, 0xDB, 0x38, 0x53, + 0x5C, 0xA8, 0xAF, 0xCE, 0xAF, 0x0B, 0xF1, 0x2B, + 0x88, 0x1D, 0xC2, 0x00, 0xC9, 0x83, 0x3D, 0xA7, + 0x26, 0xE9, 0x37, 0x6C, 0x2E, 0x32, 0xCF, 0xF7 + }; + HmacEval(pbKey, pbMsg, pbExpc, "1"); + + pbKey = new byte[131]; + for(int i = 0; i < pbKey.Length; ++i) pbKey[i] = 0xAA; + pbMsg = StrUtil.Utf8.GetBytes( + "This is a test using a larger than block-size key and " + + "a larger than block-size data. The key needs to be " + + "hashed before being used by the HMAC algorithm."); + pbExpc = new byte[32] { + 0x9B, 0x09, 0xFF, 0xA7, 0x1B, 0x94, 0x2F, 0xCB, + 0x27, 0x63, 0x5F, 0xBC, 0xD5, 0xB0, 0xE9, 0x44, + 0xBF, 0xDC, 0x63, 0x64, 0x4F, 0x07, 0x13, 0x93, + 0x8A, 0x7F, 0x51, 0x53, 0x5C, 0x3A, 0x35, 0xE2 + }; + HmacEval(pbKey, pbMsg, pbExpc, "2"); +#endif + } + +#if DEBUG + private static void HmacEval(byte[] pbKey, byte[] pbMsg, + byte[] pbExpc, string strID) + { + using(HMACSHA256 h = new HMACSHA256(pbKey)) + { + h.TransformBlock(pbMsg, 0, pbMsg.Length, pbMsg, 0); + h.TransformFinalBlock(MemUtil.EmptyByteArray, 0, 0); + + byte[] pbHash = h.Hash; + if(!MemUtil.ArraysEqual(pbHash, pbExpc)) + throw new SecurityException("HMAC-SHA-256-" + strID); + } + } +#endif + private static void TestNativeKeyTransform() { #if DEBUG @@ -235,8 +701,8 @@ namespace KeePassLib.Cryptography byte[] pbManaged = new byte[32]; Array.Copy(pbOrgKey, pbManaged, 32); - if(!CompositeKey.TransformKeyManaged(pbManaged, pbSeed, uRounds)) - throw new SecurityException("Managed transform."); + if(!AesKdf.TransformKeyManaged(pbManaged, pbSeed, uRounds)) + throw new SecurityException("AES-KDF-1"); byte[] pbNative = new byte[32]; Array.Copy(pbOrgKey, pbNative, 32); @@ -244,7 +710,7 @@ namespace KeePassLib.Cryptography return; // Native library not available ("success") if(!MemUtil.ArraysEqual(pbManaged, pbNative)) - throw new SecurityException("Native transform."); + throw new SecurityException("AES-KDF-2"); #endif } @@ -310,6 +776,15 @@ namespace KeePassLib.Cryptography pbRes = MemUtil.ParseBase32("JNSXSIDQOJXXM2LEMVZCAYTBONSWIIDPNYQG63TFFV2GS3LFEBYGC43TO5XXEZDTFY======"); pbExp = Encoding.ASCII.GetBytes("Key provider based on one-time passwords."); if(!MemUtil.ArraysEqual(pbRes, pbExp)) throw new Exception("Base32-7"); + + int i = 0 - 0x10203040; + pbRes = MemUtil.Int32ToBytes(i); + if(MemUtil.ByteArrayToHexString(pbRes) != "C0CFDFEF") + throw new Exception("MemUtil-8"); // Must be little-endian + if(MemUtil.BytesToUInt32(pbRes) != (uint)i) + throw new Exception("MemUtil-9"); + if(MemUtil.BytesToInt32(pbRes) != i) + throw new Exception("MemUtil-10"); #endif } diff --git a/src/KeePassLib2Android/Keys/CompositeKey.cs b/src/KeePassLib2Android/Keys/CompositeKey.cs index 363c7797..54bf347e 100644 --- a/src/KeePassLib2Android/Keys/CompositeKey.cs +++ b/src/KeePassLib2Android/Keys/CompositeKey.cs @@ -22,14 +22,8 @@ using System.Collections.Generic; using System.Diagnostics; using System.Text; -#if KeePassUAP -using Org.BouncyCastle.Crypto; -using Org.BouncyCastle.Crypto.Engines; -using Org.BouncyCastle.Crypto.Parameters; -#else -using System.Security.Cryptography; -#endif - +using KeePassLib.Cryptography; +using KeePassLib.Cryptography.KeyDerivation; using KeePassLib.Native; using KeePassLib.Resources; using KeePassLib.Security; @@ -197,8 +191,7 @@ namespace KeePassLib.Keys } Debug.Assert(p == cbData); - SHA256Managed sha256 = new SHA256Managed(); - byte[] pbHash = sha256.ComputeHash(pbAllData); + byte[] pbHash = CryptoUtil.HashSha256(pbAllData); MemUtil.ZeroByteArray(pbAllData); return pbHash; } @@ -216,15 +209,7 @@ namespace KeePassLib.Keys 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. + [Obsolete] public ProtectedBinary GenerateKey32(byte[] pbKeySeed32, ulong uNumRounds) { Debug.Assert(pbKeySeed32 != null); @@ -232,18 +217,43 @@ namespace KeePassLib.Keys Debug.Assert(pbKeySeed32.Length == 32); if(pbKeySeed32.Length != 32) throw new ArgumentException("pbKeySeed32"); + AesKdf kdf = new AesKdf(); + KdfParameters p = kdf.GetDefaultParameters(); + p.SetUInt64(AesKdf.ParamRounds, uNumRounds); + p.SetByteArray(AesKdf.ParamSeed, pbKeySeed32); + + return GenerateKey32(p); + } + + /// + /// Generate a 32-byte (256-bit) key from the composite key. + /// + public ProtectedBinary GenerateKey32(KdfParameters p) + { + if(p == null) { Debug.Assert(false); throw new ArgumentNullException("p"); } + 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; } + KdfEngine kdf = KdfPool.Get(p.KdfUuid); + if(kdf == null) // CryptographicExceptions are translated to "file corrupted" + throw new Exception(KLRes.UnknownKdf + MessageService.NewParagraph + + KLRes.FileNewVerOrPlgReq + MessageService.NewParagraph + + "UUID: " + p.KdfUuid.ToHexString() + "."); + + byte[] pbTrf32 = kdf.Transform(pbRaw32, p); + if(pbTrf32 == null) { Debug.Assert(false); return null; } + + if(pbTrf32.Length != 32) + { + Debug.Assert(false); + pbTrf32 = CryptoUtil.HashSha256(pbTrf32); + } ProtectedBinary pbRet = new ProtectedBinary(true, pbTrf32); MemUtil.ZeroByteArray(pbTrf32); MemUtil.ZeroByteArray(pbRaw32); - return pbRet; } @@ -263,182 +273,6 @@ namespace KeePassLib.Keys 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 - { - // Try to use the native library first - if(NativeLib.TransformKey256(pbNewKey, pbKeySeed32, uNumRounds)) - return (new SHA256Managed()).ComputeHash(pbNewKey); - - if(!TransformKeyManaged(pbNewKey, pbKeySeed32, uNumRounds)) - return null; - - return (new SHA256Managed()).ComputeHash(pbNewKey); - } - finally { MemUtil.ZeroByteArray(pbNewKey); } - } - - public static bool TransformKeyManaged(byte[] pbNewKey32, byte[] pbKeySeed32, - ulong uNumRounds) - { -#if KeePassUAP - KeyParameter kp = new KeyParameter(pbKeySeed32); - AesEngine aes = new AesEngine(); - aes.Init(true, kp); - - for(ulong i = 0; i < uNumRounds; ++i) - { - aes.ProcessBlock(pbNewKey32, 0, pbNewKey32, 0); - aes.ProcessBlock(pbNewKey32, 16, pbNewKey32, 16); - } -#else - 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); - } -#endif - - 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[] 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; - } - -#if KeePassUAP - KeyParameter kp = new KeyParameter(pbKey); - AesEngine aes = new AesEngine(); - aes.Init(true, kp); -#else - 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 = 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; - } -#endif - - uRounds = 0; - int tStart = Environment.TickCount; - while(true) - { - for(ulong j = 0; j < uStep; ++j) - { -#if KeePassUAP - aes.ProcessBlock(pbNewKey, 0, pbNewKey, 0); - aes.ProcessBlock(pbNewKey, 16, pbNewKey, 16); -#else - iCrypt.TransformBlock(pbNewKey, 0, 16, pbNewKey, 0); - iCrypt.TransformBlock(pbNewKey, 16, 16, pbNewKey, 16); -#endif - } - - uRounds += uStep; - if(uRounds < uStep) // Overflow check - { - uRounds = ulong.MaxValue; - break; - } - - uint tElapsed = (uint)(Environment.TickCount - tStart); - if(tElapsed > uMilliseconds) break; - } - - return uRounds; - } } public sealed class InvalidCompositeKeyException : Exception diff --git a/src/KeePassLib2Android/Keys/KcpCustomKey.cs b/src/KeePassLib2Android/Keys/KcpCustomKey.cs index 0f0c369b..7be09e58 100644 --- a/src/KeePassLib2Android/Keys/KcpCustomKey.cs +++ b/src/KeePassLib2Android/Keys/KcpCustomKey.cs @@ -22,10 +22,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Text; -#if !KeePassUAP -using System.Security.Cryptography; -#endif - +using KeePassLib.Cryptography; using KeePassLib.Security; namespace KeePassLib.Keys @@ -57,8 +54,7 @@ namespace KeePassLib.Keys if(bPerformHash) { - SHA256Managed sha256 = new SHA256Managed(); - byte[] pbRaw = sha256.ComputeHash(pbKeyData); + byte[] pbRaw = CryptoUtil.HashSha256(pbKeyData); m_pbKey = new ProtectedBinary(true, pbRaw); } else m_pbKey = new ProtectedBinary(true, pbKeyData); diff --git a/src/KeePassLib2Android/Keys/KcpKeyFile.cs b/src/KeePassLib2Android/Keys/KcpKeyFile.cs index 07726bcc..4b3f104e 100644 --- a/src/KeePassLib2Android/Keys/KcpKeyFile.cs +++ b/src/KeePassLib2Android/Keys/KcpKeyFile.cs @@ -133,10 +133,7 @@ namespace KeePassLib.Keys else if(iLength == 64) pbKey = LoadHexKey32(pbFileData); if(pbKey == null) - { - SHA256Managed sha256 = new SHA256Managed(); - pbKey = sha256.ComputeHash(pbFileData); - } + pbKey = CryptoUtil.HashSha256(pbFileData); return pbKey; } @@ -156,12 +153,15 @@ namespace KeePassLib.Keys try { - string strHex = StrUtil.Utf8.GetString(pbFileData, 0, 64); - if(!StrUtil.IsHexString(strHex, true)) return null; + if(!StrUtil.IsHexString(pbFileData, true)) return null; + string strHex = StrUtil.Utf8.GetString(pbFileData); byte[] pbKey = MemUtil.HexStringToByteArray(strHex); if((pbKey == null) || (pbKey.Length != 32)) + { + Debug.Assert(false); return null; + } return pbKey; } @@ -189,13 +189,13 @@ namespace KeePassLib.Keys pbFinalKey32 = pbKey32; else { - MemoryStream ms = new MemoryStream(); - ms.Write(pbAdditionalEntropy, 0, pbAdditionalEntropy.Length); - ms.Write(pbKey32, 0, 32); + using(MemoryStream ms = new MemoryStream()) + { + MemUtil.Write(ms, pbAdditionalEntropy); + MemUtil.Write(ms, pbKey32); - SHA256Managed sha256 = new SHA256Managed(); - pbFinalKey32 = sha256.ComputeHash(ms.ToArray()); - ms.Close(); + pbFinalKey32 = CryptoUtil.HashSha256(ms.ToArray()); + } } CreateXmlKeyFile(strFilePath, pbFinalKey32); diff --git a/src/KeePassLib2Android/Keys/KcpPassword.cs b/src/KeePassLib2Android/Keys/KcpPassword.cs index b5526665..a347867f 100644 --- a/src/KeePassLib2Android/Keys/KcpPassword.cs +++ b/src/KeePassLib2Android/Keys/KcpPassword.cs @@ -21,10 +21,7 @@ using System; using System.Diagnostics; using System.Text; -#if !KeePassUAP -using System.Security.Cryptography; -#endif - +using KeePassLib.Cryptography; using KeePassLib.Security; using KeePassLib.Utility; @@ -75,8 +72,7 @@ namespace KeePassLib.Keys Debug.Assert(ValidatePassword(pbPasswordUtf8)); #endif - SHA256Managed sha256 = new SHA256Managed(); - byte[] pbRaw = sha256.ComputeHash(pbPasswordUtf8); + byte[] pbRaw = CryptoUtil.HashSha256(pbPasswordUtf8); m_psPassword = new ProtectedString(true, pbPasswordUtf8); m_pbKeyData = new ProtectedBinary(true, pbRaw); diff --git a/src/KeePassLib2Android/PwDatabase.cs b/src/KeePassLib2Android/PwDatabase.cs index 5f3f7129..bf0242ea 100644 --- a/src/KeePassLib2Android/PwDatabase.cs +++ b/src/KeePassLib2Android/PwDatabase.cs @@ -29,6 +29,7 @@ using System.Drawing; using KeePassLib.Collections; using KeePassLib.Cryptography; using KeePassLib.Cryptography.Cipher; +using KeePassLib.Cryptography.KeyDerivation; using KeePassLib.Delegates; using KeePassLib.Interfaces; using KeePassLib.Keys; @@ -50,13 +51,14 @@ namespace KeePassLib private static bool m_bPrimaryCreated = false; - // Initializations see Clear() + // Initializations: see Clear() private PwGroup m_pgRootGroup = null; private PwObjectList m_vDeletedObjects = new PwObjectList(); private PwUuid m_uuidDataCipher = StandardAesEngine.AesUuid; private PwCompressionAlgorithm m_caCompression = PwCompressionAlgorithm.GZip; - private ulong m_uKeyEncryptionRounds = PwDefs.DefaultKeyEncryptionRounds; + // private ulong m_uKeyEncryptionRounds = PwDefs.DefaultKeyEncryptionRounds; + private KdfParameters m_kdfParams = KdfPool.GetDefaultParameters(); private CompositeKey m_pwUserKey = null; private MemoryProtectionConfig m_memProtConfig = new MemoryProtectionConfig(); @@ -64,6 +66,7 @@ namespace KeePassLib private List m_vCustomIcons = new List(); private bool m_bUINeedsIconUpdate = true; + private DateTime m_dtSettingsChanged = PwDefs.DtDefaultNow; private string m_strName = string.Empty; private DateTime m_dtNameChanged = PwDefs.DtDefaultNow; private string m_strDesc = string.Empty; @@ -93,7 +96,8 @@ namespace KeePassLib private int m_nHistoryMaxItems = DefaultHistoryMaxItems; private long m_lHistoryMaxSize = DefaultHistoryMaxSize; // In bytes - private StringDictionaryEx m_vCustomData = new StringDictionaryEx(); + private StringDictionaryEx m_dCustomData = new StringDictionaryEx(); + private VariantDictionary m_dPublicCustomData = new VariantDictionary(); private byte[] m_pbHashOfFileOnDisk = null; private byte[] m_pbHashOfLastIO = null; @@ -168,6 +172,12 @@ namespace KeePassLib } } + public DateTime SettingsChanged + { + get { return m_dtSettingsChanged; } + set { m_dtSettingsChanged = value; } + } + /// /// Name of the database. /// @@ -281,14 +291,23 @@ namespace KeePassLib set { m_caCompression = value; } } - /// - /// Number of key transformation rounds (in order to make dictionary - /// attacks harder). - /// - public ulong KeyEncryptionRounds + // /// + // /// Number of key transformation rounds (KDF parameter). + // /// + // public ulong KeyEncryptionRounds + // { + // get { return m_uKeyEncryptionRounds; } + // set { m_uKeyEncryptionRounds = value; } + // } + + public KdfParameters KdfParameters { - get { return m_uKeyEncryptionRounds; } - set { m_uKeyEncryptionRounds = value; } + get { return m_kdfParams; } + set + { + if(value == null) throw new ArgumentNullException("value"); + m_kdfParams = value; + } } /// @@ -408,14 +427,37 @@ namespace KeePassLib /// /// Custom data container that can be used by plugins to store /// own data in KeePass databases. + /// The data is stored in the encrypted part of encrypted + /// database files. + /// Use unique names for your items, e.g. "PluginName_ItemName". /// public StringDictionaryEx CustomData { - get { return m_vCustomData; } - set + get { return m_dCustomData; } + internal set { - Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); - m_vCustomData = value; + if(value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); } + m_dCustomData = value; + } + } + + /// + /// Custom data container that can be used by plugins to store + /// own data in KeePass databases. + /// The data is stored in the *unencrypted* part of database files, + /// and it is not supported by all file formats (e.g. supported by KDBX, + /// unsupported by XML). + /// It is highly recommended to use CustomData instead, + /// if possible. + /// Use unique names for your items, e.g. "PluginName_ItemName". + /// + public VariantDictionary PublicCustomData + { + get { return m_dPublicCustomData; } + internal set + { + if(value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); } + m_dPublicCustomData = value; } } @@ -484,7 +526,8 @@ namespace KeePassLib m_uuidDataCipher = StandardAesEngine.AesUuid; m_caCompression = PwCompressionAlgorithm.GZip; - m_uKeyEncryptionRounds = PwDefs.DefaultKeyEncryptionRounds; + // m_uKeyEncryptionRounds = PwDefs.DefaultKeyEncryptionRounds; + m_kdfParams = KdfPool.GetDefaultParameters(); m_pwUserKey = null; m_memProtConfig = new MemoryProtectionConfig(); @@ -494,6 +537,7 @@ namespace KeePassLib DateTime dtNow = DateTime.Now; + m_dtSettingsChanged = dtNow; m_strName = string.Empty; m_dtNameChanged = dtNow; m_strDesc = string.Empty; @@ -523,7 +567,8 @@ namespace KeePassLib m_nHistoryMaxItems = DefaultHistoryMaxItems; m_lHistoryMaxSize = DefaultHistoryMaxSize; - m_vCustomData = new StringDictionaryEx(); + m_dCustomData = new StringDictionaryEx(); + m_dPublicCustomData = new VariantDictionary(); m_pbHashOfFileOnDisk = null; m_pbHashOfLastIO = null; @@ -1393,6 +1438,14 @@ namespace KeePassLib return; bool bForce = (mm == PwMergeMethod.OverwriteExisting); + bool bSourceNewer = (pdSource.m_dtSettingsChanged > m_dtSettingsChanged); + + if(bForce || bSourceNewer) + { + m_dtSettingsChanged = pdSource.m_dtSettingsChanged; + + m_clr = pdSource.m_clr; + } if(bForce || (pdSource.m_dtNameChanged > m_dtNameChanged)) { @@ -1412,8 +1465,6 @@ namespace KeePassLib m_dtDefaultUserChanged = pdSource.m_dtDefaultUserChanged; } - if(bForce) m_clr = pdSource.m_clr; - PwUuid pwPrefBin = m_pwRecycleBin, pwAltBin = pdSource.m_pwRecycleBin; if(bForce || (pdSource.m_dtRecycleBinChanged > m_dtRecycleBinChanged)) { @@ -1440,6 +1491,16 @@ namespace KeePassLib else if(m_pgRootGroup.FindGroup(pwAltTmp, true) != null) m_pwEntryTemplatesGroup = pwAltTmp; else m_pwEntryTemplatesGroup = PwUuid.Zero; // Debug.Assert(false); + + foreach(KeyValuePair kvp in pdSource.m_dCustomData) + { + if(bSourceNewer || !m_dCustomData.Exists(kvp.Key)) + m_dCustomData.Set(kvp.Key, kvp.Value); + } + + VariantDictionary vdLocal = m_dPublicCustomData; // Backup + m_dPublicCustomData = (VariantDictionary)pdSource.m_dPublicCustomData.Clone(); + if(!bSourceNewer) vdLocal.CopyTo(m_dPublicCustomData); // Merge } private void MergeEntryHistory(PwEntry pe, PwEntry peSource, diff --git a/src/KeePassLib2Android/PwEntry.cs b/src/KeePassLib2Android/PwEntry.cs index 4d2694c6..fa722273 100644 --- a/src/KeePassLib2Android/PwEntry.cs +++ b/src/KeePassLib2Android/PwEntry.cs @@ -65,6 +65,8 @@ namespace KeePassLib private List m_vTags = new List(); + private StringDictionaryEx m_dCustomData = new StringDictionaryEx(); + /// /// UUID of this entry. /// @@ -274,6 +276,23 @@ namespace KeePassLib } } + /// + /// Custom data container that can be used by plugins to store + /// own data in KeePass entries. + /// The data is stored in the encrypted part of encrypted + /// database files. + /// Use unique names for your items, e.g. "PluginName_ItemName". + /// + public StringDictionaryEx CustomData + { + get { return m_dCustomData; } + internal set + { + if(value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); } + m_dCustomData = value; + } + } + public static EventHandler EntryTouched; public EventHandler Touched; @@ -366,6 +385,8 @@ namespace KeePassLib peNew.m_vTags = new List(m_vTags); + peNew.m_dCustomData = m_dCustomData.CloneDeep(); + return peNew; } @@ -486,6 +507,8 @@ namespace KeePassLib if(m_vTags[iTag] != pe.m_vTags[iTag]) return false; } + if(!m_dCustomData.Equals(pe.m_dCustomData)) return false; + return true; } @@ -502,10 +525,10 @@ namespace KeePassLib public void AssignProperties(PwEntry peTemplate, bool bOnlyIfNewer, bool bIncludeHistory, bool bAssignLocationChanged) { - Debug.Assert(peTemplate != null); if(peTemplate == null) throw new ArgumentNullException("peTemplate"); + if(peTemplate == null) { Debug.Assert(false); throw new ArgumentNullException("peTemplate"); } - if(bOnlyIfNewer && (TimeUtil.Compare(peTemplate.m_tLastMod, m_tLastMod, - true) < 0)) + if(bOnlyIfNewer && (TimeUtil.Compare(peTemplate.m_tLastMod, + m_tLastMod, true) < 0)) return; // Template UUID should be the same as the current one @@ -515,10 +538,11 @@ namespace KeePassLib if(bAssignLocationChanged) m_tParentGroupLastMod = peTemplate.m_tParentGroupLastMod; - m_listStrings = peTemplate.m_listStrings; - m_listBinaries = peTemplate.m_listBinaries; - m_listAutoType = peTemplate.m_listAutoType; - if(bIncludeHistory) m_listHistory = peTemplate.m_listHistory; + m_listStrings = peTemplate.m_listStrings.CloneDeep(); + m_listBinaries = peTemplate.m_listBinaries.CloneDeep(); + m_listAutoType = peTemplate.m_listAutoType.CloneDeep(); + if(bIncludeHistory) + m_listHistory = peTemplate.m_listHistory.CloneDeep(); m_pwIcon = peTemplate.m_pwIcon; m_pwCustomIconID = peTemplate.m_pwCustomIconID; // Immutable @@ -536,6 +560,8 @@ namespace KeePassLib m_strOverrideUrl = peTemplate.m_strOverrideUrl; m_vTags = new List(peTemplate.m_vTags); + + m_dCustomData = peTemplate.m_dCustomData.CloneDeep(); } /// @@ -787,6 +813,9 @@ namespace KeePassLib foreach(string strTag in m_vTags) uSize += (ulong)strTag.Length; + foreach(KeyValuePair kvp in m_dCustomData) + uSize += (ulong)kvp.Key.Length + (ulong)kvp.Value.Length; + return uSize; } diff --git a/src/KeePassLib2Android/PwGroup.cs b/src/KeePassLib2Android/PwGroup.cs index 142e6902..010c4f9b 100644 --- a/src/KeePassLib2Android/PwGroup.cs +++ b/src/KeePassLib2Android/PwGroup.cs @@ -67,6 +67,8 @@ namespace KeePassLib private PwUuid m_pwLastTopVisibleEntry = PwUuid.Zero; + private StringDictionaryEx m_dCustomData = new StringDictionaryEx(); + /// /// UUID of this group. /// @@ -281,6 +283,23 @@ namespace KeePassLib } } + /// + /// Custom data container that can be used by plugins to store + /// own data in KeePass groups. + /// The data is stored in the encrypted part of encrypted + /// database files. + /// Use unique names for your items, e.g. "PluginName_ItemName". + /// + public StringDictionaryEx CustomData + { + get { return m_dCustomData; } + internal set + { + if(value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); } + m_dCustomData = value; + } + } + public static EventHandler GroupTouched; public EventHandler Touched; @@ -376,6 +395,8 @@ namespace KeePassLib pg.m_pwLastTopVisibleEntry = m_pwLastTopVisibleEntry; + pg.m_dCustomData = m_dCustomData.CloneDeep(); + return pg; } @@ -444,6 +465,8 @@ namespace KeePassLib if(!m_pwLastTopVisibleEntry.Equals(pg.m_pwLastTopVisibleEntry)) return false; + if(!m_dCustomData.Equals(pg.m_dCustomData)) return false; + if((pwOpt & PwCompareOptions.PropertiesOnly) == PwCompareOptions.None) { if(m_listEntries.UCount != pg.m_listEntries.UCount) return false; @@ -509,6 +532,8 @@ namespace KeePassLib m_bEnableSearching = pgTemplate.m_bEnableSearching; m_pwLastTopVisibleEntry = pgTemplate.m_pwLastTopVisibleEntry; + + m_dCustomData = pgTemplate.m_dCustomData.CloneDeep(); } /// diff --git a/src/KeePassLib2Android/Resources/KLRes.Generated.cs b/src/KeePassLib2Android/Resources/KLRes.Generated.cs index 84c7f3e3..af6f5485 100644 --- a/src/KeePassLib2Android/Resources/KLRes.Generated.cs +++ b/src/KeePassLib2Android/Resources/KLRes.Generated.cs @@ -27,7 +27,7 @@ namespace KeePassLib.Resources if(dictNew == null) throw new ArgumentNullException("dictNew"); m_strCryptoStreamFailed = TryGetEx(dictNew, "CryptoStreamFailed", m_strCryptoStreamFailed); - m_strEncAlgorithmAes = TryGetEx(dictNew, "EncAlgorithmAes", m_strEncAlgorithmAes); + m_strEncDataTooLarge = TryGetEx(dictNew, "EncDataTooLarge", m_strEncDataTooLarge); m_strErrorInClipboard = TryGetEx(dictNew, "ErrorInClipboard", m_strErrorInClipboard); m_strExpect100Continue = TryGetEx(dictNew, "Expect100Continue", m_strExpect100Continue); m_strFatalError = TryGetEx(dictNew, "FatalError", m_strFatalError); @@ -36,6 +36,7 @@ namespace KeePassLib.Resources m_strFileHeaderEndEarly = TryGetEx(dictNew, "FileHeaderEndEarly", m_strFileHeaderEndEarly); m_strFileLoadFailed = TryGetEx(dictNew, "FileLoadFailed", m_strFileLoadFailed); m_strFileLockedWrite = TryGetEx(dictNew, "FileLockedWrite", m_strFileLockedWrite); + m_strFileNewVerOrPlgReq = TryGetEx(dictNew, "FileNewVerOrPlgReq", m_strFileNewVerOrPlgReq); m_strFileNewVerReq = TryGetEx(dictNew, "FileNewVerReq", m_strFileNewVerReq); m_strFileSaveCorruptionWarning = TryGetEx(dictNew, "FileSaveCorruptionWarning", m_strFileSaveCorruptionWarning); m_strFileSaveFailed = TryGetEx(dictNew, "FileSaveFailed", m_strFileSaveFailed); @@ -50,6 +51,7 @@ namespace KeePassLib.Resources m_strInvalidCompositeKeyHint = TryGetEx(dictNew, "InvalidCompositeKeyHint", m_strInvalidCompositeKeyHint); m_strInvalidDataWhileDecoding = TryGetEx(dictNew, "InvalidDataWhileDecoding", m_strInvalidDataWhileDecoding); m_strKeePass1xHint = TryGetEx(dictNew, "KeePass1xHint", m_strKeePass1xHint); + m_strKeyBits = TryGetEx(dictNew, "KeyBits", m_strKeyBits); m_strKeyFileDbSel = TryGetEx(dictNew, "KeyFileDbSel", m_strKeyFileDbSel); m_strMasterSeedLengthInvalid = TryGetEx(dictNew, "MasterSeedLengthInvalid", m_strMasterSeedLengthInvalid); m_strOldFormat = TryGetEx(dictNew, "OldFormat", m_strOldFormat); @@ -58,13 +60,14 @@ namespace KeePassLib.Resources m_strTimeout = TryGetEx(dictNew, "Timeout", m_strTimeout); m_strTryAgainSecs = TryGetEx(dictNew, "TryAgainSecs", m_strTryAgainSecs); m_strUnknownHeaderId = TryGetEx(dictNew, "UnknownHeaderId", m_strUnknownHeaderId); + m_strUnknownKdf = TryGetEx(dictNew, "UnknownKdf", m_strUnknownKdf); m_strUserAccountKeyError = TryGetEx(dictNew, "UserAccountKeyError", m_strUserAccountKeyError); m_strUserAgent = TryGetEx(dictNew, "UserAgent", m_strUserAgent); } private static readonly string[] m_vKeyNames = { "CryptoStreamFailed", - "EncAlgorithmAes", + "EncDataTooLarge", "ErrorInClipboard", "Expect100Continue", "FatalError", @@ -73,6 +76,7 @@ namespace KeePassLib.Resources "FileHeaderEndEarly", "FileLoadFailed", "FileLockedWrite", + "FileNewVerOrPlgReq", "FileNewVerReq", "FileSaveCorruptionWarning", "FileSaveFailed", @@ -87,6 +91,7 @@ namespace KeePassLib.Resources "InvalidCompositeKeyHint", "InvalidDataWhileDecoding", "KeePass1xHint", + "KeyBits", "KeyFileDbSel", "MasterSeedLengthInvalid", "OldFormat", @@ -95,6 +100,7 @@ namespace KeePassLib.Resources "Timeout", "TryAgainSecs", "UnknownHeaderId", + "UnknownKdf", "UserAccountKeyError", "UserAgent" }; @@ -115,15 +121,15 @@ namespace KeePassLib.Resources get { return m_strCryptoStreamFailed; } } - private static string m_strEncAlgorithmAes = - @"AES/Rijndael (256-Bit Key)"; + private static string m_strEncDataTooLarge = + @"The data is too large to be encrypted/decrypted securely using {PARAM}."; /// /// Look up a localized string similar to - /// 'AES/Rijndael (256-Bit Key)'. + /// 'The data is too large to be encrypted/decrypted securely using {PARAM}.'. /// - public static string EncAlgorithmAes + public static string EncDataTooLarge { - get { return m_strEncAlgorithmAes; } + get { return m_strEncDataTooLarge; } } private static string m_strErrorInClipboard = @@ -214,6 +220,17 @@ namespace KeePassLib.Resources get { return m_strFileLockedWrite; } } + private static string m_strFileNewVerOrPlgReq = + @"A newer KeePass version or a plugin is required to open this file."; + /// + /// Look up a localized string similar to + /// 'A newer KeePass version or a plugin is required to open this file.'. + /// + public static string FileNewVerOrPlgReq + { + get { return m_strFileNewVerOrPlgReq; } + } + private static string m_strFileNewVerReq = @"A newer KeePass version is required to open this file."; /// @@ -368,6 +385,17 @@ namespace KeePassLib.Resources get { return m_strKeePass1xHint; } } + private static string m_strKeyBits = + @"{PARAM}-bit key"; + /// + /// Look up a localized string similar to + /// '{PARAM}-bit key'. + /// + public static string KeyBits + { + get { return m_strKeyBits; } + } + private static string m_strKeyFileDbSel = @"Database files cannot be used as key files."; /// @@ -456,6 +484,17 @@ namespace KeePassLib.Resources get { return m_strUnknownHeaderId; } } + private static string m_strUnknownKdf = + @"Unknown key derivation function!"; + /// + /// Look up a localized string similar to + /// 'Unknown key derivation function!'. + /// + public static string UnknownKdf + { + get { return m_strUnknownKdf; } + } + private static string m_strUserAccountKeyError = @"The operating system did not grant KeePass read/write access to the user profile folder, where the protected user key is stored."; /// diff --git a/src/KeePassLib2Android/Security/ProtectedBinary.cs b/src/KeePassLib2Android/Security/ProtectedBinary.cs index fd9ca2f2..ab636157 100644 --- a/src/KeePassLib2Android/Security/ProtectedBinary.cs +++ b/src/KeePassLib2Android/Security/ProtectedBinary.cs @@ -76,7 +76,7 @@ namespace KeePassLib.Security { None = 0, ProtectedMemory, - Salsa20, + ChaCha20, ExtCrypt } @@ -166,7 +166,7 @@ namespace KeePassLib.Security /// public ProtectedBinary() { - Init(false, new byte[0]); + Init(false, MemUtil.EmptyByteArray); } /// @@ -263,11 +263,13 @@ namespace KeePassLib.Security if(pbUpd != null) pbKey32 = pbUpd; } - Salsa20Cipher s = new Salsa20Cipher(pbKey32, - BitConverter.GetBytes(m_lID)); - s.Encrypt(m_pbData, m_pbData.Length, true); - s.Dispose(); - m_mp = PbMemProt.Salsa20; + byte[] pbIV = new byte[12]; + MemUtil.UInt64ToBytesEx((ulong)m_lID, pbIV, 4); + using(ChaCha20Cipher c = new ChaCha20Cipher(pbKey32, pbIV, true)) + { + c.Encrypt(m_pbData, 0, m_pbData.Length); + } + m_mp = PbMemProt.ChaCha20; } private void Decrypt() @@ -276,12 +278,14 @@ namespace KeePassLib.Security if(m_mp == PbMemProt.ProtectedMemory) ProtectedMemory.Unprotect(m_pbData, MemoryProtectionScope.SameProcess); - else if(m_mp == PbMemProt.Salsa20) + else if(m_mp == PbMemProt.ChaCha20) { - Salsa20Cipher s = new Salsa20Cipher(g_pbKey32, - BitConverter.GetBytes(m_lID)); - s.Encrypt(m_pbData, m_pbData.Length, true); - s.Dispose(); + byte[] pbIV = new byte[12]; + MemUtil.UInt64ToBytesEx((ulong)m_lID, pbIV, 4); + using(ChaCha20Cipher c = new ChaCha20Cipher(g_pbKey32, pbIV, true)) + { + c.Decrypt(m_pbData, 0, m_pbData.Length); + } } else if(m_mp == PbMemProt.ExtCrypt) m_fExtCrypt(m_pbData, PbCryptFlags.Decrypt, m_lID); @@ -300,7 +304,7 @@ namespace KeePassLib.Security /// protected data and can therefore be cleared safely. public byte[] ReadData() { - if(m_uDataLen == 0) return new byte[0]; + if(m_uDataLen == 0) return MemUtil.EmptyByteArray; byte[] pbReturn = new byte[m_uDataLen]; diff --git a/src/KeePassLib2Android/Serialization/FileLock.cs b/src/KeePassLib2Android/Serialization/FileLock.cs index 9fa81777..7cc591a0 100644 --- a/src/KeePassLib2Android/Serialization/FileLock.cs +++ b/src/KeePassLib2Android/Serialization/FileLock.cs @@ -152,7 +152,7 @@ namespace KeePassLib.Serialization try { byte[] pbID = CryptoRandom.Instance.GetRandomBytes(16); - string strTime = TimeUtil.SerializeUtc(DateTime.Now); + string strTime = TimeUtil.SerializeUtc(DateTime.UtcNow); lfi = new LockFileInfo(Convert.ToBase64String(pbID), strTime, #if KeePassUAP diff --git a/src/KeePassLib2Android/Serialization/HashedBlockStream.cs b/src/KeePassLib2Android/Serialization/HashedBlockStream.cs index ead4855d..003fab96 100644 --- a/src/KeePassLib2Android/Serialization/HashedBlockStream.cs +++ b/src/KeePassLib2Android/Serialization/HashedBlockStream.cs @@ -22,10 +22,7 @@ using System.Diagnostics; using System.IO; using System.Text; -#if !KeePassUAP -using System.Security.Cryptography; -#endif - +using KeePassLib.Cryptography; using KeePassLib.Native; using KeePassLib.Utility; @@ -37,7 +34,7 @@ namespace KeePassLib.Serialization { public sealed class HashedBlockStream : Stream { - private const int m_nDefaultBufferSize = 1024 * 1024; // 1 MB + private const int NbDefaultBufferSize = 1024 * 1024; // 1 MB private Stream m_sBaseStream; private bool m_bWriting; @@ -50,7 +47,7 @@ namespace KeePassLib.Serialization private byte[] m_pbBuffer; private int m_nBufferPos = 0; - private uint m_uBufferIndex = 0; + private uint m_uBlockIndex = 0; public override bool CanRead { @@ -69,13 +66,13 @@ namespace KeePassLib.Serialization public override long Length { - get { throw new NotSupportedException(); } + get { Debug.Assert(false); throw new NotSupportedException(); } } public override long Position { - get { throw new NotSupportedException(); } - set { throw new NotSupportedException(); } + get { Debug.Assert(false); throw new NotSupportedException(); } + set { Debug.Assert(false); throw new NotSupportedException(); } } public HashedBlockStream(Stream sBaseStream, bool bWriting) @@ -100,7 +97,7 @@ namespace KeePassLib.Serialization if(sBaseStream == null) throw new ArgumentNullException("sBaseStream"); if(nBufferSize < 0) throw new ArgumentOutOfRangeException("nBufferSize"); - if(nBufferSize == 0) nBufferSize = m_nDefaultBufferSize; + if(nBufferSize == 0) nBufferSize = NbDefaultBufferSize; m_sBaseStream = sBaseStream; m_bWriting = bWriting; @@ -114,7 +111,7 @@ namespace KeePassLib.Serialization m_brInput = new BinaryReader(sBaseStream, utf8); - m_pbBuffer = new byte[0]; + m_pbBuffer = MemUtil.EmptyByteArray; } else // Writing mode { @@ -142,7 +139,7 @@ namespace KeePassLib.Serialization #endif if(m_sBaseStream != null) { - if(m_bWriting == false) // Reading mode + if(!m_bWriting) // Reading mode { m_brInput.Close(); m_brInput = null; @@ -209,9 +206,9 @@ namespace KeePassLib.Serialization m_nBufferPos = 0; - if(m_brInput.ReadUInt32() != m_uBufferIndex) + if(m_brInput.ReadUInt32() != m_uBlockIndex) throw new InvalidDataException(); - ++m_uBufferIndex; + ++m_uBlockIndex; byte[] pbStoredHash = m_brInput.ReadBytes(32); if((pbStoredHash == null) || (pbStoredHash.Length != 32)) @@ -236,7 +233,7 @@ namespace KeePassLib.Serialization } m_bEos = true; - m_pbBuffer = new byte[0]; + m_pbBuffer = MemUtil.EmptyByteArray; return false; } @@ -246,16 +243,12 @@ namespace KeePassLib.Serialization if(m_bVerify) { - SHA256Managed sha256 = new SHA256Managed(); - byte[] pbComputedHash = sha256.ComputeHash(m_pbBuffer); + byte[] pbComputedHash = CryptoUtil.HashSha256(m_pbBuffer); if((pbComputedHash == null) || (pbComputedHash.Length != 32)) throw new InvalidOperationException(); - for(int iHashPos = 0; iHashPos < 32; ++iHashPos) - { - if(pbStoredHash[iHashPos] != pbComputedHash[iHashPos]) - throw new InvalidDataException(); - } + if(!MemUtil.ArraysEqual(pbStoredHash, pbComputedHash)) + throw new InvalidDataException(); } return true; @@ -283,26 +276,24 @@ namespace KeePassLib.Serialization private void WriteHashedBlock() { - m_bwOutput.Write(m_uBufferIndex); - ++m_uBufferIndex; + m_bwOutput.Write(m_uBlockIndex); + ++m_uBlockIndex; if(m_nBufferPos > 0) { - SHA256Managed sha256 = new SHA256Managed(); + byte[] pbHash = CryptoUtil.HashSha256(m_pbBuffer, 0, m_nBufferPos); -#if !KeePassLibSD - byte[] pbHash = sha256.ComputeHash(m_pbBuffer, 0, m_nBufferPos); -#else - byte[] pbHash; - if(m_nBufferPos == m_pbBuffer.Length) - pbHash = sha256.ComputeHash(m_pbBuffer); - else - { - byte[] pbData = new byte[m_nBufferPos]; - Array.Copy(m_pbBuffer, 0, pbData, 0, m_nBufferPos); - pbHash = sha256.ComputeHash(pbData); - } -#endif + // For KeePassLibSD: + // SHA256Managed sha256 = new SHA256Managed(); + // byte[] pbHash; + // if(m_nBufferPos == m_pbBuffer.Length) + // pbHash = sha256.ComputeHash(m_pbBuffer); + // else + // { + // byte[] pbData = new byte[m_nBufferPos]; + // Array.Copy(m_pbBuffer, 0, pbData, 0, m_nBufferPos); + // pbHash = sha256.ComputeHash(pbData); + // } m_bwOutput.Write(pbHash); } diff --git a/src/KeePassLib2Android/Serialization/HmacBlockStream.cs b/src/KeePassLib2Android/Serialization/HmacBlockStream.cs new file mode 100644 index 00000000..59295742 --- /dev/null +++ b/src/KeePassLib2Android/Serialization/HmacBlockStream.cs @@ -0,0 +1,325 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2016 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.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Text; + +#if !KeePassUAP +using System.Security.Cryptography; +#endif + +using KeePassLib.Resources; +using KeePassLib.Utility; + +namespace KeePassLib.Serialization +{ + public sealed class HmacBlockStream : Stream + { + private const int NbDefaultBufferSize = 1024 * 1024; // 1 MB + + private Stream m_sBase; + private readonly bool m_bWriting; + private readonly bool m_bVerify; + private byte[] m_pbKey; + + private bool m_bEos = false; + private byte[] m_pbBuffer; + private int m_iBufferPos = 0; + + private ulong m_uBlockIndex = 0; + + public override bool CanRead + { + get { return !m_bWriting; } + } + + public override bool CanSeek + { + get { return false; } + } + + public override bool CanWrite + { + get { return m_bWriting; } + } + + public override long Length + { + get { Debug.Assert(false); throw new NotSupportedException(); } + } + + public override long Position + { + get { Debug.Assert(false); throw new NotSupportedException(); } + set { Debug.Assert(false); throw new NotSupportedException(); } + } + + public HmacBlockStream(Stream sBase, bool bWriting, bool bVerify, + byte[] pbKey) + { + if(sBase == null) throw new ArgumentNullException("sBase"); + if(pbKey == null) throw new ArgumentNullException("pbKey"); + + m_sBase = sBase; + m_bWriting = bWriting; + m_bVerify = bVerify; + m_pbKey = pbKey; + + if(!m_bWriting) // Reading mode + { + if(!m_sBase.CanRead) throw new InvalidOperationException(); + + m_pbBuffer = MemUtil.EmptyByteArray; + } + else // Writing mode + { + if(!m_sBase.CanWrite) throw new InvalidOperationException(); + + m_pbBuffer = new byte[NbDefaultBufferSize]; + } + } + + public override void Flush() + { + Debug.Assert(m_sBase != null); // Object should not be disposed + if(m_bWriting && (m_sBase != null)) m_sBase.Flush(); + } + +#if KeePassUAP + protected override void Dispose(bool disposing) + { + if(!disposing) return; +#else + public override void Close() + { +#endif + if(m_sBase != null) + { + if(m_bWriting) + { + if(m_iBufferPos == 0) // No data left in buffer + WriteSafeBlock(); // Write terminating block + else + { + WriteSafeBlock(); // Write remaining buffered data + WriteSafeBlock(); // Write terminating block + } + + Flush(); + } + + m_sBase.Close(); + m_sBase = null; + } + } + + public override long Seek(long lOffset, SeekOrigin soOrigin) + { + Debug.Assert(false); + throw new NotSupportedException(); + } + + public override void SetLength(long lValue) + { + Debug.Assert(false); + throw new NotSupportedException(); + } + + internal static byte[] GetHmacKey64(byte[] pbKey, ulong uBlockIndex) + { + if(pbKey == null) throw new ArgumentNullException("pbKey"); + Debug.Assert(pbKey.Length == 64); + + // We are computing the HMAC using SHA-256, whose internal + // block size is 512 bits; thus create a key that is 512 + // bits long (using SHA-512) + + byte[] pbBlockKey; + using(SHA512Managed h = new SHA512Managed()) + { + byte[] pbIndex = MemUtil.UInt64ToBytes(uBlockIndex); + + h.TransformBlock(pbIndex, 0, pbIndex.Length, pbIndex, 0); + h.TransformBlock(pbKey, 0, pbKey.Length, pbKey, 0); + h.TransformFinalBlock(MemUtil.EmptyByteArray, 0, 0); + + pbBlockKey = h.Hash; + } + +#if DEBUG + byte[] pbZero = new byte[64]; + Debug.Assert((pbBlockKey.Length == 64) && !MemUtil.ArraysEqual( + pbBlockKey, pbZero)); // Ensure we own pbBlockKey +#endif + return pbBlockKey; + } + + public override int Read(byte[] pbBuffer, int iOffset, int nCount) + { + if(m_bWriting) throw new InvalidOperationException(); + + int nRemaining = nCount; + while(nRemaining > 0) + { + if(m_iBufferPos == m_pbBuffer.Length) + { + if(!ReadSafeBlock()) + return (nCount - nRemaining); // Bytes actually read + } + + int nCopy = Math.Min(m_pbBuffer.Length - m_iBufferPos, nRemaining); + Debug.Assert(nCopy > 0); + + Array.Copy(m_pbBuffer, m_iBufferPos, pbBuffer, iOffset, nCopy); + + iOffset += nCopy; + m_iBufferPos += nCopy; + + nRemaining -= nCopy; + } + + return nCount; + } + + private bool ReadSafeBlock() + { + if(m_bEos) return false; // End of stream reached already + + byte[] pbStoredHmac = MemUtil.Read(m_sBase, 32); + if((pbStoredHmac == null) || (pbStoredHmac.Length != 32)) + throw new EndOfStreamException(); + + // Block index is implicit: it's used in the HMAC computation, + // but does not need to be stored + // byte[] pbBlockIndex = MemUtil.Read(m_sBase, 8); + // if((pbBlockIndex == null) || (pbBlockIndex.Length != 8)) + // throw new EndOfStreamException(); + // ulong uBlockIndex = MemUtil.BytesToUInt64(pbBlockIndex); + // if((uBlockIndex != m_uBlockIndex) && m_bVerify) + // throw new InvalidDataException(); + byte[] pbBlockIndex = MemUtil.UInt64ToBytes(m_uBlockIndex); + + byte[] pbBlockSize = MemUtil.Read(m_sBase, 4); + if((pbBlockSize == null) || (pbBlockSize.Length != 4)) + throw new EndOfStreamException(); + int nBlockSize = MemUtil.BytesToInt32(pbBlockSize); + if(nBlockSize < 0) + throw new InvalidDataException(KLRes.FileCorrupted); + + m_iBufferPos = 0; + + m_pbBuffer = MemUtil.Read(m_sBase, nBlockSize); + if((m_pbBuffer == null) || ((m_pbBuffer.Length != nBlockSize) && m_bVerify)) + throw new EndOfStreamException(); + + if(m_bVerify) + { + byte[] pbCmpHmac; + byte[] pbBlockKey = GetHmacKey64(m_pbKey, m_uBlockIndex); + using(HMACSHA256 h = new HMACSHA256(pbBlockKey)) + { + h.TransformBlock(pbBlockIndex, 0, pbBlockIndex.Length, + pbBlockIndex, 0); + h.TransformBlock(pbBlockSize, 0, pbBlockSize.Length, + pbBlockSize, 0); + + if(m_pbBuffer.Length > 0) + h.TransformBlock(m_pbBuffer, 0, m_pbBuffer.Length, + m_pbBuffer, 0); + + h.TransformFinalBlock(MemUtil.EmptyByteArray, 0, 0); + + pbCmpHmac = h.Hash; + } + MemUtil.ZeroByteArray(pbBlockKey); + + if(!MemUtil.ArraysEqual(pbCmpHmac, pbStoredHmac)) + throw new InvalidDataException(KLRes.FileCorrupted); + } + + ++m_uBlockIndex; + + if(nBlockSize == 0) + { + m_bEos = true; + return false; // No further data available + } + return true; + } + + public override void Write(byte[] pbBuffer, int iOffset, int nCount) + { + if(!m_bWriting) throw new InvalidOperationException(); + + while(nCount > 0) + { + if(m_iBufferPos == m_pbBuffer.Length) + WriteSafeBlock(); + + int nCopy = Math.Min(m_pbBuffer.Length - m_iBufferPos, nCount); + Debug.Assert(nCopy > 0); + + Array.Copy(pbBuffer, iOffset, m_pbBuffer, m_iBufferPos, nCopy); + + iOffset += nCopy; + m_iBufferPos += nCopy; + + nCount -= nCopy; + } + } + + private void WriteSafeBlock() + { + byte[] pbBlockIndex = MemUtil.UInt64ToBytes(m_uBlockIndex); + + int cbBlockSize = m_iBufferPos; + byte[] pbBlockSize = MemUtil.Int32ToBytes(cbBlockSize); + + byte[] pbBlockHmac; + byte[] pbBlockKey = GetHmacKey64(m_pbKey, m_uBlockIndex); + using(HMACSHA256 h = new HMACSHA256(pbBlockKey)) + { + h.TransformBlock(pbBlockIndex, 0, pbBlockIndex.Length, + pbBlockIndex, 0); + h.TransformBlock(pbBlockSize, 0, pbBlockSize.Length, + pbBlockSize, 0); + + if(cbBlockSize > 0) + h.TransformBlock(m_pbBuffer, 0, cbBlockSize, m_pbBuffer, 0); + + h.TransformFinalBlock(MemUtil.EmptyByteArray, 0, 0); + + pbBlockHmac = h.Hash; + } + MemUtil.ZeroByteArray(pbBlockKey); + + MemUtil.Write(m_sBase, pbBlockHmac); + // MemUtil.Write(m_sBase, pbBlockIndex); // Implicit + MemUtil.Write(m_sBase, pbBlockSize); + if(cbBlockSize > 0) + m_sBase.Write(m_pbBuffer, 0, cbBlockSize); + + ++m_uBlockIndex; + m_iBufferPos = 0; + } + } +} diff --git a/src/KeePassLib2Android/Serialization/KdbxFile.Read.Streamed.cs b/src/KeePassLib2Android/Serialization/KdbxFile.Read.Streamed.cs index 5f39afda..5cbc4e14 100644 --- a/src/KeePassLib2Android/Serialization/KdbxFile.Read.Streamed.cs +++ b/src/KeePassLib2Android/Serialization/KdbxFile.Read.Streamed.cs @@ -58,13 +58,17 @@ namespace KeePassLib.Serialization DeletedObject, Group, GroupTimes, + GroupCustomData, + GroupCustomDataItem, Entry, EntryTimes, EntryString, EntryBinary, EntryAutoType, EntryAutoTypeItem, - EntryHistory + EntryHistory, + EntryCustomData, + EntryCustomDataItem } private bool m_bReadNextNode = true; @@ -84,10 +88,14 @@ namespace KeePassLib.Serialization private byte[] m_pbCustomIconData = null; private string m_strCustomDataKey = null; private string m_strCustomDataValue = null; + private string m_strGroupCustomDataKey = null; + private string m_strGroupCustomDataValue = null; + private string m_strEntryCustomDataKey = null; + private string m_strEntryCustomDataValue = null; - private void ReadXmlStreamed(Stream readerStream, Stream sParentStream) + private void ReadXmlStreamed(Stream sXml, Stream sParent) { - ReadDocumentStreamed(CreateXmlReader(readerStream), sParentStream); + ReadDocumentStreamed(CreateXmlReader(sXml), sParent); } internal static XmlReaderSettings CreateStdXmlReaderSettings() @@ -215,15 +223,25 @@ namespace KeePassLib.Serialization ReadString(xr); // Ignore else if(xr.Name == ElemHeaderHash) { + // The header hash is typically only stored in + // KDBX <= 3.1 files, not in KDBX >= 4 files + // (here, the header is verified via a HMAC), + // but we also support it for KDBX >= 4 files + // (i.e. if it's present, we check it) + string strHash = ReadString(xr); if(!string.IsNullOrEmpty(strHash) && (m_pbHashOfHeader != null) && !m_bRepairMode) { + Debug.Assert(m_uFileVersion <= FileVersion32_3); + byte[] pbHash = Convert.FromBase64String(strHash); if(!MemUtil.ArraysEqual(pbHash, m_pbHashOfHeader)) throw new IOException(KLRes.FileCorrupted); } } + else if(xr.Name == ElemSettingsChanged) + m_pwDatabase.SettingsChanged = ReadTime(xr); else if(xr.Name == ElemDbName) m_pwDatabase.Name = ReadString(xr); else if(xr.Name == ElemDbNameChanged) @@ -383,6 +401,8 @@ namespace KeePassLib.Serialization m_ctxGroup.EnableSearching = StrUtil.StringToBoolEx(ReadString(xr)); else if(xr.Name == ElemLastTopVisibleEntry) m_ctxGroup.LastTopVisibleEntry = ReadUuid(xr); + else if(xr.Name == ElemCustomData) + return SwitchContext(ctx, KdbContext.GroupCustomData, xr); else if(xr.Name == ElemGroup) { m_ctxGroup = new PwGroup(false, false); @@ -403,6 +423,20 @@ namespace KeePassLib.Serialization else ReadUnknown(xr); break; + case KdbContext.GroupCustomData: + if(xr.Name == ElemStringDictExItem) + return SwitchContext(ctx, KdbContext.GroupCustomDataItem, xr); + else ReadUnknown(xr); + break; + + case KdbContext.GroupCustomDataItem: + if(xr.Name == ElemKey) + m_strGroupCustomDataKey = ReadString(xr); + else if(xr.Name == ElemValue) + m_strGroupCustomDataValue = ReadString(xr); + else ReadUnknown(xr); + break; + case KdbContext.Entry: if(xr.Name == ElemUuid) m_ctxEntry.Uuid = ReadUuid(xr); @@ -434,6 +468,8 @@ namespace KeePassLib.Serialization return SwitchContext(ctx, KdbContext.EntryBinary, xr); else if(xr.Name == ElemAutoType) return SwitchContext(ctx, KdbContext.EntryAutoType, xr); + else if(xr.Name == ElemCustomData) + return SwitchContext(ctx, KdbContext.EntryCustomData, xr); else if(xr.Name == ElemHistory) { Debug.Assert(m_bEntryInHistory == false); @@ -508,6 +544,20 @@ namespace KeePassLib.Serialization else ReadUnknown(xr); break; + case KdbContext.EntryCustomData: + if(xr.Name == ElemStringDictExItem) + return SwitchContext(ctx, KdbContext.EntryCustomDataItem, xr); + else ReadUnknown(xr); + break; + + case KdbContext.EntryCustomDataItem: + if(xr.Name == ElemKey) + m_strEntryCustomDataKey = ReadString(xr); + else if(xr.Name == ElemValue) + m_strEntryCustomDataValue = ReadString(xr); + else ReadUnknown(xr); + break; + case KdbContext.EntryHistory: if(xr.Name == ElemEntry) { @@ -609,6 +659,19 @@ namespace KeePassLib.Serialization } else if((ctx == KdbContext.GroupTimes) && (xr.Name == ElemTimes)) return KdbContext.Group; + else if((ctx == KdbContext.GroupCustomData) && (xr.Name == ElemCustomData)) + return KdbContext.Group; + else if((ctx == KdbContext.GroupCustomDataItem) && (xr.Name == ElemStringDictExItem)) + { + if((m_strGroupCustomDataKey != null) && (m_strGroupCustomDataValue != null)) + m_ctxGroup.CustomData.Set(m_strGroupCustomDataKey, m_strGroupCustomDataValue); + else { Debug.Assert(false); } + + m_strGroupCustomDataKey = null; + m_strGroupCustomDataValue = null; + + return KdbContext.GroupCustomData; + } else if((ctx == KdbContext.Entry) && (xr.Name == ElemEntry)) { // Create new UUID if absent @@ -659,6 +722,19 @@ namespace KeePassLib.Serialization m_ctxATSeq = null; return KdbContext.EntryAutoType; } + else if((ctx == KdbContext.EntryCustomData) && (xr.Name == ElemCustomData)) + return KdbContext.Entry; + else if((ctx == KdbContext.EntryCustomDataItem) && (xr.Name == ElemStringDictExItem)) + { + if((m_strEntryCustomDataKey != null) && (m_strEntryCustomDataValue != null)) + m_ctxEntry.CustomData.Set(m_strEntryCustomDataKey, m_strEntryCustomDataValue); + else { Debug.Assert(false); } + + m_strEntryCustomDataKey = null; + m_strEntryCustomDataValue = null; + + return KdbContext.EntryCustomData; + } else if((ctx == KdbContext.EntryHistory) && (xr.Name == ElemHistory)) { m_bEntryInHistory = false; @@ -883,7 +959,7 @@ namespace KeePassLib.Serialization byte[] pbEncrypted; if(strEncrypted.Length > 0) pbEncrypted = Convert.FromBase64String(strEncrypted); - else pbEncrypted = new byte[0]; + else pbEncrypted = MemUtil.EmptyByteArray; byte[] pbPad = m_randomStream.GetRandomBytes((uint)pbEncrypted.Length); diff --git a/src/KeePassLib2Android/Serialization/KdbxFile.Read.cs b/src/KeePassLib2Android/Serialization/KdbxFile.Read.cs index 8d9b4a00..68028bc2 100644 --- a/src/KeePassLib2Android/Serialization/KdbxFile.Read.cs +++ b/src/KeePassLib2Android/Serialization/KdbxFile.Read.cs @@ -35,8 +35,10 @@ using System.IO.Compression; using KeePassLibSD; #endif +using KeePassLib.Collections; using KeePassLib.Cryptography; using KeePassLib.Cryptography.Cipher; +using KeePassLib.Cryptography.KeyDerivation; using KeePassLib.Interfaces; using KeePassLib.Keys; using KeePassLib.Resources; @@ -50,74 +52,108 @@ namespace KeePassLib.Serialization public sealed partial class KdbxFile { /// - /// Load a KDB file from a file. + /// Load a KDBX file. /// /// File to load. - /// Format specifier. + /// Format. /// Status logger (optional). - public void Load(string strFilePath, KdbxFormat kdbFormat, IStatusLogger slLogger) + public void Load(string strFilePath, KdbxFormat fmt, IStatusLogger slLogger) { IOConnectionInfo ioc = IOConnectionInfo.FromPath(strFilePath); - Load(IOConnection.OpenRead(ioc), kdbFormat, slLogger); + Load(IOConnection.OpenRead(ioc), fmt, slLogger); } /// - /// Load a KDB file from a stream. + /// Load a KDBX file from a stream. /// /// Stream to read the data from. Must contain /// a KDBX stream. - /// Format specifier. + /// Format. /// Status logger (optional). - public void Load(Stream sSource, KdbxFormat kdbFormat, IStatusLogger slLogger) + public void Load(Stream sSource, KdbxFormat fmt, IStatusLogger slLogger) { Debug.Assert(sSource != null); if(sSource == null) throw new ArgumentNullException("sSource"); - m_format = kdbFormat; + m_format = fmt; m_slLogger = slLogger; - HashingStreamEx hashedStream = new HashingStreamEx(sSource, false, null); - UTF8Encoding encNoBom = StrUtil.Utf8; + byte[] pbCipherKey = null; + byte[] pbHmacKey64 = null; + + List lStreams = new List(); + lStreams.Add(sSource); + + HashingStreamEx sHashing = new HashingStreamEx(sSource, false, null); + lStreams.Add(sHashing); + try { - BinaryReaderEx br = null; - BinaryReaderEx brDecrypted = null; - Stream readerStream = null; - - if(kdbFormat == KdbxFormat.Default) + Stream sXml; + if(fmt == KdbxFormat.Default) { - br = new BinaryReaderEx(hashedStream, encNoBom, KLRes.FileCorrupted); - ReadHeader(br); + BinaryReaderEx br = new BinaryReaderEx(sHashing, + encNoBom, KLRes.FileCorrupted); + byte[] pbHeader = LoadHeader(br); - Stream sDecrypted = AttachStreamDecryptor(hashedStream); - if((sDecrypted == null) || (sDecrypted == hashedStream)) - throw new SecurityException(KLRes.CryptoStreamFailed); + int cbEncKey, cbEncIV; + ICipherEngine iCipher = GetCipher(out cbEncKey, out cbEncIV); - brDecrypted = new BinaryReaderEx(sDecrypted, encNoBom, KLRes.FileCorrupted); - byte[] pbStoredStartBytes = brDecrypted.ReadBytes(32); + ComputeKeys(out pbCipherKey, cbEncKey, out pbHmacKey64); - if((m_pbStreamStartBytes == null) || (m_pbStreamStartBytes.Length != 32)) - throw new InvalidDataException(); - - for(int iStart = 0; iStart < 32; ++iStart) + Stream sPlain; + if(m_uFileVersion <= FileVersion32_3) { - if(pbStoredStartBytes[iStart] != m_pbStreamStartBytes[iStart]) - throw new InvalidCompositeKeyException(); - } + Stream sDecrypted = EncryptStream(sHashing, iCipher, + pbCipherKey, cbEncIV, false); + if((sDecrypted == null) || (sDecrypted == sHashing)) + throw new SecurityException(KLRes.CryptoStreamFailed); + lStreams.Add(sDecrypted); - Stream sHashed = new HashedBlockStream(sDecrypted, false, 0, - !m_bRepairMode); + BinaryReaderEx brDecrypted = new BinaryReaderEx(sDecrypted, + encNoBom, KLRes.FileCorrupted); + byte[] pbStoredStartBytes = brDecrypted.ReadBytes(32); + + if((m_pbStreamStartBytes == null) || (m_pbStreamStartBytes.Length != 32)) + throw new InvalidDataException(); + if(!MemUtil.ArraysEqual(pbStoredStartBytes, m_pbStreamStartBytes)) + throw new InvalidCompositeKeyException(); + + sPlain = new HashedBlockStream(sDecrypted, false, 0, !m_bRepairMode); + } + else // KDBX >= 4 + { + byte[] pbHeaderHmac = ComputeHeaderHmac(pbHeader, pbHmacKey64); + byte[] pbStoredHmac = MemUtil.Read(sHashing, 32); + if((pbStoredHmac == null) || (pbStoredHmac.Length != 32)) + throw new InvalidDataException(); + if(!MemUtil.ArraysEqual(pbHeaderHmac, pbStoredHmac)) + throw new InvalidCompositeKeyException(); + + HmacBlockStream sBlocks = new HmacBlockStream(sHashing, + false, !m_bRepairMode, pbHmacKey64); + lStreams.Add(sBlocks); + + sPlain = EncryptStream(sBlocks, iCipher, pbCipherKey, + cbEncIV, false); + if((sPlain == null) || (sPlain == sBlocks)) + throw new SecurityException(KLRes.CryptoStreamFailed); + } + lStreams.Add(sPlain); if(m_pwDatabase.Compression == PwCompressionAlgorithm.GZip) - readerStream = new GZipStream(sHashed, CompressionMode.Decompress); - else readerStream = sHashed; + { + sXml = new GZipStream(sPlain, CompressionMode.Decompress); + lStreams.Add(sXml); + } + else sXml = sPlain; } - else if(kdbFormat == KdbxFormat.PlainXml) - readerStream = hashedStream; - else { Debug.Assert(false); throw new FormatException("KdbFormat"); } + else if(fmt == KdbxFormat.PlainXml) + sXml = sHashing; + else { Debug.Assert(false); throw new ArgumentOutOfRangeException("fmt"); } - if(kdbFormat != KdbxFormat.PlainXml) // Is an encrypted format + if(fmt == KdbxFormat.Default) { if(m_pbProtectedStreamKey == null) { @@ -137,7 +173,7 @@ namespace KeePassLib.Serialization // { // while(true) // { - // int b = readerStream.ReadByte(); + // int b = sXml.ReadByte(); // if(b == -1) break; // fsOut.WriteByte((byte)b); // } @@ -146,26 +182,29 @@ namespace KeePassLib.Serialization // fsOut.Close(); #endif - ReadXmlStreamed(readerStream, hashedStream); - // ReadXmlDom(readerStream); - - readerStream.Close(); - // GC.KeepAlive(br); - // GC.KeepAlive(brDecrypted); + ReadXmlStreamed(sXml, sHashing); + // ReadXmlDom(sXml); } catch(CryptographicException) // Thrown on invalid padding { throw new CryptographicException(KLRes.FileCorrupted); } - finally { CommonCleanUpRead(sSource, hashedStream); } + finally + { + if(pbCipherKey != null) MemUtil.ZeroByteArray(pbCipherKey); + if(pbHmacKey64 != null) MemUtil.ZeroByteArray(pbHmacKey64); + + CommonCleanUpRead(lStreams, sHashing); + } } - private void CommonCleanUpRead(Stream sSource, HashingStreamEx hashedStream) + private void CommonCleanUpRead(List lStreams, HashingStreamEx sHashing) { - hashedStream.Close(); - m_pbHashOfFileOnDisk = hashedStream.Hash; + CloseStreams(lStreams); - sSource.Close(); + Debug.Assert(lStreams.Contains(sHashing)); // sHashing must be closed + m_pbHashOfFileOnDisk = sHashing.Hash; + Debug.Assert(m_pbHashOfFileOnDisk != null); // Reset memory protection settings (to always use reasonable // defaults) @@ -187,7 +226,7 @@ namespace KeePassLib.Serialization m_pbHashOfHeader = null; } - private void ReadHeader(BinaryReaderEx br) + private byte[] LoadHeader(BinaryReaderEx br) { MemoryStream msHeader = new MemoryStream(); Debug.Assert(br.CopyDataTo == null); @@ -212,18 +251,19 @@ namespace KeePassLib.Serialization if((uVersion & FileVersionCriticalMask) > (FileVersion32 & FileVersionCriticalMask)) throw new FormatException(KLRes.FileVersionUnsupported + MessageService.NewParagraph + KLRes.FileNewVerReq); + m_uFileVersion = uVersion; while(true) { - if(ReadHeaderField(br) == false) - break; + if(!ReadHeaderField(br)) break; } br.CopyDataTo = null; byte[] pbHeader = msHeader.ToArray(); msHeader.Close(); - SHA256Managed sha256 = new SHA256Managed(); - m_pbHashOfHeader = sha256.ComputeHash(pbHeader); + + m_pbHashOfHeader = CryptoUtil.HashSha256(pbHeader); + return pbHeader; } private bool ReadHeaderField(BinaryReaderEx brSource) @@ -232,15 +272,21 @@ namespace KeePassLib.Serialization if(brSource == null) throw new ArgumentNullException("brSource"); byte btFieldID = brSource.ReadByte(); - ushort uSize = MemUtil.BytesToUInt16(brSource.ReadBytes(2)); - byte[] pbData = null; - if(uSize > 0) + int cbSize; + Debug.Assert(m_uFileVersion > 0); + if(m_uFileVersion <= FileVersion32_3) + cbSize = (int)MemUtil.BytesToUInt16(brSource.ReadBytes(2)); + else cbSize = MemUtil.BytesToInt32(brSource.ReadBytes(4)); + if(cbSize < 0) throw new FormatException(KLRes.FileCorrupted); + + byte[] pbData = MemUtil.EmptyByteArray; + if(cbSize > 0) { string strPrevExcpText = brSource.ReadExceptionText; brSource.ReadExceptionText = KLRes.FileHeaderEndEarly; - pbData = brSource.ReadBytes(uSize); + pbData = brSource.ReadBytes(cbSize); brSource.ReadExceptionText = strPrevExcpText; } @@ -266,13 +312,27 @@ namespace KeePassLib.Serialization CryptoRandom.Instance.AddEntropy(pbData); break; + // Obsolete; for backward compatibility only case KdbxHeaderFieldID.TransformSeed: - m_pbTransformSeed = pbData; + AesKdf kdfS = new AesKdf(); + if(!m_pwDatabase.KdfParameters.KdfUuid.Equals(kdfS.Uuid)) + m_pwDatabase.KdfParameters = kdfS.GetDefaultParameters(); + + // m_pbTransformSeed = pbData; + m_pwDatabase.KdfParameters.SetByteArray(AesKdf.ParamSeed, pbData); + CryptoRandom.Instance.AddEntropy(pbData); break; + // Obsolete; for backward compatibility only case KdbxHeaderFieldID.TransformRounds: - m_pwDatabase.KeyEncryptionRounds = MemUtil.BytesToUInt64(pbData); + AesKdf kdfR = new AesKdf(); + if(!m_pwDatabase.KdfParameters.KdfUuid.Equals(kdfR.Uuid)) + m_pwDatabase.KdfParameters = kdfR.GetDefaultParameters(); + + // m_pwDatabase.KeyEncryptionRounds = MemUtil.BytesToUInt64(pbData); + m_pwDatabase.KdfParameters.SetUInt64(AesKdf.ParamRounds, + MemUtil.BytesToUInt64(pbData)); break; case KdbxHeaderFieldID.EncryptionIV: @@ -285,6 +345,7 @@ namespace KeePassLib.Serialization break; case KdbxHeaderFieldID.StreamStartBytes: + Debug.Assert(m_uFileVersion <= FileVersion32_3); m_pbStreamStartBytes = pbData; break; @@ -292,6 +353,15 @@ namespace KeePassLib.Serialization SetInnerRandomStreamID(pbData); break; + case KdbxHeaderFieldID.KdfParameters: + m_pwDatabase.KdfParameters = KdfParameters.DeserializeExt(pbData); + break; + + case KdbxHeaderFieldID.PublicCustomData: + Debug.Assert(m_pwDatabase.PublicCustomData.Count == 0); + m_pwDatabase.PublicCustomData = VariantDictionary.Deserialize(pbData); + break; + default: Debug.Assert(false); if(m_slLogger != null) @@ -305,7 +375,7 @@ namespace KeePassLib.Serialization private void SetCipher(byte[] pbID) { - if((pbID == null) || (pbID.Length != 16)) + if((pbID == null) || (pbID.Length != (int)PwUuid.UuidSize)) throw new FormatException(KLRes.FileUnknownCipher); m_pwDatabase.DataCipherUuid = new PwUuid(pbID); @@ -329,35 +399,6 @@ namespace KeePassLib.Serialization m_craInnerRandomStream = (CrsAlgorithm)uID; } - private Stream AttachStreamDecryptor(Stream s) - { - MemoryStream ms = new MemoryStream(); - - Debug.Assert(m_pbMasterSeed.Length == 32); - if(m_pbMasterSeed.Length != 32) - throw new FormatException(KLRes.MasterSeedLengthInvalid); - ms.Write(m_pbMasterSeed, 0, 32); - - byte[] pKey32 = m_pwDatabase.MasterKey.GenerateKey32(m_pbTransformSeed, - m_pwDatabase.KeyEncryptionRounds).ReadData(); - if((pKey32 == null) || (pKey32.Length != 32)) - throw new SecurityException(KLRes.InvalidCompositeKey); - ms.Write(pKey32, 0, 32); - - SHA256Managed sha256 = new SHA256Managed(); - byte[] aesKey = sha256.ComputeHash(ms.ToArray()); - - ms.Close(); - Array.Clear(pKey32, 0, 32); - - if((aesKey == null) || (aesKey.Length != 32)) - throw new SecurityException(KLRes.FinalKeyCreationFailed); - - ICipherEngine iEngine = CipherPool.GlobalPool.GetCipher(m_pwDatabase.DataCipherUuid); - if(iEngine == null) throw new SecurityException(KLRes.FileUnknownCipher); - return iEngine.DecryptStream(s, aesKey, m_pbEncryptionIV); - } - [Obsolete] public static List ReadEntries(PwDatabase pwDatabase, Stream msData) { diff --git a/src/KeePassLib2Android/Serialization/KdbxFile.Write.cs b/src/KeePassLib2Android/Serialization/KdbxFile.Write.cs index 27a1853e..ea80fc17 100644 --- a/src/KeePassLib2Android/Serialization/KdbxFile.Write.cs +++ b/src/KeePassLib2Android/Serialization/KdbxFile.Write.cs @@ -40,6 +40,7 @@ using System.IO.Compression; using KeePassLib.Collections; using KeePassLib.Cryptography; using KeePassLib.Cryptography.Cipher; +using KeePassLib.Cryptography.KeyDerivation; using KeePassLib.Delegates; using KeePassLib.Interfaces; using KeePassLib.Keys; @@ -54,7 +55,7 @@ namespace KeePassLib.Serialization /// public sealed partial class KdbxFile { - // public void Save(string strFile, PwGroup pgDataSource, KdbxFormat format, + // public void Save(string strFile, PwGroup pgDataSource, KdbxFormat fmt, // IStatusLogger slLogger) // { // bool bMadeUnhidden = UrlUtil.UnhideFile(strFile); @@ -72,56 +73,117 @@ namespace KeePassLib.Serialization /// Group containing all groups and /// entries to write. If null, the complete database will /// be written. - /// Format of the file to create. + /// Format of the file to create. /// Logger that recieves status information. - public void Save(Stream sSaveTo, PwGroup pgDataSource, KdbxFormat format, + public void Save(Stream sSaveTo, PwGroup pgDataSource, KdbxFormat fmt, IStatusLogger slLogger) { Debug.Assert(sSaveTo != null); if(sSaveTo == null) throw new ArgumentNullException("sSaveTo"); - m_format = format; + m_format = fmt; m_slLogger = slLogger; - HashingStreamEx hashedStream = new HashingStreamEx(sSaveTo, true, null); - UTF8Encoding encNoBom = StrUtil.Utf8; CryptoRandom cr = CryptoRandom.Instance; + byte[] pbCipherKey = null; + byte[] pbHmacKey64 = null; + + List lStreams = new List(); + lStreams.Add(sSaveTo); + + HashingStreamEx sHashing = new HashingStreamEx(sSaveTo, true, null); + lStreams.Add(sHashing); try { - m_pbMasterSeed = cr.GetRandomBytes(32); - m_pbTransformSeed = cr.GetRandomBytes(32); - m_pbEncryptionIV = cr.GetRandomBytes(16); + m_uFileVersion = GetMinKdbxVersion(); - m_pbProtectedStreamKey = cr.GetRandomBytes(32); - m_craInnerRandomStream = CrsAlgorithm.Salsa20; + int cbEncKey, cbEncIV; + ICipherEngine iCipher = GetCipher(out cbEncKey, out cbEncIV); + + m_pbMasterSeed = cr.GetRandomBytes(32); + m_pbEncryptionIV = cr.GetRandomBytes((uint)cbEncIV); + + // m_pbTransformSeed = cr.GetRandomBytes(32); + PwUuid puKdf = m_pwDatabase.KdfParameters.KdfUuid; + KdfEngine kdf = KdfPool.Get(puKdf); + if(kdf == null) + throw new Exception(KLRes.UnknownKdf + MessageService.NewParagraph + + // KLRes.FileNewVerOrPlgReq + MessageService.NewParagraph + + "UUID: " + puKdf.ToHexString() + "."); + kdf.Randomize(m_pwDatabase.KdfParameters); + + if(m_uFileVersion <= FileVersion32_3) + { + m_craInnerRandomStream = CrsAlgorithm.Salsa20; + m_pbProtectedStreamKey = cr.GetRandomBytes(32); + } + else // KDBX >= 4 + { + m_craInnerRandomStream = CrsAlgorithm.ChaCha20; + m_pbProtectedStreamKey = cr.GetRandomBytes(64); + } m_randomStream = new CryptoRandomStream(m_craInnerRandomStream, m_pbProtectedStreamKey); - m_pbStreamStartBytes = cr.GetRandomBytes(32); + if(m_uFileVersion <= FileVersion32_3) + m_pbStreamStartBytes = cr.GetRandomBytes(32); - Stream writerStream; + Stream sXml; if(m_format == KdbxFormat.Default) { - WriteHeader(hashedStream); // Also flushes the stream + byte[] pbHeader = GenerateHeader(); + m_pbHashOfHeader = CryptoUtil.HashSha256(pbHeader); - Stream sEncrypted = AttachStreamEncryptor(hashedStream); - if((sEncrypted == null) || (sEncrypted == hashedStream)) - throw new SecurityException(KLRes.CryptoStreamFailed); + MemUtil.Write(sHashing, pbHeader); + sHashing.Flush(); - sEncrypted.Write(m_pbStreamStartBytes, 0, m_pbStreamStartBytes.Length); + ComputeKeys(out pbCipherKey, cbEncKey, out pbHmacKey64); - Stream sHashed = new HashedBlockStream(sEncrypted, true); + Stream sPlain; + if(m_uFileVersion <= FileVersion32_3) + { + Stream sEncrypted = EncryptStream(sHashing, iCipher, + pbCipherKey, cbEncIV, true); + if((sEncrypted == null) || (sEncrypted == sHashing)) + throw new SecurityException(KLRes.CryptoStreamFailed); + lStreams.Add(sEncrypted); + + MemUtil.Write(sEncrypted, m_pbStreamStartBytes); + + sPlain = new HashedBlockStream(sEncrypted, true); + } + else // KDBX >= 4 + { + byte[] pbHeaderHmac = ComputeHeaderHmac(pbHeader, pbHmacKey64); + MemUtil.Write(sHashing, pbHeaderHmac); + + Stream sBlocks = new HmacBlockStream(sHashing, true, + true, pbHmacKey64); + lStreams.Add(sBlocks); + + sPlain = EncryptStream(sBlocks, iCipher, pbCipherKey, + cbEncIV, true); + if((sPlain == null) || (sPlain == sBlocks)) + throw new SecurityException(KLRes.CryptoStreamFailed); + } + lStreams.Add(sPlain); if(m_pwDatabase.Compression == PwCompressionAlgorithm.GZip) - writerStream = new GZipStream(sHashed, CompressionMode.Compress); - else - writerStream = sHashed; + { + sXml = new GZipStream(sPlain, CompressionMode.Compress); + lStreams.Add(sXml); + } + else sXml = sPlain; } else if(m_format == KdbxFormat.PlainXml) - writerStream = hashedStream; - else { Debug.Assert(false); throw new FormatException("KdbFormat"); } + sXml = sHashing; + else + { + Debug.Assert(false); + throw new ArgumentOutOfRangeException("fmt"); + } #if KeePassUAP XmlWriterSettings xws = new XmlWriterSettings(); @@ -130,122 +192,125 @@ namespace KeePassLib.Serialization xws.IndentChars = "\t"; xws.NewLineOnAttributes = false; - XmlWriter xw = XmlWriter.Create(writerStream, xws); + XmlWriter xw = XmlWriter.Create(sXml, xws); #else - XmlTextWriter xw = new XmlTextWriter(writerStream, encNoBom); + XmlTextWriter xw = new XmlTextWriter(sXml, encNoBom); xw.Formatting = Formatting.Indented; xw.IndentChar = '\t'; xw.Indentation = 1; #endif - m_xmlWriter = xw; WriteDocument(pgDataSource); m_xmlWriter.Flush(); m_xmlWriter.Close(); - writerStream.Close(); } - finally { CommonCleanUpWrite(sSaveTo, hashedStream); } + finally + { + if(pbCipherKey != null) MemUtil.ZeroByteArray(pbCipherKey); + if(pbHmacKey64 != null) MemUtil.ZeroByteArray(pbHmacKey64); + + CommonCleanUpWrite(lStreams, sHashing); + } } - private void CommonCleanUpWrite(Stream sSaveTo, HashingStreamEx hashedStream) + private void CommonCleanUpWrite(List lStreams, HashingStreamEx sHashing) { - hashedStream.Close(); - m_pbHashOfFileOnDisk = hashedStream.Hash; + CloseStreams(lStreams); - sSaveTo.Close(); + Debug.Assert(lStreams.Contains(sHashing)); // sHashing must be closed + m_pbHashOfFileOnDisk = sHashing.Hash; + Debug.Assert(m_pbHashOfFileOnDisk != null); m_xmlWriter = null; m_pbHashOfHeader = null; } - private void WriteHeader(Stream s) + private byte[] GenerateHeader() { - MemoryStream ms = new MemoryStream(); + byte[] pbHeader; + using(MemoryStream ms = new MemoryStream()) + { + MemUtil.Write(ms, MemUtil.UInt32ToBytes(FileSignature1)); + MemUtil.Write(ms, MemUtil.UInt32ToBytes(FileSignature2)); + MemUtil.Write(ms, MemUtil.UInt32ToBytes(m_uFileVersion)); - MemUtil.Write(ms, MemUtil.UInt32ToBytes(FileSignature1)); - MemUtil.Write(ms, MemUtil.UInt32ToBytes(FileSignature2)); - MemUtil.Write(ms, MemUtil.UInt32ToBytes(FileVersion32)); + WriteHeaderField(ms, KdbxHeaderFieldID.CipherID, + m_pwDatabase.DataCipherUuid.UuidBytes); - WriteHeaderField(ms, KdbxHeaderFieldID.CipherID, - m_pwDatabase.DataCipherUuid.UuidBytes); + int nCprID = (int)m_pwDatabase.Compression; + WriteHeaderField(ms, KdbxHeaderFieldID.CompressionFlags, + MemUtil.UInt32ToBytes((uint)nCprID)); - int nCprID = (int)m_pwDatabase.Compression; - WriteHeaderField(ms, KdbxHeaderFieldID.CompressionFlags, - MemUtil.UInt32ToBytes((uint)nCprID)); + WriteHeaderField(ms, KdbxHeaderFieldID.MasterSeed, m_pbMasterSeed); - WriteHeaderField(ms, KdbxHeaderFieldID.MasterSeed, m_pbMasterSeed); - WriteHeaderField(ms, KdbxHeaderFieldID.TransformSeed, m_pbTransformSeed); - WriteHeaderField(ms, KdbxHeaderFieldID.TransformRounds, - MemUtil.UInt64ToBytes(m_pwDatabase.KeyEncryptionRounds)); - WriteHeaderField(ms, KdbxHeaderFieldID.EncryptionIV, m_pbEncryptionIV); - WriteHeaderField(ms, KdbxHeaderFieldID.ProtectedStreamKey, m_pbProtectedStreamKey); - WriteHeaderField(ms, KdbxHeaderFieldID.StreamStartBytes, m_pbStreamStartBytes); + if(m_uFileVersion <= FileVersion32_3) + { + Debug.Assert(m_pwDatabase.KdfParameters.KdfUuid.Equals( + (new AesKdf()).Uuid)); + WriteHeaderField(ms, KdbxHeaderFieldID.TransformSeed, + m_pwDatabase.KdfParameters.GetByteArray(AesKdf.ParamSeed)); + WriteHeaderField(ms, KdbxHeaderFieldID.TransformRounds, + MemUtil.UInt64ToBytes(m_pwDatabase.KdfParameters.GetUInt64( + AesKdf.ParamRounds, PwDefs.DefaultKeyEncryptionRounds))); + } + else + WriteHeaderField(ms, KdbxHeaderFieldID.KdfParameters, + KdfParameters.SerializeExt(m_pwDatabase.KdfParameters)); - int nIrsID = (int)m_craInnerRandomStream; - WriteHeaderField(ms, KdbxHeaderFieldID.InnerRandomStreamID, - MemUtil.UInt32ToBytes((uint)nIrsID)); + if(m_pbEncryptionIV.Length > 0) + WriteHeaderField(ms, KdbxHeaderFieldID.EncryptionIV, m_pbEncryptionIV); - WriteHeaderField(ms, KdbxHeaderFieldID.EndOfHeader, new byte[]{ - (byte)'\r', (byte)'\n', (byte)'\r', (byte)'\n' }); + WriteHeaderField(ms, KdbxHeaderFieldID.ProtectedStreamKey, m_pbProtectedStreamKey); - byte[] pbHeader = ms.ToArray(); - ms.Close(); + if(m_uFileVersion <= FileVersion32_3) + WriteHeaderField(ms, KdbxHeaderFieldID.StreamStartBytes, + m_pbStreamStartBytes); - SHA256Managed sha256 = new SHA256Managed(); - m_pbHashOfHeader = sha256.ComputeHash(pbHeader); + int nIrsID = (int)m_craInnerRandomStream; + WriteHeaderField(ms, KdbxHeaderFieldID.InnerRandomStreamID, + MemUtil.Int32ToBytes(nIrsID)); - s.Write(pbHeader, 0, pbHeader.Length); - s.Flush(); + // Write public custom data only when there is at least one item, + // because KDBX 3.1 didn't support this field yet + if(m_pwDatabase.PublicCustomData.Count > 0) + WriteHeaderField(ms, KdbxHeaderFieldID.PublicCustomData, + VariantDictionary.Serialize(m_pwDatabase.PublicCustomData)); + + WriteHeaderField(ms, KdbxHeaderFieldID.EndOfHeader, new byte[] { + (byte)'\r', (byte)'\n', (byte)'\r', (byte)'\n' }); + + pbHeader = ms.ToArray(); + } + + return pbHeader; } - private static void WriteHeaderField(Stream s, KdbxHeaderFieldID kdbID, + private void WriteHeaderField(Stream s, KdbxHeaderFieldID kdbID, byte[] pbData) { s.WriteByte((byte)kdbID); - if(pbData != null) + byte[] pb = (pbData ?? MemUtil.EmptyByteArray); + int cb = pb.Length; + if(cb < 0) { Debug.Assert(false); throw new OutOfMemoryException(); } + + Debug.Assert(m_uFileVersion > 0); + if(m_uFileVersion <= FileVersion32_3) { - ushort uLength = (ushort)pbData.Length; - MemUtil.Write(s, MemUtil.UInt16ToBytes(uLength)); + if(cb > (int)ushort.MaxValue) + { + Debug.Assert(false); + throw new ArgumentOutOfRangeException("pbData"); + } - if(uLength > 0) s.Write(pbData, 0, pbData.Length); + MemUtil.Write(s, MemUtil.UInt16ToBytes((ushort)cb)); } - else MemUtil.Write(s, MemUtil.UInt16ToBytes((ushort)0)); - } + else MemUtil.Write(s, MemUtil.Int32ToBytes(cb)); - private Stream AttachStreamEncryptor(Stream s) - { - MemoryStream ms = new MemoryStream(); - - Debug.Assert(m_pbMasterSeed != null); - Debug.Assert(m_pbMasterSeed.Length == 32); - ms.Write(m_pbMasterSeed, 0, 32); - - Debug.Assert(m_pwDatabase != null); - Debug.Assert(m_pwDatabase.MasterKey != null); - ProtectedBinary pbinKey = m_pwDatabase.MasterKey.GenerateKey32( - m_pbTransformSeed, m_pwDatabase.KeyEncryptionRounds); - Debug.Assert(pbinKey != null); - if(pbinKey == null) - throw new SecurityException(KLRes.InvalidCompositeKey); - byte[] pKey32 = pbinKey.ReadData(); - if((pKey32 == null) || (pKey32.Length != 32)) - throw new SecurityException(KLRes.InvalidCompositeKey); - ms.Write(pKey32, 0, 32); - - SHA256Managed sha256 = new SHA256Managed(); - byte[] aesKey = sha256.ComputeHash(ms.ToArray()); - - ms.Close(); - Array.Clear(pKey32, 0, 32); - - Debug.Assert(CipherPool.GlobalPool != null); - ICipherEngine iEngine = CipherPool.GlobalPool.GetCipher(m_pwDatabase.DataCipherUuid); - if(iEngine == null) throw new SecurityException(KLRes.FileUnknownCipher); - return iEngine.EncryptStream(s, aesKey, m_pbEncryptionIV); + if(cb > 0) s.Write(pb, 0, cb); } private void WriteDocument(PwGroup pgDataSource) @@ -331,12 +396,15 @@ namespace KeePassLib.Serialization { m_xmlWriter.WriteStartElement(ElemMeta); - WriteObject(ElemGenerator, PwDatabase.LocalizedAppName, false); // Generator name + WriteObject(ElemGenerator, PwDatabase.LocalizedAppName, false); - if(m_pbHashOfHeader != null) + if((m_pbHashOfHeader != null) && (m_uFileVersion <= FileVersion32_3)) WriteObject(ElemHeaderHash, Convert.ToBase64String( m_pbHashOfHeader), false); + if(m_uFileVersion > FileVersion32_3) + WriteObject(ElemSettingsChanged, m_pwDatabase.SettingsChanged); + WriteObject(ElemDbName, m_pwDatabase.Name, true); WriteObject(ElemDbNameChanged, m_pwDatabase.NameChanged); WriteObject(ElemDbDesc, m_pwDatabase.Description, true); @@ -387,6 +455,9 @@ namespace KeePassLib.Serialization WriteObject(ElemEnableAutoType, StrUtil.BoolToStringEx(pg.EnableAutoType), false); WriteObject(ElemEnableSearching, StrUtil.BoolToStringEx(pg.EnableSearching), false); WriteObject(ElemLastTopVisibleEntry, pg.LastTopVisibleEntry); + + if(pg.CustomData.Count > 0) + WriteList(ElemCustomData, pg.CustomData); } private void EndGroup() @@ -402,7 +473,7 @@ namespace KeePassLib.Serialization WriteObject(ElemUuid, pe.Uuid); WriteObject(ElemIcon, (int)pe.IconId); - + if(!pe.CustomIconUuid.Equals(PwUuid.Zero)) WriteObject(ElemCustomIconID, pe.CustomIconUuid); @@ -417,6 +488,9 @@ namespace KeePassLib.Serialization WriteList(pe.Binaries); WriteList(ElemAutoType, pe.AutoType); + if(pe.CustomData.Count > 0) + WriteList(ElemCustomData, pe.CustomData); + if(!bIsHistory) WriteList(ElemHistory, pe.History, true); else { Debug.Assert(pe.History.UCount == 0); } diff --git a/src/KeePassLib2Android/Serialization/KdbxFile.cs b/src/KeePassLib2Android/Serialization/KdbxFile.cs index 2ca29c95..d3d2aaa9 100644 --- a/src/KeePassLib2Android/Serialization/KdbxFile.cs +++ b/src/KeePassLib2Android/Serialization/KdbxFile.cs @@ -22,13 +22,21 @@ using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; +using System.Security; using System.Text; using System.Xml; +#if !KeePassUAP +using System.Security.Cryptography; +#endif + using KeePassLib.Collections; using KeePassLib.Cryptography; +using KeePassLib.Cryptography.Cipher; +using KeePassLib.Cryptography.KeyDerivation; using KeePassLib.Delegates; using KeePassLib.Interfaces; +using KeePassLib.Resources; using KeePassLib.Security; using KeePassLib.Utility; @@ -73,7 +81,8 @@ namespace KeePassLib.Serialization /// The first 2 bytes are critical (i.e. loading will fail, if the /// file version is too high), the last 2 bytes are informational. /// - private const uint FileVersion32 = 0x00030001; + private const uint FileVersion32 = 0x00040000; + private const uint FileVersion32_3 = 0x00030001; // Old format private const uint FileVersionCriticalMask = 0xFFFF0000; @@ -92,6 +101,7 @@ namespace KeePassLib.Serialization private const string ElemGenerator = "Generator"; private const string ElemHeaderHash = "HeaderHash"; + private const string ElemSettingsChanged = "SettingsChanged"; private const string ElemDbName = "DatabaseName"; private const string ElemDbNameChanged = "DatabaseNameChanged"; private const string ElemDbDesc = "DatabaseDescription"; @@ -192,14 +202,15 @@ namespace KeePassLib.Serialization private KdbxFormat m_format = KdbxFormat.Default; private IStatusLogger m_slLogger = null; + private uint m_uFileVersion = 0; private byte[] m_pbMasterSeed = null; - private byte[] m_pbTransformSeed = null; + // private byte[] m_pbTransformSeed = null; private byte[] m_pbEncryptionIV = null; private byte[] m_pbProtectedStreamKey = null; private byte[] m_pbStreamStartBytes = null; - // ArcFourVariant only for compatibility; KeePass will default to a - // different (more secure) algorithm when *writing* databases + // ArcFourVariant only for backward compatibility; KeePass defaults + // to a more secure algorithm when *writing* databases private CrsAlgorithm m_craInnerRandomStream = CrsAlgorithm.ArcFourVariant; private Dictionary m_dictBinPool = @@ -222,12 +233,14 @@ namespace KeePassLib.Serialization CipherID = 2, CompressionFlags = 3, MasterSeed = 4, - TransformSeed = 5, - TransformRounds = 6, + TransformSeed = 5, // KDBX 3.1, for backward compatibility only + TransformRounds = 6, // KDBX 3.1, for backward compatibility only EncryptionIV = 7, ProtectedStreamKey = 8, - StreamStartBytes = 9, - InnerRandomStreamID = 10 + StreamStartBytes = 9, // KDBX 3.1, for backward compatibility only + InnerRandomStreamID = 10, + KdfParameters = 11, // KDBX 4 + PublicCustomData = 12 // KDBX 4 } public byte[] HashOfFileOnDisk @@ -286,6 +299,152 @@ namespace KeePassLib.Serialization } } + private uint GetMinKdbxVersion() + { + AesKdf kdfAes = new AesKdf(); + if(!kdfAes.Uuid.Equals(m_pwDatabase.KdfParameters.KdfUuid)) + return FileVersion32; + + if(m_pwDatabase.PublicCustomData.Count > 0) + return FileVersion32; + + bool bCustomData = false; + GroupHandler gh = delegate(PwGroup pg) + { + if(pg == null) { Debug.Assert(false); return true; } + if(pg.CustomData.Count > 0) { bCustomData = true; return false; } + return true; + }; + EntryHandler eh = delegate(PwEntry pe) + { + if(pe == null) { Debug.Assert(false); return true; } + if(pe.CustomData.Count > 0) { bCustomData = true; return false; } + return true; + }; + gh(m_pwDatabase.RootGroup); + m_pwDatabase.RootGroup.TraverseTree(TraversalMethod.PreOrder, gh, eh); + if(bCustomData) return FileVersion32; + + return FileVersion32_3; // KDBX 3.1 is sufficient + } + + private void ComputeKeys(out byte[] pbCipherKey, int cbCipherKey, + out byte[] pbHmacKey64) + { + byte[] pbCmp = new byte[32 + 32 + 1]; + try + { + Debug.Assert(m_pbMasterSeed != null); + if(m_pbMasterSeed == null) + throw new ArgumentNullException("m_pbMasterSeed"); + Debug.Assert(m_pbMasterSeed.Length == 32); + if(m_pbMasterSeed.Length != 32) + throw new FormatException(KLRes.MasterSeedLengthInvalid); + Array.Copy(m_pbMasterSeed, 0, pbCmp, 0, 32); + + Debug.Assert(m_pwDatabase != null); + Debug.Assert(m_pwDatabase.MasterKey != null); + ProtectedBinary pbinUser = m_pwDatabase.MasterKey.GenerateKey32( + m_pwDatabase.KdfParameters); + Debug.Assert(pbinUser != null); + if(pbinUser == null) + throw new SecurityException(KLRes.InvalidCompositeKey); + byte[] pUserKey32 = pbinUser.ReadData(); + if((pUserKey32 == null) || (pUserKey32.Length != 32)) + throw new SecurityException(KLRes.InvalidCompositeKey); + Array.Copy(pUserKey32, 0, pbCmp, 32, 32); + MemUtil.ZeroByteArray(pUserKey32); + + pbCipherKey = CryptoUtil.ResizeKey(pbCmp, 0, 64, cbCipherKey); + + pbCmp[64] = 1; + using(SHA512Managed h = new SHA512Managed()) + { + pbHmacKey64 = h.ComputeHash(pbCmp); + } + } + finally { MemUtil.ZeroByteArray(pbCmp); } + } + + private ICipherEngine GetCipher(out int cbEncKey, out int cbEncIV) + { + PwUuid pu = m_pwDatabase.DataCipherUuid; + ICipherEngine iCipher = CipherPool.GlobalPool.GetCipher(pu); + if(iCipher == null) // CryptographicExceptions are translated to "file corrupted" + throw new Exception(KLRes.FileUnknownCipher + + MessageService.NewParagraph + KLRes.FileNewVerOrPlgReq + + MessageService.NewParagraph + "UUID: " + pu.ToHexString() + "."); + + ICipherEngine2 iCipher2 = (iCipher as ICipherEngine2); + if(iCipher2 != null) + { + cbEncKey = iCipher2.KeyLength; + if(cbEncKey < 0) throw new InvalidOperationException("EncKey.Length"); + + cbEncIV = iCipher2.IVLength; + if(cbEncIV < 0) throw new InvalidOperationException("EncIV.Length"); + } + else + { + cbEncKey = 32; + cbEncIV = 16; + } + + return iCipher; + } + + private Stream EncryptStream(Stream s, ICipherEngine iCipher, + byte[] pbKey, int cbIV, bool bEncrypt) + { + byte[] pbIV = (m_pbEncryptionIV ?? MemUtil.EmptyByteArray); + if(pbIV.Length != cbIV) + { + Debug.Assert(false); + throw new Exception(KLRes.FileCorrupted); + } + + if(bEncrypt) + return iCipher.EncryptStream(s, pbKey, pbIV); + return iCipher.DecryptStream(s, pbKey, pbIV); + } + + private byte[] ComputeHeaderHmac(byte[] pbHeader, byte[] pbKey) + { + byte[] pbHeaderHmac; + byte[] pbBlockKey = HmacBlockStream.GetHmacKey64( + pbKey, ulong.MaxValue); + using(HMACSHA256 h = new HMACSHA256(pbBlockKey)) + { + pbHeaderHmac = h.ComputeHash(pbHeader); + } + MemUtil.ZeroByteArray(pbBlockKey); + + return pbHeaderHmac; + } + + private void CloseStreams(List lStreams) + { + if(lStreams == null) { Debug.Assert(false); return; } + + // Typically, closing a stream also closes its base + // stream; however, there may be streams that do not + // do this (e.g. some cipher plugin), thus for safety + // we close all streams manually, from the innermost + // to the outermost + + for(int i = lStreams.Count - 1; i >= 0; --i) + { + // Check for duplicates + Debug.Assert((lStreams.IndexOf(lStreams[i]) == i) && + (lStreams.LastIndexOf(lStreams[i]) == i)); + + try { lStreams[i].Close(); } + catch(Exception) { Debug.Assert(false); } + } + + // Do not clear the list + } + private void BinPoolBuild(PwGroup pgDataSource) { m_dictBinPool = new Dictionary(); diff --git a/src/KeePassLib2Android/Translation/KPControlCustomization.cs b/src/KeePassLib2Android/Translation/KPControlCustomization.cs index f71d3213..45e4e244 100644 --- a/src/KeePassLib2Android/Translation/KPControlCustomization.cs +++ b/src/KeePassLib2Android/Translation/KPControlCustomization.cs @@ -27,10 +27,10 @@ using System.Xml.Serialization; #if !KeePassUAP using System.Drawing; -using System.Security.Cryptography; using System.Windows.Forms; #endif +using KeePassLib.Cryptography; using KeePassLib.Utility; namespace KeePassLib.Translation @@ -333,9 +333,7 @@ namespace KeePassLib.Translation WriteControlDependentParams(sb, c); byte[] pb = StrUtil.Utf8.GetBytes(sb.ToString()); - - SHA256Managed sha256 = new SHA256Managed(); - byte[] pbSha = sha256.ComputeHash(pb); + byte[] pbSha = CryptoUtil.HashSha256(pb); // Also see MatchHash return "v1:" + Convert.ToBase64String(pbSha, 0, 3, diff --git a/src/KeePassLib2Android/Utility/GfxUtil.cs b/src/KeePassLib2Android/Utility/GfxUtil.cs index 2a61e370..f61fe9bd 100644 --- a/src/KeePassLib2Android/Utility/GfxUtil.cs +++ b/src/KeePassLib2Android/Utility/GfxUtil.cs @@ -214,14 +214,11 @@ namespace KeePassLib.Utility const int SizeICONDIR = 6; const int SizeICONDIRENTRY = 16; - Debug.Assert(BitConverter.ToInt32(new byte[] { 1, 2, 3, 4 }, - 0) == 0x04030201); // Little-endian - if(pb.Length < SizeICONDIR) return null; - if(BitConverter.ToUInt16(pb, 0) != 0) return null; // Reserved, 0 - if(BitConverter.ToUInt16(pb, 2) != 1) return null; // ICO type, 1 + if(MemUtil.BytesToUInt16(pb, 0) != 0) return null; // Reserved, 0 + if(MemUtil.BytesToUInt16(pb, 2) != 1) return null; // ICO type, 1 - int n = BitConverter.ToUInt16(pb, 4); + int n = MemUtil.BytesToUInt16(pb, 4); if(n < 0) { Debug.Assert(false); return null; } int cbDir = SizeICONDIR + (n * SizeICONDIRENTRY); @@ -235,10 +232,10 @@ namespace KeePassLib.Utility int h = pb[iOffset + 1]; if((w < 0) || (h < 0)) { Debug.Assert(false); return null; } - int cb = BitConverter.ToInt32(pb, iOffset + 8); + int cb = MemUtil.BytesToInt32(pb, iOffset + 8); if(cb <= 0) return null; // Data must have header (even BMP) - int p = BitConverter.ToInt32(pb, iOffset + 12); + int p = MemUtil.BytesToInt32(pb, iOffset + 12); if(p < cbDir) return null; if((p + cb) > pb.Length) return null; diff --git a/src/KeePassLib2Android/Utility/MemUtil.cs b/src/KeePassLib2Android/Utility/MemUtil.cs index 6b22b720..964e25a4 100644 --- a/src/KeePassLib2Android/Utility/MemUtil.cs +++ b/src/KeePassLib2Android/Utility/MemUtil.cs @@ -37,6 +37,8 @@ namespace KeePassLib.Utility /// public static class MemUtil { + internal static readonly byte[] EmptyByteArray = new byte[0]; + private static readonly uint[] m_vSBox = new uint[256] { 0xCD2FACB3, 0xE78A7F5C, 0x6F0803FC, 0xBCF6E230, 0x3A321712, 0x06403DB1, 0xD2F84B95, 0xDF22A6E4, @@ -262,6 +264,22 @@ namespace KeePassLib.Utility Array.Clear(pbArray, 0, pbArray.Length); } + /// + /// Set all elements of an array to the default value. + /// + /// Input array. +#if KeePassLibSD + [MethodImpl(MethodImplOptions.NoInlining)] +#else + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] +#endif + public static void ZeroArray(T[] v) + { + if(v == null) { Debug.Assert(false); throw new ArgumentNullException("v"); } + + Array.Clear(v, 0, v.Length); + } + /// /// Convert 2 bytes to a 16-bit unsigned integer (little-endian). /// @@ -269,11 +287,26 @@ namespace KeePassLib.Utility { Debug.Assert((pb != null) && (pb.Length == 2)); if(pb == null) throw new ArgumentNullException("pb"); - if(pb.Length != 2) throw new ArgumentException(); + if(pb.Length != 2) throw new ArgumentOutOfRangeException("pb"); return (ushort)((ushort)pb[0] | ((ushort)pb[1] << 8)); } + /// + /// Convert 2 bytes to a 16-bit unsigned integer (little-endian). + /// + public static ushort BytesToUInt16(byte[] pb, int iOffset) + { + if(pb == null) { Debug.Assert(false); throw new ArgumentNullException("pb"); } + if((iOffset < 0) || ((iOffset + 1) >= pb.Length)) + { + Debug.Assert(false); + throw new ArgumentOutOfRangeException("iOffset"); + } + + return (ushort)((ushort)pb[iOffset] | ((ushort)pb[iOffset + 1] << 8)); + } + /// /// Convert 4 bytes to a 32-bit unsigned integer (little-endian). /// @@ -281,12 +314,28 @@ namespace KeePassLib.Utility { Debug.Assert((pb != null) && (pb.Length == 4)); if(pb == null) throw new ArgumentNullException("pb"); - if(pb.Length != 4) throw new ArgumentException(); + if(pb.Length != 4) throw new ArgumentOutOfRangeException("pb"); return ((uint)pb[0] | ((uint)pb[1] << 8) | ((uint)pb[2] << 16) | ((uint)pb[3] << 24)); } + /// + /// Convert 4 bytes to a 32-bit unsigned integer (little-endian). + /// + public static uint BytesToUInt32(byte[] pb, int iOffset) + { + if(pb == null) { Debug.Assert(false); throw new ArgumentNullException("pb"); } + if((iOffset < 0) || ((iOffset + 3) >= pb.Length)) + { + Debug.Assert(false); + throw new ArgumentOutOfRangeException("iOffset"); + } + + return ((uint)pb[iOffset] | ((uint)pb[iOffset + 1] << 8) | + ((uint)pb[iOffset + 2] << 16) | ((uint)pb[iOffset + 3] << 24)); + } + /// /// Convert 8 bytes to a 64-bit unsigned integer (little-endian). /// @@ -294,13 +343,54 @@ namespace KeePassLib.Utility { Debug.Assert((pb != null) && (pb.Length == 8)); if(pb == null) throw new ArgumentNullException("pb"); - if(pb.Length != 8) throw new ArgumentException(); + if(pb.Length != 8) throw new ArgumentOutOfRangeException("pb"); return ((ulong)pb[0] | ((ulong)pb[1] << 8) | ((ulong)pb[2] << 16) | ((ulong)pb[3] << 24) | ((ulong)pb[4] << 32) | ((ulong)pb[5] << 40) | ((ulong)pb[6] << 48) | ((ulong)pb[7] << 56)); } + /// + /// Convert 8 bytes to a 64-bit unsigned integer (little-endian). + /// + public static ulong BytesToUInt64(byte[] pb, int iOffset) + { + if(pb == null) { Debug.Assert(false); throw new ArgumentNullException("pb"); } + if((iOffset < 0) || ((iOffset + 7) >= pb.Length)) + { + Debug.Assert(false); + throw new ArgumentOutOfRangeException("iOffset"); + } + + // if(BitConverter.IsLittleEndian) + // return BitConverter.ToUInt64(pb, iOffset); + + return ((ulong)pb[iOffset] | ((ulong)pb[iOffset + 1] << 8) | + ((ulong)pb[iOffset + 2] << 16) | ((ulong)pb[iOffset + 3] << 24) | + ((ulong)pb[iOffset + 4] << 32) | ((ulong)pb[iOffset + 5] << 40) | + ((ulong)pb[iOffset + 6] << 48) | ((ulong)pb[iOffset + 7] << 56)); + } + + public static int BytesToInt32(byte[] pb) + { + return (int)BytesToUInt32(pb); + } + + public static int BytesToInt32(byte[] pb, int iOffset) + { + return (int)BytesToUInt32(pb, iOffset); + } + + public static long BytesToInt64(byte[] pb) + { + return (long)BytesToUInt64(pb); + } + + public static long BytesToInt64(byte[] pb, int iOffset) + { + return (long)BytesToUInt64(pb, iOffset); + } + /// /// Convert a 16-bit unsigned integer to 2 bytes (little-endian). /// @@ -335,6 +425,27 @@ namespace KeePassLib.Utility return pb; } + /// + /// Convert a 32-bit unsigned integer to 4 bytes (little-endian). + /// + public static void UInt32ToBytesEx(uint uValue, byte[] pb, int iOffset) + { + if(pb == null) { Debug.Assert(false); throw new ArgumentNullException("pb"); } + if((iOffset < 0) || ((iOffset + 3) >= pb.Length)) + { + Debug.Assert(false); + throw new ArgumentOutOfRangeException("iOffset"); + } + + unchecked + { + pb[iOffset] = (byte)uValue; + pb[iOffset + 1] = (byte)(uValue >> 8); + pb[iOffset + 2] = (byte)(uValue >> 16); + pb[iOffset + 3] = (byte)(uValue >> 24); + } + } + /// /// Convert a 64-bit unsigned integer to 8 bytes (little-endian). /// @@ -357,6 +468,61 @@ namespace KeePassLib.Utility return pb; } + /// + /// Convert a 64-bit unsigned integer to 8 bytes (little-endian). + /// + public static void UInt64ToBytesEx(ulong uValue, byte[] pb, int iOffset) + { + if(pb == null) { Debug.Assert(false); throw new ArgumentNullException("pb"); } + if((iOffset < 0) || ((iOffset + 7) >= pb.Length)) + { + Debug.Assert(false); + throw new ArgumentOutOfRangeException("iOffset"); + } + + unchecked + { + pb[iOffset] = (byte)uValue; + pb[iOffset + 1] = (byte)(uValue >> 8); + pb[iOffset + 2] = (byte)(uValue >> 16); + pb[iOffset + 3] = (byte)(uValue >> 24); + pb[iOffset + 4] = (byte)(uValue >> 32); + pb[iOffset + 5] = (byte)(uValue >> 40); + pb[iOffset + 6] = (byte)(uValue >> 48); + pb[iOffset + 7] = (byte)(uValue >> 56); + } + } + + public static byte[] Int32ToBytes(int iValue) + { + return UInt32ToBytes((uint)iValue); + } + + public static byte[] Int64ToBytes(long lValue) + { + return UInt64ToBytes((ulong)lValue); + } + + public static uint RotateLeft32(uint u, int nBits) + { + return ((u << nBits) | (u >> (32 - nBits))); + } + + public static uint RotateRight32(uint u, int nBits) + { + return ((u >> nBits) | (u << (32 - nBits))); + } + + public static ulong RotateLeft64(ulong u, int nBits) + { + return ((u << nBits) | (u >> (64 - nBits))); + } + + public static ulong RotateRight64(ulong u, int nBits) + { + return ((u >> nBits) | (u << (64 - nBits))); + } + public static bool ArraysEqual(byte[] x, byte[] y) { // Return false if one of them is null (not comparable)! @@ -372,19 +538,21 @@ namespace KeePassLib.Utility return true; } - public static void XorArray(byte[] pbSource, int nSourceOffset, - byte[] pbBuffer, int nBufferOffset, int nLength) + public static void XorArray(byte[] pbSource, int iSourceOffset, + byte[] pbBuffer, int iBufferOffset, int cb) { if(pbSource == null) throw new ArgumentNullException("pbSource"); - if(nSourceOffset < 0) throw new ArgumentException(); + if(iSourceOffset < 0) throw new ArgumentOutOfRangeException("iSourceOffset"); if(pbBuffer == null) throw new ArgumentNullException("pbBuffer"); - if(nBufferOffset < 0) throw new ArgumentException(); - if(nLength < 0) throw new ArgumentException(); - if((nSourceOffset + nLength) > pbSource.Length) throw new ArgumentException(); - if((nBufferOffset + nLength) > pbBuffer.Length) throw new ArgumentException(); + if(iBufferOffset < 0) throw new ArgumentOutOfRangeException("iBufferOffset"); + if(cb < 0) throw new ArgumentOutOfRangeException("cb"); + if(iSourceOffset > (pbSource.Length - cb)) + throw new ArgumentOutOfRangeException("cb"); + if(iBufferOffset > (pbBuffer.Length - cb)) + throw new ArgumentOutOfRangeException("cb"); - for(int i = 0; i < nLength; ++i) - pbBuffer[nBufferOffset + i] ^= pbSource[nSourceOffset + i]; + for(int i = 0; i < cb; ++i) + pbBuffer[iBufferOffset + i] ^= pbSource[iSourceOffset + i]; } /// @@ -463,7 +631,8 @@ namespace KeePassLib.Utility if(s == null) { Debug.Assert(false); return; } if(pbData == null) { Debug.Assert(false); return; } - s.Write(pbData, 0, pbData.Length); + Debug.Assert(pbData.Length >= 0); + if(pbData.Length > 0) s.Write(pbData, 0, pbData.Length); } public static byte[] Compress(byte[] pbData) diff --git a/src/KeePassLib2Android/Utility/MonoWorkarounds.cs b/src/KeePassLib2Android/Utility/MonoWorkarounds.cs index 064791c9..aa1782b7 100644 --- a/src/KeePassLib2Android/Utility/MonoWorkarounds.cs +++ b/src/KeePassLib2Android/Utility/MonoWorkarounds.cs @@ -70,6 +70,12 @@ namespace KeePassLib.Utility // 1418: // Minimizing a form while loading it doesn't work. // https://sourceforge.net/p/keepass/bugs/1418/ + // 2139: + // Shortcut keys are ignored. + // https://sourceforge.net/p/keepass/feature-requests/2139/ + // 2140: + // Explicit control focusing is ignored. + // https://sourceforge.net/p/keepass/feature-requests/2140/ // 5795: // Text in input field is incomplete. // https://bugzilla.xamarin.com/show_bug.cgi?id=5795 diff --git a/src/KeePassLib2Android/Utility/StrUtil.cs b/src/KeePassLib2Android/Utility/StrUtil.cs index 39ef5282..bbd0195c 100644 --- a/src/KeePassLib2Android/Utility/StrUtil.cs +++ b/src/KeePassLib2Android/Utility/StrUtil.cs @@ -978,13 +978,12 @@ namespace KeePassLib.Utility public static bool IsHexString(string str, bool bStrict) { if(str == null) throw new ArgumentNullException("str"); - if(str.Length == 0) return true; foreach(char ch in str) { if((ch >= '0') && (ch <= '9')) continue; - if((ch >= 'a') && (ch <= 'z')) continue; - if((ch >= 'A') && (ch <= 'Z')) continue; + if((ch >= 'a') && (ch <= 'f')) continue; + if((ch >= 'A') && (ch <= 'F')) continue; if(bStrict) return false; @@ -997,8 +996,31 @@ namespace KeePassLib.Utility return true; } + public static bool IsHexString(byte[] pbUtf8, bool bStrict) + { + if(pbUtf8 == null) throw new ArgumentNullException("pbUtf8"); + + for(int i = 0; i < pbUtf8.Length; ++i) + { + byte bt = pbUtf8[i]; + if((bt >= (byte)'0') && (bt <= (byte)'9')) continue; + if((bt >= (byte)'a') && (bt <= (byte)'f')) continue; + if((bt >= (byte)'A') && (bt <= (byte)'F')) continue; + + if(bStrict) return false; + + if((bt == (byte)' ') || (bt == (byte)'\t') || + (bt == (byte)'\r') || (bt == (byte)'\n')) + continue; + + return false; + } + + return true; + } + #if !KeePassLibSD - private static readonly char[] m_vPatternPartsSep = new char[]{ '*' }; + private static readonly char[] m_vPatternPartsSep = new char[] { '*' }; public static bool SimplePatternMatch(string strPattern, string strText, StringComparison sc) {