Keepass Orig KeePass_160826

This commit is contained in:
Philipp Crocoll 2016-08-30 04:11:48 +02:00
parent 6d1e28e502
commit a2f2e3d6f8
43 changed files with 4888 additions and 877 deletions

View File

@ -32,14 +32,14 @@ using KeePassLibSD;
namespace KeePassLib.Collections namespace KeePassLib.Collections
{ {
public sealed class StringDictionaryEx : IDeepCloneable<StringDictionaryEx>, public sealed class StringDictionaryEx : IDeepCloneable<StringDictionaryEx>,
IEnumerable<KeyValuePair<string, string>> IEnumerable<KeyValuePair<string, string>>, IEquatable<StringDictionaryEx>
{ {
private SortedDictionary<string, string> m_vDict = private SortedDictionary<string, string> m_dict =
new SortedDictionary<string, string>(); new SortedDictionary<string, string>();
public int Count public int Count
{ {
get { return m_vDict.Count; } get { return m_dict.Count; }
} }
public StringDictionaryEx() public StringDictionaryEx()
@ -48,39 +48,53 @@ namespace KeePassLib.Collections
IEnumerator IEnumerable.GetEnumerator() IEnumerator IEnumerable.GetEnumerator()
{ {
return m_vDict.GetEnumerator(); return m_dict.GetEnumerator();
} }
public IEnumerator<KeyValuePair<string, string>> GetEnumerator() public IEnumerator<KeyValuePair<string, string>> GetEnumerator()
{ {
return m_vDict.GetEnumerator(); return m_dict.GetEnumerator();
} }
public StringDictionaryEx CloneDeep() public StringDictionaryEx CloneDeep()
{ {
StringDictionaryEx plNew = new StringDictionaryEx(); StringDictionaryEx sdNew = new StringDictionaryEx();
foreach(KeyValuePair<string, string> kvpStr in m_vDict) foreach(KeyValuePair<string, string> kvp in m_dict)
plNew.Set(kvpStr.Key, kvpStr.Value); 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<string, string> 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) 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; string s;
if(m_vDict.TryGetValue(strName, out s)) return s; if(m_dict.TryGetValue(strName, out s)) return s;
return null; return null;
} }
public bool Exists(string strName) 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);
} }
/// <summary> /// <summary>
@ -92,25 +106,25 @@ namespace KeePassLib.Collections
/// parameters is <c>null</c>.</exception> /// parameters is <c>null</c>.</exception>
public void Set(string strField, string strNewValue) public void Set(string strField, string strNewValue)
{ {
Debug.Assert(strField != null); if(strField == null) throw new ArgumentNullException("strField"); if(strField == null) { Debug.Assert(false); throw new ArgumentNullException("strField"); }
Debug.Assert(strNewValue != null); if(strNewValue == null) throw new ArgumentNullException("strNewValue"); if(strNewValue == null) { Debug.Assert(false); throw new ArgumentNullException("strNewValue"); }
m_vDict[strField] = strNewValue; m_dict[strField] = strNewValue;
} }
/// <summary> /// <summary>
/// Delete a string. /// Delete a string.
/// </summary> /// </summary>
/// <param name="strField">Name of the string field to delete.</param> /// <param name="strField">Name of the string field to delete.</param>
/// <returns>Returns <c>true</c> if the field has been successfully /// <returns>Returns <c>true</c>, if the field has been successfully
/// removed, otherwise the return value is <c>false</c>.</returns> /// removed. Otherwise, the return value is <c>false</c>.</returns>
/// <exception cref="System.ArgumentNullException">Thrown if the input /// <exception cref="System.ArgumentNullException">Thrown if the input
/// parameter is <c>null</c>.</exception> /// parameter is <c>null</c>.</exception>
public bool Remove(string strField) 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);
} }
} }
} }

View File

@ -0,0 +1,415 @@
/*
KeePass Password Safe - The Open-Source Password Manager
Copyright (C) 2003-2016 Dominik Reichl <dominik.reichl@t-online.de>
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<string, object> m_d = new Dictionary<string, object>();
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<T>(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<T>(string strName, T t)
where T : struct
{
if(string.IsNullOrEmpty(strName)) { Debug.Assert(false); return; }
#if DEBUG
T tEx;
Get<T>(strName, out tEx); // Assert same type
#endif
m_d[strName] = t;
}
private void SetRef<T>(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<T>(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<string, object> 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<uint>(strName, out u)) return u;
return uDefault;
}
public void SetUInt32(string strName, uint uValue)
{
SetStruct<uint>(strName, uValue);
}
public ulong GetUInt64(string strName, ulong uDefault)
{
ulong u;
if(Get<ulong>(strName, out u)) return u;
return uDefault;
}
public void SetUInt64(string strName, ulong uValue)
{
SetStruct<ulong>(strName, uValue);
}
public bool GetBool(string strName, bool bDefault)
{
bool b;
if(Get<bool>(strName, out b)) return b;
return bDefault;
}
public void SetBool(string strName, bool bValue)
{
SetStruct<bool>(strName, bValue);
}
public int GetInt32(string strName, int iDefault)
{
int i;
if(Get<int>(strName, out i)) return i;
return iDefault;
}
public void SetInt32(string strName, int iValue)
{
SetStruct<int>(strName, iValue);
}
public long GetInt64(string strName, long lDefault)
{
long l;
if(Get<long>(strName, out l)) return l;
return lDefault;
}
public void SetInt64(string strName, long lValue)
{
SetStruct<long>(strName, lValue);
}
public string GetString(string strName)
{
string str;
Get<string>(strName, out str);
return str;
}
public void SetString(string strName, string strValue)
{
SetRef<string>(strName, strValue);
}
public byte[] GetByteArray(string strName)
{
byte[] pb;
Get<byte[]>(strName, out pb);
return pb;
}
public void SetByteArray(string strName, byte[] pbValue)
{
SetRef<byte[]>(strName, pbValue);
}
/// <summary>
/// Create a deep copy.
/// </summary>
public virtual object Clone()
{
VariantDictionary vdNew = new VariantDictionary();
foreach(KeyValuePair<string, object> 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<string, object> 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;
}
}
}

View File

@ -0,0 +1,251 @@
/*
KeePass Password Safe - The Open-Source Password Manager
Copyright (C) 2003-2016 Dominik Reichl <dominik.reichl@t-online.de>
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
{
/// <summary>
/// Implementation of the ChaCha20 cipher with a 96-bit nonce,
/// as specified in RFC 7539.
/// https://tools.ietf.org/html/rfc7539
/// </summary>
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)
{
}
/// <summary>
/// Constructor.
/// </summary>
/// <param name="pbKey32">Key (32 bytes).</param>
/// <param name="pbIV12">Nonce (12 bytes).</param>
/// <param name="bLargeCounter">If <c>false</c>, 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 <paramref name="bLargeCounter" /> is <c>true</c>, 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 <paramref name="pbIV12" />
/// 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).</param>
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<uint>(m_s);
MemUtil.ZeroArray<uint>(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;
}
}
}

View File

@ -0,0 +1,174 @@
/*
KeePass Password Safe - The Open-Source Password Manager
Copyright (C) 2003-2016 Dominik Reichl <dominik.reichl@t-online.de>
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);
}
}
}

View File

@ -40,12 +40,17 @@ namespace KeePassLib.Cryptography.Cipher
{ {
get 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 = cp;
m_poolGlobal.AddCipher(new StandardAesEngine()); }
return m_poolGlobal; return cp;
} }
} }

View File

@ -0,0 +1,101 @@
/*
KeePass Password Safe - The Open-Source Password Manager
Copyright (C) 2003-2016 Dominik Reichl <dominik.reichl@t-online.de>
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);
}
}
}

View File

@ -63,4 +63,25 @@ namespace KeePassLib.Cryptography.Cipher
/// <returns>Stream, from which the decrypted data can be read.</returns> /// <returns>Stream, from which the decrypted data can be read.</returns>
Stream DecryptStream(Stream sEncrypted, byte[] pbKey, byte[] pbIV); Stream DecryptStream(Stream sEncrypted, byte[] pbKey, byte[] pbIV);
} }
public interface ICipherEngine2 : ICipherEngine
{
/// <summary>
/// Length of an encryption key in bytes.
/// The base <c>ICipherEngine</c> assumes 32.
/// </summary>
int KeyLength
{
get;
}
/// <summary>
/// Length of the initialization vector in bytes.
/// The base <c>ICipherEngine</c> assumes 16.
/// </summary>
int IVLength
{
get;
}
}
} }

View File

@ -17,182 +17,145 @@
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 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;
using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using KeePassLib.Utility; using KeePassLib.Utility;
namespace KeePassLib.Cryptography.Cipher 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 uint[] m_x = new uint[16]; // Working buffer
private byte[] m_output = new byte[64]; private static readonly uint[] g_sigma = new uint[4] {
private int m_outputPos = 64;
private static readonly uint[] m_sigma = new uint[4] {
0x61707865, 0x3320646E, 0x79622D32, 0x6B206574 0x61707865, 0x3320646E, 0x79622D32, 0x6B206574
}; };
public Salsa20Cipher(byte[] pbKey32, byte[] pbIV8) public override int BlockSize
{ {
KeySetup(pbKey32); get { return 64; }
IvSetup(pbIV8);
} }
~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); MemUtil.ZeroArray<uint>(m_s);
GC.SuppressFinalize(this); MemUtil.ZeroArray<uint>(m_x);
base.Dispose(bDisposing);
} }
private void Dispose(bool bDisposing) protected override void NextBlock(byte[] pBlock)
{ {
// Clear sensitive data if(pBlock == null) throw new ArgumentNullException("pBlock");
Array.Clear(m_state, 0, m_state.Length); if(pBlock.Length != 64) throw new ArgumentOutOfRangeException("pBlock");
Array.Clear(m_x, 0, m_x.Length);
}
private void NextOutput() // x is a local alias for the working buffer; with this,
{ // the compiler/runtime might remove some checks
uint[] x = m_x; // Local alias for working buffer uint[] x = m_x;
if(x == null) throw new InvalidOperationException();
// Compiler/runtime might remove array bound checks after this
if(x.Length < 16) 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 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[ 4] ^= MemUtil.RotateLeft32(x[ 0] + x[12], 7);
x[ 8] ^= Rotl32(x[ 4] + x[ 0], 9); x[ 8] ^= MemUtil.RotateLeft32(x[ 4] + x[ 0], 9);
x[12] ^= Rotl32(x[ 8] + x[ 4], 13); x[12] ^= MemUtil.RotateLeft32(x[ 8] + x[ 4], 13);
x[ 0] ^= Rotl32(x[12] + x[ 8], 18); x[ 0] ^= MemUtil.RotateLeft32(x[12] + x[ 8], 18);
x[ 9] ^= Rotl32(x[ 5] + x[ 1], 7);
x[13] ^= Rotl32(x[ 9] + x[ 5], 9); x[ 9] ^= MemUtil.RotateLeft32(x[ 5] + x[ 1], 7);
x[ 1] ^= Rotl32(x[13] + x[ 9], 13); x[13] ^= MemUtil.RotateLeft32(x[ 9] + x[ 5], 9);
x[ 5] ^= Rotl32(x[ 1] + x[13], 18); x[ 1] ^= MemUtil.RotateLeft32(x[13] + x[ 9], 13);
x[14] ^= Rotl32(x[10] + x[ 6], 7); x[ 5] ^= MemUtil.RotateLeft32(x[ 1] + x[13], 18);
x[ 2] ^= Rotl32(x[14] + x[10], 9);
x[ 6] ^= Rotl32(x[ 2] + x[14], 13); x[14] ^= MemUtil.RotateLeft32(x[10] + x[ 6], 7);
x[10] ^= Rotl32(x[ 6] + x[ 2], 18); x[ 2] ^= MemUtil.RotateLeft32(x[14] + x[10], 9);
x[ 3] ^= Rotl32(x[15] + x[11], 7); x[ 6] ^= MemUtil.RotateLeft32(x[ 2] + x[14], 13);
x[ 7] ^= Rotl32(x[ 3] + x[15], 9); x[10] ^= MemUtil.RotateLeft32(x[ 6] + x[ 2], 18);
x[11] ^= Rotl32(x[ 7] + x[ 3], 13);
x[15] ^= Rotl32(x[11] + x[ 7], 18); x[ 3] ^= MemUtil.RotateLeft32(x[15] + x[11], 7);
x[ 1] ^= Rotl32(x[ 0] + x[ 3], 7); x[ 7] ^= MemUtil.RotateLeft32(x[ 3] + x[15], 9);
x[ 2] ^= Rotl32(x[ 1] + x[ 0], 9); x[11] ^= MemUtil.RotateLeft32(x[ 7] + x[ 3], 13);
x[ 3] ^= Rotl32(x[ 2] + x[ 1], 13); x[15] ^= MemUtil.RotateLeft32(x[11] + x[ 7], 18);
x[ 0] ^= Rotl32(x[ 3] + x[ 2], 18);
x[ 6] ^= Rotl32(x[ 5] + x[ 4], 7); x[ 1] ^= MemUtil.RotateLeft32(x[ 0] + x[ 3], 7);
x[ 7] ^= Rotl32(x[ 6] + x[ 5], 9); x[ 2] ^= MemUtil.RotateLeft32(x[ 1] + x[ 0], 9);
x[ 4] ^= Rotl32(x[ 7] + x[ 6], 13); x[ 3] ^= MemUtil.RotateLeft32(x[ 2] + x[ 1], 13);
x[ 5] ^= Rotl32(x[ 4] + x[ 7], 18); x[ 0] ^= MemUtil.RotateLeft32(x[ 3] + x[ 2], 18);
x[11] ^= Rotl32(x[10] + x[ 9], 7);
x[ 8] ^= Rotl32(x[11] + x[10], 9); x[ 6] ^= MemUtil.RotateLeft32(x[ 5] + x[ 4], 7);
x[ 9] ^= Rotl32(x[ 8] + x[11], 13); x[ 7] ^= MemUtil.RotateLeft32(x[ 6] + x[ 5], 9);
x[10] ^= Rotl32(x[ 9] + x[ 8], 18); x[ 4] ^= MemUtil.RotateLeft32(x[ 7] + x[ 6], 13);
x[12] ^= Rotl32(x[15] + x[14], 7); x[ 5] ^= MemUtil.RotateLeft32(x[ 4] + x[ 7], 18);
x[13] ^= Rotl32(x[12] + x[15], 9);
x[14] ^= Rotl32(x[13] + x[12], 13); x[11] ^= MemUtil.RotateLeft32(x[10] + x[ 9], 7);
x[15] ^= Rotl32(x[14] + x[13], 18); 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) for(int i = 0; i < 16; ++i) x[i] += s[i];
x[i] += m_state[i];
for(int i = 0; i < 16; ++i) for(int i = 0; i < 16; ++i)
{ {
m_output[i << 2] = (byte)x[i]; int i4 = i << 2;
m_output[(i << 2) + 1] = (byte)(x[i] >> 8); uint xi = x[i];
m_output[(i << 2) + 2] = (byte)(x[i] >> 16);
m_output[(i << 2) + 3] = (byte)(x[i] >> 24); 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; ++s[8];
++m_state[8]; if(s[8] == 0) ++s[9];
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;
} }
} }
} }

View File

@ -42,7 +42,7 @@ namespace KeePassLib.Cryptography.Cipher
private const PaddingMode m_rCipherPadding = PaddingMode.PKCS7; private const PaddingMode m_rCipherPadding = PaddingMode.PKCS7;
#endif #endif
private static PwUuid m_uuidAes = null; private static PwUuid g_uuidAes = null;
/// <summary> /// <summary>
/// UUID of the cipher engine. This ID uniquely identifies the /// UUID of the cipher engine. This ID uniquely identifies the
@ -52,12 +52,16 @@ namespace KeePassLib.Cryptography.Cipher
{ {
get get
{ {
if(m_uuidAes == null) PwUuid pu = g_uuidAes;
m_uuidAes = new PwUuid(new byte[]{ if(pu == null)
{
pu = new PwUuid(new byte[] {
0x31, 0xC1, 0xF2, 0xE6, 0xBF, 0x71, 0x43, 0x50, 0x31, 0xC1, 0xF2, 0xE6, 0xBF, 0x71, 0x43, 0x50,
0xBE, 0x58, 0x05, 0x21, 0x6A, 0xFC, 0x5A, 0xFF }); 0xBE, 0x58, 0x05, 0x21, 0x6A, 0xFC, 0x5A, 0xFF });
g_uuidAes = pu;
}
return m_uuidAes; return pu;
} }
} }
@ -72,7 +76,14 @@ namespace KeePassLib.Cryptography.Cipher
/// <summary> /// <summary>
/// Get a displayable name describing this cipher engine. /// Get a displayable name describing this cipher engine.
/// </summary> /// </summary>
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) private static void ValidateArguments(Stream stream, bool bEncrypt, byte[] pbKey, byte[] pbIV)
{ {

View File

@ -40,14 +40,14 @@ namespace KeePassLib.Cryptography
public sealed class CryptoRandom public sealed class CryptoRandom
{ {
private byte[] m_pbEntropyPool = new byte[64]; private byte[] m_pbEntropyPool = new byte[64];
private uint m_uCounter; private ulong m_uCounter;
private RNGCryptoServiceProvider m_rng = new RNGCryptoServiceProvider(); private RNGCryptoServiceProvider m_rng = new RNGCryptoServiceProvider();
private ulong m_uGeneratedBytesCount = 0; private ulong m_uGeneratedBytesCount = 0;
private static object g_oSyncRoot = new object(); private static object g_oSyncRoot = new object();
private object m_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 public static CryptoRandom Instance
{ {
get get
@ -55,11 +55,11 @@ namespace KeePassLib.Cryptography
CryptoRandom cr; CryptoRandom cr;
lock(g_oSyncRoot) lock(g_oSyncRoot)
{ {
cr = m_pInstance; cr = g_pInstance;
if(cr == null) if(cr == null)
{ {
cr = new CryptoRandom(); cr = new CryptoRandom();
m_pInstance = cr; g_pInstance = cr;
} }
} }
@ -90,10 +90,12 @@ namespace KeePassLib.Cryptography
private CryptoRandom() private CryptoRandom()
{ {
Random r = new Random(); Random rWeak = new Random();
m_uCounter = (uint)r.Next(); byte[] pb = new byte[8];
rWeak.NextBytes(pb);
m_uCounter = MemUtil.BytesToUInt64(pb);
AddEntropy(GetSystemData(r)); AddEntropy(GetSystemData(rWeak));
AddEntropy(GetCspData()); AddEntropy(GetCspData());
} }
@ -109,32 +111,40 @@ namespace KeePassLib.Cryptography
if(pbEntropy.Length == 0) { Debug.Assert(false); return; } if(pbEntropy.Length == 0) { Debug.Assert(false); return; }
byte[] pbNewData = pbEntropy; byte[] pbNewData = pbEntropy;
if(pbEntropy.Length >= 64) if(pbEntropy.Length > 64)
{ {
#if KeePassLibSD #if KeePassLibSD
SHA256Managed shaNew = new SHA256Managed(); using(SHA256Managed shaNew = new SHA256Managed())
#else #else
SHA512Managed shaNew = new SHA512Managed(); using(SHA512Managed shaNew = new SHA512Managed())
#endif #endif
pbNewData = shaNew.ComputeHash(pbEntropy); {
pbNewData = shaNew.ComputeHash(pbEntropy);
}
} }
MemoryStream ms = new MemoryStream();
lock(m_oSyncRoot) lock(m_oSyncRoot)
{ {
ms.Write(m_pbEntropyPool, 0, m_pbEntropyPool.Length); int cbPool = m_pbEntropyPool.Length;
ms.Write(pbNewData, 0, pbNewData.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 #if KeePassLibSD
SHA256Managed shaPool = new SHA256Managed(); using(SHA256Managed shaPool = new SHA256Managed())
#else #else
Debug.Assert(pbFinal.Length == (64 + pbNewData.Length)); using(SHA512Managed shaPool = new SHA512Managed())
SHA512Managed shaPool = new SHA512Managed();
#endif #endif
m_pbEntropyPool = shaPool.ComputeHash(pbFinal); {
m_pbEntropyPool = shaPool.ComputeHash(pbCmp);
}
MemUtil.ZeroByteArray(pbCmp);
} }
ms.Close();
} }
private static byte[] GetSystemData(Random rWeak) private static byte[] GetSystemData(Random rWeak)
@ -142,11 +152,11 @@ namespace KeePassLib.Cryptography
MemoryStream ms = new MemoryStream(); MemoryStream ms = new MemoryStream();
byte[] pb; byte[] pb;
pb = MemUtil.UInt32ToBytes((uint)Environment.TickCount); pb = MemUtil.Int32ToBytes(Environment.TickCount);
ms.Write(pb, 0, pb.Length); MemUtil.Write(ms, pb);
pb = TimeUtil.PackTime(DateTime.Now); pb = MemUtil.Int64ToBytes(DateTime.UtcNow.ToBinary());
ms.Write(pb, 0, pb.Length); MemUtil.Write(ms, pb);
#if !KeePassLibSD #if !KeePassLibSD
// In try-catch for systems without GUI; // In try-catch for systems without GUI;
@ -154,79 +164,79 @@ namespace KeePassLib.Cryptography
try try
{ {
Point pt = Cursor.Position; Point pt = Cursor.Position;
pb = MemUtil.UInt32ToBytes((uint)pt.X); pb = MemUtil.Int32ToBytes(pt.X);
ms.Write(pb, 0, pb.Length); MemUtil.Write(ms, pb);
pb = MemUtil.UInt32ToBytes((uint)pt.Y); pb = MemUtil.Int32ToBytes(pt.Y);
ms.Write(pb, 0, pb.Length); MemUtil.Write(ms, pb);
} }
catch(Exception) { } catch(Exception) { }
#endif #endif
pb = MemUtil.UInt32ToBytes((uint)rWeak.Next()); pb = MemUtil.Int32ToBytes(rWeak.Next());
ms.Write(pb, 0, pb.Length); MemUtil.Write(ms, pb);
pb = MemUtil.UInt32ToBytes((uint)NativeLib.GetPlatformID()); pb = MemUtil.UInt32ToBytes((uint)NativeLib.GetPlatformID());
ms.Write(pb, 0, pb.Length); MemUtil.Write(ms, pb);
try try
{ {
pb = MemUtil.UInt32ToBytes((uint)Environment.ProcessorCount); pb = MemUtil.Int32ToBytes(Environment.ProcessorCount);
ms.Write(pb, 0, pb.Length); MemUtil.Write(ms, pb);
#if KeePassUAP #if KeePassUAP
Version v = EnvironmentExt.OSVersion.Version; Version v = EnvironmentExt.OSVersion.Version;
#else #else
Version v = Environment.OSVersion.Version; Version v = Environment.OSVersion.Version;
#endif #endif
pb = MemUtil.UInt32ToBytes((uint)v.GetHashCode()); pb = MemUtil.Int32ToBytes(v.GetHashCode());
ms.Write(pb, 0, pb.Length); MemUtil.Write(ms, pb);
#if !KeePassUAP #if !KeePassUAP
pb = MemUtil.UInt64ToBytes((ulong)Environment.WorkingSet); pb = MemUtil.Int64ToBytes(Environment.WorkingSet);
ms.Write(pb, 0, pb.Length); MemUtil.Write(ms, pb);
#endif #endif
} }
catch(Exception) { Debug.Assert(false); } catch(Exception) { Debug.Assert(false); }
#if KeePassUAP #if KeePassUAP
pb = DiagnosticsExt.GetProcessEntropy(); pb = DiagnosticsExt.GetProcessEntropy();
ms.Write(pb, 0, pb.Length); MemUtil.Write(ms, pb);
#elif !KeePassLibSD #elif !KeePassLibSD
Process p = null; Process p = null;
try try
{ {
p = Process.GetCurrentProcess(); p = Process.GetCurrentProcess();
pb = MemUtil.UInt64ToBytes((ulong)p.Handle.ToInt64()); pb = MemUtil.Int64ToBytes(p.Handle.ToInt64());
ms.Write(pb, 0, pb.Length); MemUtil.Write(ms, pb);
pb = MemUtil.UInt32ToBytes((uint)p.HandleCount); pb = MemUtil.Int32ToBytes(p.HandleCount);
ms.Write(pb, 0, pb.Length); MemUtil.Write(ms, pb);
pb = MemUtil.UInt32ToBytes((uint)p.Id); pb = MemUtil.Int32ToBytes(p.Id);
ms.Write(pb, 0, pb.Length); MemUtil.Write(ms, pb);
pb = MemUtil.UInt64ToBytes((ulong)p.NonpagedSystemMemorySize64); pb = MemUtil.Int64ToBytes(p.NonpagedSystemMemorySize64);
ms.Write(pb, 0, pb.Length); MemUtil.Write(ms, pb);
pb = MemUtil.UInt64ToBytes((ulong)p.PagedMemorySize64); pb = MemUtil.Int64ToBytes(p.PagedMemorySize64);
ms.Write(pb, 0, pb.Length); MemUtil.Write(ms, pb);
pb = MemUtil.UInt64ToBytes((ulong)p.PagedSystemMemorySize64); pb = MemUtil.Int64ToBytes(p.PagedSystemMemorySize64);
ms.Write(pb, 0, pb.Length); MemUtil.Write(ms, pb);
pb = MemUtil.UInt64ToBytes((ulong)p.PeakPagedMemorySize64); pb = MemUtil.Int64ToBytes(p.PeakPagedMemorySize64);
ms.Write(pb, 0, pb.Length); MemUtil.Write(ms, pb);
pb = MemUtil.UInt64ToBytes((ulong)p.PeakVirtualMemorySize64); pb = MemUtil.Int64ToBytes(p.PeakVirtualMemorySize64);
ms.Write(pb, 0, pb.Length); MemUtil.Write(ms, pb);
pb = MemUtil.UInt64ToBytes((ulong)p.PeakWorkingSet64); pb = MemUtil.Int64ToBytes(p.PeakWorkingSet64);
ms.Write(pb, 0, pb.Length); MemUtil.Write(ms, pb);
pb = MemUtil.UInt64ToBytes((ulong)p.PrivateMemorySize64); pb = MemUtil.Int64ToBytes(p.PrivateMemorySize64);
ms.Write(pb, 0, pb.Length); MemUtil.Write(ms, pb);
pb = MemUtil.UInt64ToBytes((ulong)p.StartTime.ToBinary()); pb = MemUtil.Int64ToBytes(p.StartTime.ToBinary());
ms.Write(pb, 0, pb.Length); MemUtil.Write(ms, pb);
pb = MemUtil.UInt64ToBytes((ulong)p.VirtualMemorySize64); pb = MemUtil.Int64ToBytes(p.VirtualMemorySize64);
ms.Write(pb, 0, pb.Length); MemUtil.Write(ms, pb);
pb = MemUtil.UInt64ToBytes((ulong)p.WorkingSet64); pb = MemUtil.Int64ToBytes(p.WorkingSet64);
ms.Write(pb, 0, pb.Length); MemUtil.Write(ms, pb);
// Not supported in Mono 1.2.6: // Not supported in Mono 1.2.6:
// pb = MemUtil.UInt32ToBytes((uint)p.SessionId); // pb = MemUtil.UInt32ToBytes((uint)p.SessionId);
// ms.Write(pb, 0, pb.Length); // MemUtil.Write(ms, pb);
} }
catch(Exception) { Debug.Assert(NativeLib.IsUnix()); } catch(Exception) { Debug.Assert(NativeLib.IsUnix()); }
finally finally
@ -237,7 +247,7 @@ namespace KeePassLib.Cryptography
#endif #endif
pb = Guid.NewGuid().ToByteArray(); pb = Guid.NewGuid().ToByteArray();
ms.Write(pb, 0, pb.Length); MemUtil.Write(ms, pb);
byte[] pbAll = ms.ToArray(); byte[] pbAll = ms.ToArray();
ms.Close(); ms.Close();
@ -256,28 +266,31 @@ namespace KeePassLib.Cryptography
if(this.GenerateRandom256Pre != null) if(this.GenerateRandom256Pre != null)
this.GenerateRandom256Pre(this, EventArgs.Empty); this.GenerateRandom256Pre(this, EventArgs.Empty);
byte[] pbFinal; byte[] pbCmp;
lock(m_oSyncRoot) lock(m_oSyncRoot)
{ {
unchecked { m_uCounter += 386047; } // Prime number m_uCounter += 0x74D8B29E4D38E161UL; // Prime number
byte[] pbCounter = MemUtil.UInt32ToBytes(m_uCounter); byte[] pbCounter = MemUtil.UInt64ToBytes(m_uCounter);
byte[] pbCspRandom = GetCspData(); byte[] pbCspRandom = GetCspData();
MemoryStream ms = new MemoryStream(); int cbPool = m_pbEntropyPool.Length;
ms.Write(m_pbEntropyPool, 0, m_pbEntropyPool.Length); int cbCtr = pbCounter.Length;
ms.Write(pbCounter, 0, pbCounter.Length); int cbCsp = pbCspRandom.Length;
ms.Write(pbCspRandom, 0, pbCspRandom.Length);
pbFinal = ms.ToArray(); pbCmp = new byte[cbPool + cbCtr + cbCsp];
Debug.Assert(pbFinal.Length == (m_pbEntropyPool.Length + Array.Copy(m_pbEntropyPool, pbCmp, cbPool);
pbCounter.Length + pbCspRandom.Length)); Array.Copy(pbCounter, 0, pbCmp, cbPool, cbCtr);
ms.Close(); Array.Copy(pbCspRandom, 0, pbCmp, cbPool + cbCtr, cbCsp);
MemUtil.ZeroByteArray(pbCspRandom);
m_uGeneratedBytesCount += 32; m_uGeneratedBytesCount += 32;
} }
SHA256Managed sha256 = new SHA256Managed(); byte[] pbRet = CryptoUtil.HashSha256(pbCmp);
return sha256.ComputeHash(pbFinal); MemUtil.ZeroByteArray(pbCmp);
return pbRet;
} }
/// <summary> /// <summary>
@ -289,29 +302,32 @@ namespace KeePassLib.Cryptography
/// random bytes.</returns> /// random bytes.</returns>
public byte[] GetRandomBytes(uint uRequestedBytes) 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]; int cbRem = (int)uRequestedBytes;
long lPos = 0; byte[] pbRes = new byte[cbRem];
int iPos = 0;
while(uRequestedBytes != 0) while(cbRem != 0)
{ {
byte[] pbRandom256 = GenerateRandom256(); byte[] pbRandom256 = GenerateRandom256();
Debug.Assert(pbRandom256.Length == 32); 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) MemUtil.ZeroByteArray(pbRandom256);
Array.Copy(pbRandom256, 0, pbRes, lPos, lCopy);
#else
Array.Copy(pbRandom256, 0, pbRes, (int)lPos, (int)lCopy);
#endif
lPos += lCopy; iPos += cbCopy;
uRequestedBytes -= (uint)lCopy; cbRem -= cbCopy;
} }
Debug.Assert((int)lPos == pbRes.Length); Debug.Assert(iPos == pbRes.Length);
return pbRes; return pbRes;
} }
} }

View File

@ -25,6 +25,7 @@ using System.Security.Cryptography;
#endif #endif
using KeePassLib.Cryptography.Cipher; using KeePassLib.Cryptography.Cipher;
using KeePassLib.Utility;
namespace KeePassLib.Cryptography namespace KeePassLib.Cryptography
{ {
@ -40,6 +41,7 @@ namespace KeePassLib.Cryptography
/// <summary> /// <summary>
/// A variant of the ARCFour algorithm (RC4 incompatible). /// A variant of the ARCFour algorithm (RC4 incompatible).
/// Insecure; for backward compatibility only.
/// </summary> /// </summary>
ArcFourVariant = 1, ArcFourVariant = 1,
@ -48,7 +50,12 @@ namespace KeePassLib.Cryptography
/// </summary> /// </summary>
Salsa20 = 2, Salsa20 = 2,
Count = 3 /// <summary>
/// ChaCha20 stream cipher algorithm.
/// </summary>
ChaCha20 = 3,
Count = 4
} }
/// <summary> /// <summary>
@ -59,45 +66,68 @@ namespace KeePassLib.Cryptography
/// </summary> /// </summary>
public sealed class CryptoRandomStream public sealed class CryptoRandomStream
{ {
private CrsAlgorithm m_crsAlgorithm; private readonly CrsAlgorithm m_crsAlgorithm;
private byte[] m_pbState = null; private byte[] m_pbState = null;
private byte m_i = 0; private byte m_i = 0;
private byte m_j = 0; private byte m_j = 0;
private Salsa20Cipher m_salsa20 = null; private Salsa20Cipher m_salsa20 = null;
private ChaCha20Cipher m_chacha20 = null;
/// <summary> /// <summary>
/// Construct a new cryptographically secure random stream object. /// Construct a new cryptographically secure random stream object.
/// </summary> /// </summary>
/// <param name="genAlgorithm">Algorithm to use.</param> /// <param name="a">Algorithm to use.</param>
/// <param name="pbKey">Initialization key. Must not be <c>null</c> and /// <param name="pbKey">Initialization key. Must not be <c>null</c> and
/// must contain at least 1 byte.</param> /// must contain at least 1 byte.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the public CryptoRandomStream(CrsAlgorithm a, byte[] pbKey)
/// <paramref name="pbKey" /> parameter is <c>null</c>.</exception>
/// <exception cref="System.ArgumentException">Thrown if the
/// <paramref name="pbKey" /> parameter contains no bytes or the
/// algorithm is unknown.</exception>
public CryptoRandomStream(CrsAlgorithm genAlgorithm, 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; m_crsAlgorithm = a;
Debug.Assert(uKeyLen != 0); if(uKeyLen == 0) throw new ArgumentException();
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 // Fill the state linearly
m_pbState = new byte[256]; 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 unchecked
{ {
byte j = 0, t; byte j = 0, t;
uint inxKey = 0; int inxKey = 0;
for(uint w = 0; w < 256; ++w) // Key setup for(int w = 0; w < 256; ++w) // Key setup
{ {
j += (byte)(m_pbState[w] + pbKey[inxKey]); j += (byte)(m_pbState[w] + pbKey[inxKey]);
@ -106,25 +136,16 @@ namespace KeePassLib.Cryptography
m_pbState[j] = t; m_pbState[j] = t;
++inxKey; ++inxKey;
if(inxKey >= uKeyLen) inxKey = 0; if(inxKey >= cbKey) inxKey = 0;
} }
} }
GetRandomBytes(512); // Increases security, see cryptanalysis 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 else // Unknown algorithm
{ {
Debug.Assert(false); Debug.Assert(false);
throw new ArgumentException(); throw new ArgumentOutOfRangeException("a");
} }
} }
@ -135,15 +156,23 @@ namespace KeePassLib.Cryptography
/// <returns>Returns <paramref name="uRequestedCount" /> random bytes.</returns> /// <returns>Returns <paramref name="uRequestedCount" /> random bytes.</returns>
public byte[] GetRandomBytes(uint uRequestedCount) 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 unchecked
{ {
for(uint w = 0; w < uRequestedCount; ++w) for(int w = 0; w < cb; ++w)
{ {
++m_i; ++m_i;
m_j += m_pbState[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); } else { Debug.Assert(false); }
return pbRet; return pbRet;
@ -167,14 +194,7 @@ namespace KeePassLib.Cryptography
public ulong GetRandomUInt64() public ulong GetRandomUInt64()
{ {
byte[] pb = GetRandomBytes(8); byte[] pb = GetRandomBytes(8);
return MemUtil.BytesToUInt64(pb);
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);
}
} }
#if CRSBENCHMARK #if CRSBENCHMARK

View File

@ -0,0 +1,126 @@
/*
KeePass Password Safe - The Open-Source Password Manager
Copyright (C) 2003-2016 Dominik Reichl <dominik.reichl@t-online.de>
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;
}
/// <summary>
/// Create a cryptographic key of length <paramref name="cbOut" />
/// (in bytes) from <paramref name="pbIn" />.
/// </summary>
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;
}
}
}

View File

@ -0,0 +1,229 @@
/*
KeePass Password Safe - The Open-Source Password Manager
Copyright (C) 2003-2016 Dominik Reichl <dominik.reichl@t-online.de>
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;
}
}
}

View File

@ -34,7 +34,7 @@ namespace KeePassLib.Cryptography
public sealed class HashingStreamEx : Stream public sealed class HashingStreamEx : Stream
{ {
private Stream m_sBaseStream; private Stream m_sBaseStream;
private bool m_bWriting; private readonly bool m_bWriting;
private HashAlgorithm m_hash; private HashAlgorithm m_hash;
private byte[] m_pbFinalHash = null; private byte[] m_pbFinalHash = null;
@ -67,7 +67,7 @@ namespace KeePassLib.Cryptography
public override long Position public override long Position
{ {
get { return m_sBaseStream.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) public HashingStreamEx(Stream sBaseStream, bool bWriting, HashAlgorithm hashAlgorithm)
@ -114,7 +114,7 @@ namespace KeePassLib.Cryptography
{ {
try try
{ {
m_hash.TransformFinalBlock(new byte[0], 0, 0); m_hash.TransformFinalBlock(MemUtil.EmptyByteArray, 0, 0);
m_pbFinalHash = m_hash.Hash; m_pbFinalHash = m_hash.Hash;
} }

View File

@ -0,0 +1,270 @@
/*
KeePass Password Safe - The Open-Source Password Manager
Copyright (C) 2003-2016 Dominik Reichl <dominik.reichl@t-online.de>
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;
}
}
}

View File

@ -0,0 +1,610 @@
/*
KeePass Password Safe - The Open-Source Password Manager
Copyright (C) 2003-2016 Dominik Reichl <dominik.reichl@t-online.de>
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<ulong>(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<ulong>(pbR);
MemUtil.ZeroArray<ulong>(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<ulong>(pqBlockHash);
MemUtil.ZeroByteArray(pbBlockHashBytes);
return pbOut;
}
}
}

View File

@ -0,0 +1,144 @@
/*
KeePass Password Safe - The Open-Source Password Manager
Copyright (C) 2003-2016 Dominik Reichl <dominik.reichl@t-online.de>
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;
}
}
}

View File

@ -0,0 +1,142 @@
/*
KeePass Password Safe - The Open-Source Password Manager
Copyright (C) 2003-2016 Dominik Reichl <dominik.reichl@t-online.de>
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);
}
/// <summary>
/// Generate random seeds and store them in <paramref name="p" />.
/// </summary>
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);
}
}
}

View File

@ -0,0 +1,80 @@
/*
KeePass Password Safe - The Open-Source Password Manager
Copyright (C) 2003-2016 Dominik Reichl <dominik.reichl@t-online.de>
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);
}
/// <summary>
/// Unsupported.
/// </summary>
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;
}
}
}

View File

@ -0,0 +1,96 @@
/*
KeePass Password Safe - The Open-Source Password Manager
Copyright (C) 2003-2016 Dominik Reichl <dominik.reichl@t-online.de>
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<KdfEngine> g_l = new List<KdfEngine>();
public static IEnumerable<KdfEngine> 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);
}
}
}

View File

@ -19,10 +19,12 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text;
using System.Diagnostics; using System.Diagnostics;
using System.Security.Cryptography;
using System.Text;
using KeePassLib.Security; using KeePassLib.Security;
using KeePassLib.Utility;
namespace KeePassLib.Cryptography.PasswordGenerator namespace KeePassLib.Cryptography.PasswordGenerator
{ {
@ -62,16 +64,20 @@ namespace KeePassLib.Cryptography.PasswordGenerator
private static CryptoRandomStream CreateCryptoStream(byte[] pbAdditionalEntropy) private static CryptoRandomStream CreateCryptoStream(byte[] pbAdditionalEntropy)
{ {
byte[] pbKey = CryptoRandom.Instance.GetRandomBytes(256); byte[] pbKey = CryptoRandom.Instance.GetRandomBytes(128);
// Mix in additional entropy // Mix in additional entropy
Debug.Assert(pbKey.Length >= 64);
if((pbAdditionalEntropy != null) && (pbAdditionalEntropy.Length > 0)) if((pbAdditionalEntropy != null) && (pbAdditionalEntropy.Length > 0))
{ {
for(int nKeyPos = 0; nKeyPos < pbKey.Length; ++nKeyPos) using(SHA512Managed h = new SHA512Managed())
pbKey[nKeyPos] ^= pbAdditionalEntropy[nKeyPos % pbAdditionalEntropy.Length]; {
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, internal static char GenerateCharacter(PwProfile pwProfile,

View File

@ -21,6 +21,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Globalization; using System.Globalization;
using System.IO;
using System.Security; using System.Security;
using System.Text; using System.Text;
@ -33,6 +34,8 @@ using System.Security.Cryptography;
#endif #endif
using KeePassLib.Cryptography.Cipher; using KeePassLib.Cryptography.Cipher;
using KeePassLib.Cryptography.Hash;
using KeePassLib.Cryptography.KeyDerivation;
using KeePassLib.Keys; using KeePassLib.Keys;
using KeePassLib.Native; using KeePassLib.Native;
using KeePassLib.Resources; using KeePassLib.Resources;
@ -45,17 +48,6 @@ using KeePassLib.Utility;
namespace KeePassLib.Cryptography namespace KeePassLib.Cryptography
{ {
/* /// <summary>
/// Return values of the <c>SelfTest.Perform</c> method.
/// </summary>
public enum SelfTestResult
{
Success = 0,
RijndaelEcbError = 1,
Salsa20Error = 2,
NativeKeyTransformationError = 3
} */
/// <summary> /// <summary>
/// Class containing self-test methods. /// Class containing self-test methods.
/// </summary> /// </summary>
@ -70,7 +62,11 @@ namespace KeePassLib.Cryptography
TestRijndael(); TestRijndael();
TestSalsa20(); TestSalsa20();
TestChaCha20();
TestBlake2b();
TestArgon2();
TestHmac();
TestNativeKeyTransform(); TestNativeKeyTransform();
TestHmacOtp(); TestHmacOtp();
@ -92,14 +88,14 @@ namespace KeePassLib.Cryptography
internal static void TestFipsComplianceProblems() internal static void TestFipsComplianceProblems()
{ {
#if !KeePassUAP #if !KeePassUAP
try { new RijndaelManaged(); } try { using(RijndaelManaged r = new RijndaelManaged()) { } }
catch(Exception exAes) catch(Exception exAes)
{ {
throw new SecurityException("AES/Rijndael: " + exAes.Message); throw new SecurityException("AES/Rijndael: " + exAes.Message);
} }
#endif #endif
try { new SHA256Managed(); } try { using(SHA256Managed h = new SHA256Managed()) { } }
catch(Exception exSha256) catch(Exception exSha256)
{ {
throw new SecurityException("SHA-256: " + exSha256.Message); throw new SecurityException("SHA-256: " + exSha256.Message);
@ -126,7 +122,7 @@ namespace KeePassLib.Cryptography
AesEngine r = new AesEngine(); AesEngine r = new AesEngine();
r.Init(true, new KeyParameter(pbTestKey)); r.Init(true, new KeyParameter(pbTestKey));
if(r.GetBlockSize() != pbTestData.Length) if(r.GetBlockSize() != pbTestData.Length)
throw new SecurityException(KLRes.EncAlgorithmAes + " (BS)."); throw new SecurityException("AES (BC)");
r.ProcessBlock(pbTestData, 0, pbTestData, 0); r.ProcessBlock(pbTestData, 0, pbTestData, 0);
#else #else
RijndaelManaged r = new RijndaelManaged(); RijndaelManaged r = new RijndaelManaged();
@ -147,13 +143,14 @@ namespace KeePassLib.Cryptography
#endif #endif
if(!MemUtil.ArraysEqual(pbTestData, pbReferenceCT)) if(!MemUtil.ArraysEqual(pbTestData, pbReferenceCT))
throw new SecurityException(KLRes.EncAlgorithmAes + "."); throw new SecurityException("AES");
} }
private static void TestSalsa20() private static void TestSalsa20()
{ {
#if DEBUG
// Test values from official set 6, vector 3 // 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, 0x0F, 0x62, 0xB5, 0x08, 0x5B, 0xAE, 0x01, 0x54,
0xA7, 0xFA, 0x4D, 0xA0, 0xF3, 0x46, 0x99, 0xEC, 0xA7, 0xFA, 0x4D, 0xA0, 0xF3, 0x46, 0x99, 0xEC,
0x3F, 0x92, 0xE5, 0x38, 0x8B, 0xDE, 0x31, 0x84, 0x3F, 0x92, 0xE5, 0x38, 0x8B, 0xDE, 0x31, 0x84,
@ -168,12 +165,11 @@ namespace KeePassLib.Cryptography
byte[] pb = new byte[16]; byte[] pb = new byte[16];
Salsa20Cipher c = new Salsa20Cipher(pbKey, pbIV); Salsa20Cipher c = new Salsa20Cipher(pbKey, pbIV);
c.Encrypt(pb, pb.Length, false); c.Encrypt(pb, 0, pb.Length);
if(!MemUtil.ArraysEqual(pb, pbExpected)) if(!MemUtil.ArraysEqual(pb, pbExpected))
throw new SecurityException("Salsa20-1"); throw new SecurityException("Salsa20-1");
#if DEBUG // Extended test
// Extended test in debug mode
byte[] pbExpected2 = new byte[16] { byte[] pbExpected2 = new byte[16] {
0xAB, 0xF3, 0x9A, 0x21, 0x0E, 0xEE, 0x89, 0x59, 0xAB, 0xF3, 0x9A, 0x21, 0x0E, 0xEE, 0x89, 0x59,
0x8B, 0x71, 0x33, 0x37, 0x70, 0x56, 0xC2, 0xFE 0x8B, 0x71, 0x33, 0x37, 0x70, 0x56, 0xC2, 0xFE
@ -185,13 +181,14 @@ namespace KeePassLib.Cryptography
Random r = new Random(); Random r = new Random();
int nPos = Salsa20ToPos(c, r, pb.Length, 65536); 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)) if(!MemUtil.ArraysEqual(pb, pbExpected2))
throw new SecurityException("Salsa20-2"); throw new SecurityException("Salsa20-2");
nPos = Salsa20ToPos(c, r, nPos + pb.Length, 131008); nPos = Salsa20ToPos(c, r, nPos + pb.Length, 131008);
Array.Clear(pb, 0, pb.Length); Array.Clear(pb, 0, pb.Length);
c.Encrypt(pb, pb.Length, true); c.Encrypt(pb, 0, pb.Length);
if(!MemUtil.ArraysEqual(pb, pbExpected3)) if(!MemUtil.ArraysEqual(pb, pbExpected3))
throw new SecurityException("Salsa20-3"); throw new SecurityException("Salsa20-3");
@ -200,8 +197,8 @@ namespace KeePassLib.Cryptography
for(int i = 0; i < nRounds; ++i) for(int i = 0; i < nRounds; ++i)
{ {
byte[] z = new byte[32]; byte[] z = new byte[32];
c = new Salsa20Cipher(z, BitConverter.GetBytes((long)i)); c = new Salsa20Cipher(z, MemUtil.Int64ToBytes(i));
c.Encrypt(z, z.Length, true); c.Encrypt(z, 0, z.Length);
d[MemUtil.ByteArrayToHexString(z)] = true; d[MemUtil.ByteArrayToHexString(z)] = true;
} }
if(d.Count != nRounds) throw new SecurityException("Salsa20-4"); if(d.Count != nRounds) throw new SecurityException("Salsa20-4");
@ -218,7 +215,7 @@ namespace KeePassLib.Cryptography
{ {
int x = r.Next(1, 513); int x = r.Next(1, 513);
int nGen = Math.Min(nTargetPos - nPos, x); int nGen = Math.Min(nTargetPos - nPos, x);
c.Encrypt(pb, nGen, r.Next(0, 2) == 0); c.Encrypt(pb, 0, nGen);
nPos += nGen; nPos += nGen;
} }
@ -226,6 +223,475 @@ namespace KeePassLib.Cryptography
} }
#endif #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() private static void TestNativeKeyTransform()
{ {
#if DEBUG #if DEBUG
@ -235,8 +701,8 @@ namespace KeePassLib.Cryptography
byte[] pbManaged = new byte[32]; byte[] pbManaged = new byte[32];
Array.Copy(pbOrgKey, pbManaged, 32); Array.Copy(pbOrgKey, pbManaged, 32);
if(!CompositeKey.TransformKeyManaged(pbManaged, pbSeed, uRounds)) if(!AesKdf.TransformKeyManaged(pbManaged, pbSeed, uRounds))
throw new SecurityException("Managed transform."); throw new SecurityException("AES-KDF-1");
byte[] pbNative = new byte[32]; byte[] pbNative = new byte[32];
Array.Copy(pbOrgKey, pbNative, 32); Array.Copy(pbOrgKey, pbNative, 32);
@ -244,7 +710,7 @@ namespace KeePassLib.Cryptography
return; // Native library not available ("success") return; // Native library not available ("success")
if(!MemUtil.ArraysEqual(pbManaged, pbNative)) if(!MemUtil.ArraysEqual(pbManaged, pbNative))
throw new SecurityException("Native transform."); throw new SecurityException("AES-KDF-2");
#endif #endif
} }
@ -310,6 +776,15 @@ namespace KeePassLib.Cryptography
pbRes = MemUtil.ParseBase32("JNSXSIDQOJXXM2LEMVZCAYTBONSWIIDPNYQG63TFFV2GS3LFEBYGC43TO5XXEZDTFY======"); pbRes = MemUtil.ParseBase32("JNSXSIDQOJXXM2LEMVZCAYTBONSWIIDPNYQG63TFFV2GS3LFEBYGC43TO5XXEZDTFY======");
pbExp = Encoding.ASCII.GetBytes("Key provider based on one-time passwords."); pbExp = Encoding.ASCII.GetBytes("Key provider based on one-time passwords.");
if(!MemUtil.ArraysEqual(pbRes, pbExp)) throw new Exception("Base32-7"); 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 #endif
} }

View File

@ -22,14 +22,8 @@ using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Text; using System.Text;
#if KeePassUAP using KeePassLib.Cryptography;
using Org.BouncyCastle.Crypto; using KeePassLib.Cryptography.KeyDerivation;
using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.Crypto.Parameters;
#else
using System.Security.Cryptography;
#endif
using KeePassLib.Native; using KeePassLib.Native;
using KeePassLib.Resources; using KeePassLib.Resources;
using KeePassLib.Security; using KeePassLib.Security;
@ -197,8 +191,7 @@ namespace KeePassLib.Keys
} }
Debug.Assert(p == cbData); Debug.Assert(p == cbData);
SHA256Managed sha256 = new SHA256Managed(); byte[] pbHash = CryptoUtil.HashSha256(pbAllData);
byte[] pbHash = sha256.ComputeHash(pbAllData);
MemUtil.ZeroByteArray(pbAllData); MemUtil.ZeroByteArray(pbAllData);
return pbHash; return pbHash;
} }
@ -216,15 +209,7 @@ namespace KeePassLib.Keys
return bResult; return bResult;
} }
/// <summary> [Obsolete]
/// Generate a 32-bit wide key out of the composite key.
/// </summary>
/// <param name="pbKeySeed32">Seed used in the key transformation
/// rounds. Must be a byte array containing exactly 32 bytes; must
/// not be null.</param>
/// <param name="uNumRounds">Number of key transformation rounds.</param>
/// <returns>Returns a protected binary object that contains the
/// resulting 32-bit wide key.</returns>
public ProtectedBinary GenerateKey32(byte[] pbKeySeed32, ulong uNumRounds) public ProtectedBinary GenerateKey32(byte[] pbKeySeed32, ulong uNumRounds)
{ {
Debug.Assert(pbKeySeed32 != null); Debug.Assert(pbKeySeed32 != null);
@ -232,18 +217,43 @@ namespace KeePassLib.Keys
Debug.Assert(pbKeySeed32.Length == 32); Debug.Assert(pbKeySeed32.Length == 32);
if(pbKeySeed32.Length != 32) throw new ArgumentException("pbKeySeed32"); 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);
}
/// <summary>
/// Generate a 32-byte (256-bit) key from the composite key.
/// </summary>
public ProtectedBinary GenerateKey32(KdfParameters p)
{
if(p == null) { Debug.Assert(false); throw new ArgumentNullException("p"); }
byte[] pbRaw32 = CreateRawCompositeKey32(); byte[] pbRaw32 = CreateRawCompositeKey32();
if((pbRaw32 == null) || (pbRaw32.Length != 32)) if((pbRaw32 == null) || (pbRaw32.Length != 32))
{ Debug.Assert(false); return null; } { Debug.Assert(false); return null; }
byte[] pbTrf32 = TransformKey(pbRaw32, pbKeySeed32, uNumRounds); KdfEngine kdf = KdfPool.Get(p.KdfUuid);
if((pbTrf32 == null) || (pbTrf32.Length != 32)) if(kdf == null) // CryptographicExceptions are translated to "file corrupted"
{ Debug.Assert(false); return null; } 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); ProtectedBinary pbRet = new ProtectedBinary(true, pbTrf32);
MemUtil.ZeroByteArray(pbTrf32); MemUtil.ZeroByteArray(pbTrf32);
MemUtil.ZeroByteArray(pbRaw32); MemUtil.ZeroByteArray(pbRaw32);
return pbRet; return pbRet;
} }
@ -263,182 +273,6 @@ namespace KeePassLib.Keys
throw new InvalidOperationException(); throw new InvalidOperationException();
} }
} }
/// <summary>
/// Transform the current key <c>uNumRounds</c> times.
/// </summary>
/// <param name="pbOriginalKey32">The original key which will be transformed.
/// This parameter won't be modified.</param>
/// <param name="pbKeySeed32">Seed used for key transformations. Must not
/// be <c>null</c>. This parameter won't be modified.</param>
/// <param name="uNumRounds">Transformation count.</param>
/// <returns>256-bit transformed key.</returns>
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;
}
/// <summary>
/// Benchmark the <c>TransformKey</c> method. Within
/// <paramref name="uMilliseconds"/> ms, random keys will be transformed
/// and the number of performed transformations are returned.
/// </summary>
/// <param name="uMilliseconds">Test duration in ms.</param>
/// <param name="uStep">Stepping.
/// <paramref name="uStep" /> should be a prime number. For fast processors
/// (PCs) a value of <c>3001</c> is recommended, for slower processors (PocketPC)
/// a value of <c>401</c> is recommended.</param>
/// <returns>Number of transformations performed in the specified
/// amount of time. Maximum value is <c>uint.MaxValue</c>.</returns>
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 public sealed class InvalidCompositeKeyException : Exception

View File

@ -22,10 +22,7 @@ using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Text; using System.Text;
#if !KeePassUAP using KeePassLib.Cryptography;
using System.Security.Cryptography;
#endif
using KeePassLib.Security; using KeePassLib.Security;
namespace KeePassLib.Keys namespace KeePassLib.Keys
@ -57,8 +54,7 @@ namespace KeePassLib.Keys
if(bPerformHash) if(bPerformHash)
{ {
SHA256Managed sha256 = new SHA256Managed(); byte[] pbRaw = CryptoUtil.HashSha256(pbKeyData);
byte[] pbRaw = sha256.ComputeHash(pbKeyData);
m_pbKey = new ProtectedBinary(true, pbRaw); m_pbKey = new ProtectedBinary(true, pbRaw);
} }
else m_pbKey = new ProtectedBinary(true, pbKeyData); else m_pbKey = new ProtectedBinary(true, pbKeyData);

View File

@ -133,10 +133,7 @@ namespace KeePassLib.Keys
else if(iLength == 64) pbKey = LoadHexKey32(pbFileData); else if(iLength == 64) pbKey = LoadHexKey32(pbFileData);
if(pbKey == null) if(pbKey == null)
{ pbKey = CryptoUtil.HashSha256(pbFileData);
SHA256Managed sha256 = new SHA256Managed();
pbKey = sha256.ComputeHash(pbFileData);
}
return pbKey; return pbKey;
} }
@ -156,12 +153,15 @@ namespace KeePassLib.Keys
try try
{ {
string strHex = StrUtil.Utf8.GetString(pbFileData, 0, 64); if(!StrUtil.IsHexString(pbFileData, true)) return null;
if(!StrUtil.IsHexString(strHex, true)) return null;
string strHex = StrUtil.Utf8.GetString(pbFileData);
byte[] pbKey = MemUtil.HexStringToByteArray(strHex); byte[] pbKey = MemUtil.HexStringToByteArray(strHex);
if((pbKey == null) || (pbKey.Length != 32)) if((pbKey == null) || (pbKey.Length != 32))
{
Debug.Assert(false);
return null; return null;
}
return pbKey; return pbKey;
} }
@ -189,13 +189,13 @@ namespace KeePassLib.Keys
pbFinalKey32 = pbKey32; pbFinalKey32 = pbKey32;
else else
{ {
MemoryStream ms = new MemoryStream(); using(MemoryStream ms = new MemoryStream())
ms.Write(pbAdditionalEntropy, 0, pbAdditionalEntropy.Length); {
ms.Write(pbKey32, 0, 32); MemUtil.Write(ms, pbAdditionalEntropy);
MemUtil.Write(ms, pbKey32);
SHA256Managed sha256 = new SHA256Managed(); pbFinalKey32 = CryptoUtil.HashSha256(ms.ToArray());
pbFinalKey32 = sha256.ComputeHash(ms.ToArray()); }
ms.Close();
} }
CreateXmlKeyFile(strFilePath, pbFinalKey32); CreateXmlKeyFile(strFilePath, pbFinalKey32);

View File

@ -21,10 +21,7 @@ using System;
using System.Diagnostics; using System.Diagnostics;
using System.Text; using System.Text;
#if !KeePassUAP using KeePassLib.Cryptography;
using System.Security.Cryptography;
#endif
using KeePassLib.Security; using KeePassLib.Security;
using KeePassLib.Utility; using KeePassLib.Utility;
@ -75,8 +72,7 @@ namespace KeePassLib.Keys
Debug.Assert(ValidatePassword(pbPasswordUtf8)); Debug.Assert(ValidatePassword(pbPasswordUtf8));
#endif #endif
SHA256Managed sha256 = new SHA256Managed(); byte[] pbRaw = CryptoUtil.HashSha256(pbPasswordUtf8);
byte[] pbRaw = sha256.ComputeHash(pbPasswordUtf8);
m_psPassword = new ProtectedString(true, pbPasswordUtf8); m_psPassword = new ProtectedString(true, pbPasswordUtf8);
m_pbKeyData = new ProtectedBinary(true, pbRaw); m_pbKeyData = new ProtectedBinary(true, pbRaw);

View File

@ -29,6 +29,7 @@ using System.Drawing;
using KeePassLib.Collections; using KeePassLib.Collections;
using KeePassLib.Cryptography; using KeePassLib.Cryptography;
using KeePassLib.Cryptography.Cipher; using KeePassLib.Cryptography.Cipher;
using KeePassLib.Cryptography.KeyDerivation;
using KeePassLib.Delegates; using KeePassLib.Delegates;
using KeePassLib.Interfaces; using KeePassLib.Interfaces;
using KeePassLib.Keys; using KeePassLib.Keys;
@ -50,13 +51,14 @@ namespace KeePassLib
private static bool m_bPrimaryCreated = false; private static bool m_bPrimaryCreated = false;
// Initializations see Clear() // Initializations: see Clear()
private PwGroup m_pgRootGroup = null; private PwGroup m_pgRootGroup = null;
private PwObjectList<PwDeletedObject> m_vDeletedObjects = new PwObjectList<PwDeletedObject>(); private PwObjectList<PwDeletedObject> m_vDeletedObjects = new PwObjectList<PwDeletedObject>();
private PwUuid m_uuidDataCipher = StandardAesEngine.AesUuid; private PwUuid m_uuidDataCipher = StandardAesEngine.AesUuid;
private PwCompressionAlgorithm m_caCompression = PwCompressionAlgorithm.GZip; 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 CompositeKey m_pwUserKey = null;
private MemoryProtectionConfig m_memProtConfig = new MemoryProtectionConfig(); private MemoryProtectionConfig m_memProtConfig = new MemoryProtectionConfig();
@ -64,6 +66,7 @@ namespace KeePassLib
private List<PwCustomIcon> m_vCustomIcons = new List<PwCustomIcon>(); private List<PwCustomIcon> m_vCustomIcons = new List<PwCustomIcon>();
private bool m_bUINeedsIconUpdate = true; private bool m_bUINeedsIconUpdate = true;
private DateTime m_dtSettingsChanged = PwDefs.DtDefaultNow;
private string m_strName = string.Empty; private string m_strName = string.Empty;
private DateTime m_dtNameChanged = PwDefs.DtDefaultNow; private DateTime m_dtNameChanged = PwDefs.DtDefaultNow;
private string m_strDesc = string.Empty; private string m_strDesc = string.Empty;
@ -93,7 +96,8 @@ namespace KeePassLib
private int m_nHistoryMaxItems = DefaultHistoryMaxItems; private int m_nHistoryMaxItems = DefaultHistoryMaxItems;
private long m_lHistoryMaxSize = DefaultHistoryMaxSize; // In bytes 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_pbHashOfFileOnDisk = null;
private byte[] m_pbHashOfLastIO = null; private byte[] m_pbHashOfLastIO = null;
@ -168,6 +172,12 @@ namespace KeePassLib
} }
} }
public DateTime SettingsChanged
{
get { return m_dtSettingsChanged; }
set { m_dtSettingsChanged = value; }
}
/// <summary> /// <summary>
/// Name of the database. /// Name of the database.
/// </summary> /// </summary>
@ -281,14 +291,23 @@ namespace KeePassLib
set { m_caCompression = value; } set { m_caCompression = value; }
} }
/// <summary> // /// <summary>
/// Number of key transformation rounds (in order to make dictionary // /// Number of key transformation rounds (KDF parameter).
/// attacks harder). // /// </summary>
/// </summary> // public ulong KeyEncryptionRounds
public ulong KeyEncryptionRounds // {
// get { return m_uKeyEncryptionRounds; }
// set { m_uKeyEncryptionRounds = value; }
// }
public KdfParameters KdfParameters
{ {
get { return m_uKeyEncryptionRounds; } get { return m_kdfParams; }
set { m_uKeyEncryptionRounds = value; } set
{
if(value == null) throw new ArgumentNullException("value");
m_kdfParams = value;
}
} }
/// <summary> /// <summary>
@ -408,14 +427,37 @@ namespace KeePassLib
/// <summary> /// <summary>
/// Custom data container that can be used by plugins to store /// Custom data container that can be used by plugins to store
/// own data in KeePass databases. /// 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".
/// </summary> /// </summary>
public StringDictionaryEx CustomData public StringDictionaryEx CustomData
{ {
get { return m_vCustomData; } get { return m_dCustomData; }
set internal set
{ {
Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); if(value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); }
m_vCustomData = value; m_dCustomData = value;
}
}
/// <summary>
/// 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 <c>CustomData</c> instead,
/// if possible.
/// Use unique names for your items, e.g. "PluginName_ItemName".
/// </summary>
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_uuidDataCipher = StandardAesEngine.AesUuid;
m_caCompression = PwCompressionAlgorithm.GZip; m_caCompression = PwCompressionAlgorithm.GZip;
m_uKeyEncryptionRounds = PwDefs.DefaultKeyEncryptionRounds; // m_uKeyEncryptionRounds = PwDefs.DefaultKeyEncryptionRounds;
m_kdfParams = KdfPool.GetDefaultParameters();
m_pwUserKey = null; m_pwUserKey = null;
m_memProtConfig = new MemoryProtectionConfig(); m_memProtConfig = new MemoryProtectionConfig();
@ -494,6 +537,7 @@ namespace KeePassLib
DateTime dtNow = DateTime.Now; DateTime dtNow = DateTime.Now;
m_dtSettingsChanged = dtNow;
m_strName = string.Empty; m_strName = string.Empty;
m_dtNameChanged = dtNow; m_dtNameChanged = dtNow;
m_strDesc = string.Empty; m_strDesc = string.Empty;
@ -523,7 +567,8 @@ namespace KeePassLib
m_nHistoryMaxItems = DefaultHistoryMaxItems; m_nHistoryMaxItems = DefaultHistoryMaxItems;
m_lHistoryMaxSize = DefaultHistoryMaxSize; m_lHistoryMaxSize = DefaultHistoryMaxSize;
m_vCustomData = new StringDictionaryEx(); m_dCustomData = new StringDictionaryEx();
m_dPublicCustomData = new VariantDictionary();
m_pbHashOfFileOnDisk = null; m_pbHashOfFileOnDisk = null;
m_pbHashOfLastIO = null; m_pbHashOfLastIO = null;
@ -1393,6 +1438,14 @@ namespace KeePassLib
return; return;
bool bForce = (mm == PwMergeMethod.OverwriteExisting); 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)) if(bForce || (pdSource.m_dtNameChanged > m_dtNameChanged))
{ {
@ -1412,8 +1465,6 @@ namespace KeePassLib
m_dtDefaultUserChanged = pdSource.m_dtDefaultUserChanged; m_dtDefaultUserChanged = pdSource.m_dtDefaultUserChanged;
} }
if(bForce) m_clr = pdSource.m_clr;
PwUuid pwPrefBin = m_pwRecycleBin, pwAltBin = pdSource.m_pwRecycleBin; PwUuid pwPrefBin = m_pwRecycleBin, pwAltBin = pdSource.m_pwRecycleBin;
if(bForce || (pdSource.m_dtRecycleBinChanged > m_dtRecycleBinChanged)) if(bForce || (pdSource.m_dtRecycleBinChanged > m_dtRecycleBinChanged))
{ {
@ -1440,6 +1491,16 @@ namespace KeePassLib
else if(m_pgRootGroup.FindGroup(pwAltTmp, true) != null) else if(m_pgRootGroup.FindGroup(pwAltTmp, true) != null)
m_pwEntryTemplatesGroup = pwAltTmp; m_pwEntryTemplatesGroup = pwAltTmp;
else m_pwEntryTemplatesGroup = PwUuid.Zero; // Debug.Assert(false); else m_pwEntryTemplatesGroup = PwUuid.Zero; // Debug.Assert(false);
foreach(KeyValuePair<string, string> 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, private void MergeEntryHistory(PwEntry pe, PwEntry peSource,

View File

@ -65,6 +65,8 @@ namespace KeePassLib
private List<string> m_vTags = new List<string>(); private List<string> m_vTags = new List<string>();
private StringDictionaryEx m_dCustomData = new StringDictionaryEx();
/// <summary> /// <summary>
/// UUID of this entry. /// UUID of this entry.
/// </summary> /// </summary>
@ -274,6 +276,23 @@ namespace KeePassLib
} }
} }
/// <summary>
/// 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".
/// </summary>
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<ObjectTouchedEventArgs> EntryTouched; public static EventHandler<ObjectTouchedEventArgs> EntryTouched;
public EventHandler<ObjectTouchedEventArgs> Touched; public EventHandler<ObjectTouchedEventArgs> Touched;
@ -366,6 +385,8 @@ namespace KeePassLib
peNew.m_vTags = new List<string>(m_vTags); peNew.m_vTags = new List<string>(m_vTags);
peNew.m_dCustomData = m_dCustomData.CloneDeep();
return peNew; return peNew;
} }
@ -486,6 +507,8 @@ namespace KeePassLib
if(m_vTags[iTag] != pe.m_vTags[iTag]) return false; if(m_vTags[iTag] != pe.m_vTags[iTag]) return false;
} }
if(!m_dCustomData.Equals(pe.m_dCustomData)) return false;
return true; return true;
} }
@ -502,10 +525,10 @@ namespace KeePassLib
public void AssignProperties(PwEntry peTemplate, bool bOnlyIfNewer, public void AssignProperties(PwEntry peTemplate, bool bOnlyIfNewer,
bool bIncludeHistory, bool bAssignLocationChanged) 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, if(bOnlyIfNewer && (TimeUtil.Compare(peTemplate.m_tLastMod,
true) < 0)) m_tLastMod, true) < 0))
return; return;
// Template UUID should be the same as the current one // Template UUID should be the same as the current one
@ -515,10 +538,11 @@ namespace KeePassLib
if(bAssignLocationChanged) if(bAssignLocationChanged)
m_tParentGroupLastMod = peTemplate.m_tParentGroupLastMod; m_tParentGroupLastMod = peTemplate.m_tParentGroupLastMod;
m_listStrings = peTemplate.m_listStrings; m_listStrings = peTemplate.m_listStrings.CloneDeep();
m_listBinaries = peTemplate.m_listBinaries; m_listBinaries = peTemplate.m_listBinaries.CloneDeep();
m_listAutoType = peTemplate.m_listAutoType; m_listAutoType = peTemplate.m_listAutoType.CloneDeep();
if(bIncludeHistory) m_listHistory = peTemplate.m_listHistory; if(bIncludeHistory)
m_listHistory = peTemplate.m_listHistory.CloneDeep();
m_pwIcon = peTemplate.m_pwIcon; m_pwIcon = peTemplate.m_pwIcon;
m_pwCustomIconID = peTemplate.m_pwCustomIconID; // Immutable m_pwCustomIconID = peTemplate.m_pwCustomIconID; // Immutable
@ -536,6 +560,8 @@ namespace KeePassLib
m_strOverrideUrl = peTemplate.m_strOverrideUrl; m_strOverrideUrl = peTemplate.m_strOverrideUrl;
m_vTags = new List<string>(peTemplate.m_vTags); m_vTags = new List<string>(peTemplate.m_vTags);
m_dCustomData = peTemplate.m_dCustomData.CloneDeep();
} }
/// <summary> /// <summary>
@ -787,6 +813,9 @@ namespace KeePassLib
foreach(string strTag in m_vTags) foreach(string strTag in m_vTags)
uSize += (ulong)strTag.Length; uSize += (ulong)strTag.Length;
foreach(KeyValuePair<string, string> kvp in m_dCustomData)
uSize += (ulong)kvp.Key.Length + (ulong)kvp.Value.Length;
return uSize; return uSize;
} }

View File

@ -67,6 +67,8 @@ namespace KeePassLib
private PwUuid m_pwLastTopVisibleEntry = PwUuid.Zero; private PwUuid m_pwLastTopVisibleEntry = PwUuid.Zero;
private StringDictionaryEx m_dCustomData = new StringDictionaryEx();
/// <summary> /// <summary>
/// UUID of this group. /// UUID of this group.
/// </summary> /// </summary>
@ -281,6 +283,23 @@ namespace KeePassLib
} }
} }
/// <summary>
/// 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".
/// </summary>
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<ObjectTouchedEventArgs> GroupTouched; public static EventHandler<ObjectTouchedEventArgs> GroupTouched;
public EventHandler<ObjectTouchedEventArgs> Touched; public EventHandler<ObjectTouchedEventArgs> Touched;
@ -376,6 +395,8 @@ namespace KeePassLib
pg.m_pwLastTopVisibleEntry = m_pwLastTopVisibleEntry; pg.m_pwLastTopVisibleEntry = m_pwLastTopVisibleEntry;
pg.m_dCustomData = m_dCustomData.CloneDeep();
return pg; return pg;
} }
@ -444,6 +465,8 @@ namespace KeePassLib
if(!m_pwLastTopVisibleEntry.Equals(pg.m_pwLastTopVisibleEntry)) return false; 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((pwOpt & PwCompareOptions.PropertiesOnly) == PwCompareOptions.None)
{ {
if(m_listEntries.UCount != pg.m_listEntries.UCount) return false; if(m_listEntries.UCount != pg.m_listEntries.UCount) return false;
@ -509,6 +532,8 @@ namespace KeePassLib
m_bEnableSearching = pgTemplate.m_bEnableSearching; m_bEnableSearching = pgTemplate.m_bEnableSearching;
m_pwLastTopVisibleEntry = pgTemplate.m_pwLastTopVisibleEntry; m_pwLastTopVisibleEntry = pgTemplate.m_pwLastTopVisibleEntry;
m_dCustomData = pgTemplate.m_dCustomData.CloneDeep();
} }
/// <summary> /// <summary>

View File

@ -27,7 +27,7 @@ namespace KeePassLib.Resources
if(dictNew == null) throw new ArgumentNullException("dictNew"); if(dictNew == null) throw new ArgumentNullException("dictNew");
m_strCryptoStreamFailed = TryGetEx(dictNew, "CryptoStreamFailed", m_strCryptoStreamFailed); 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_strErrorInClipboard = TryGetEx(dictNew, "ErrorInClipboard", m_strErrorInClipboard);
m_strExpect100Continue = TryGetEx(dictNew, "Expect100Continue", m_strExpect100Continue); m_strExpect100Continue = TryGetEx(dictNew, "Expect100Continue", m_strExpect100Continue);
m_strFatalError = TryGetEx(dictNew, "FatalError", m_strFatalError); m_strFatalError = TryGetEx(dictNew, "FatalError", m_strFatalError);
@ -36,6 +36,7 @@ namespace KeePassLib.Resources
m_strFileHeaderEndEarly = TryGetEx(dictNew, "FileHeaderEndEarly", m_strFileHeaderEndEarly); m_strFileHeaderEndEarly = TryGetEx(dictNew, "FileHeaderEndEarly", m_strFileHeaderEndEarly);
m_strFileLoadFailed = TryGetEx(dictNew, "FileLoadFailed", m_strFileLoadFailed); m_strFileLoadFailed = TryGetEx(dictNew, "FileLoadFailed", m_strFileLoadFailed);
m_strFileLockedWrite = TryGetEx(dictNew, "FileLockedWrite", m_strFileLockedWrite); m_strFileLockedWrite = TryGetEx(dictNew, "FileLockedWrite", m_strFileLockedWrite);
m_strFileNewVerOrPlgReq = TryGetEx(dictNew, "FileNewVerOrPlgReq", m_strFileNewVerOrPlgReq);
m_strFileNewVerReq = TryGetEx(dictNew, "FileNewVerReq", m_strFileNewVerReq); m_strFileNewVerReq = TryGetEx(dictNew, "FileNewVerReq", m_strFileNewVerReq);
m_strFileSaveCorruptionWarning = TryGetEx(dictNew, "FileSaveCorruptionWarning", m_strFileSaveCorruptionWarning); m_strFileSaveCorruptionWarning = TryGetEx(dictNew, "FileSaveCorruptionWarning", m_strFileSaveCorruptionWarning);
m_strFileSaveFailed = TryGetEx(dictNew, "FileSaveFailed", m_strFileSaveFailed); m_strFileSaveFailed = TryGetEx(dictNew, "FileSaveFailed", m_strFileSaveFailed);
@ -50,6 +51,7 @@ namespace KeePassLib.Resources
m_strInvalidCompositeKeyHint = TryGetEx(dictNew, "InvalidCompositeKeyHint", m_strInvalidCompositeKeyHint); m_strInvalidCompositeKeyHint = TryGetEx(dictNew, "InvalidCompositeKeyHint", m_strInvalidCompositeKeyHint);
m_strInvalidDataWhileDecoding = TryGetEx(dictNew, "InvalidDataWhileDecoding", m_strInvalidDataWhileDecoding); m_strInvalidDataWhileDecoding = TryGetEx(dictNew, "InvalidDataWhileDecoding", m_strInvalidDataWhileDecoding);
m_strKeePass1xHint = TryGetEx(dictNew, "KeePass1xHint", m_strKeePass1xHint); m_strKeePass1xHint = TryGetEx(dictNew, "KeePass1xHint", m_strKeePass1xHint);
m_strKeyBits = TryGetEx(dictNew, "KeyBits", m_strKeyBits);
m_strKeyFileDbSel = TryGetEx(dictNew, "KeyFileDbSel", m_strKeyFileDbSel); m_strKeyFileDbSel = TryGetEx(dictNew, "KeyFileDbSel", m_strKeyFileDbSel);
m_strMasterSeedLengthInvalid = TryGetEx(dictNew, "MasterSeedLengthInvalid", m_strMasterSeedLengthInvalid); m_strMasterSeedLengthInvalid = TryGetEx(dictNew, "MasterSeedLengthInvalid", m_strMasterSeedLengthInvalid);
m_strOldFormat = TryGetEx(dictNew, "OldFormat", m_strOldFormat); m_strOldFormat = TryGetEx(dictNew, "OldFormat", m_strOldFormat);
@ -58,13 +60,14 @@ namespace KeePassLib.Resources
m_strTimeout = TryGetEx(dictNew, "Timeout", m_strTimeout); m_strTimeout = TryGetEx(dictNew, "Timeout", m_strTimeout);
m_strTryAgainSecs = TryGetEx(dictNew, "TryAgainSecs", m_strTryAgainSecs); m_strTryAgainSecs = TryGetEx(dictNew, "TryAgainSecs", m_strTryAgainSecs);
m_strUnknownHeaderId = TryGetEx(dictNew, "UnknownHeaderId", m_strUnknownHeaderId); m_strUnknownHeaderId = TryGetEx(dictNew, "UnknownHeaderId", m_strUnknownHeaderId);
m_strUnknownKdf = TryGetEx(dictNew, "UnknownKdf", m_strUnknownKdf);
m_strUserAccountKeyError = TryGetEx(dictNew, "UserAccountKeyError", m_strUserAccountKeyError); m_strUserAccountKeyError = TryGetEx(dictNew, "UserAccountKeyError", m_strUserAccountKeyError);
m_strUserAgent = TryGetEx(dictNew, "UserAgent", m_strUserAgent); m_strUserAgent = TryGetEx(dictNew, "UserAgent", m_strUserAgent);
} }
private static readonly string[] m_vKeyNames = { private static readonly string[] m_vKeyNames = {
"CryptoStreamFailed", "CryptoStreamFailed",
"EncAlgorithmAes", "EncDataTooLarge",
"ErrorInClipboard", "ErrorInClipboard",
"Expect100Continue", "Expect100Continue",
"FatalError", "FatalError",
@ -73,6 +76,7 @@ namespace KeePassLib.Resources
"FileHeaderEndEarly", "FileHeaderEndEarly",
"FileLoadFailed", "FileLoadFailed",
"FileLockedWrite", "FileLockedWrite",
"FileNewVerOrPlgReq",
"FileNewVerReq", "FileNewVerReq",
"FileSaveCorruptionWarning", "FileSaveCorruptionWarning",
"FileSaveFailed", "FileSaveFailed",
@ -87,6 +91,7 @@ namespace KeePassLib.Resources
"InvalidCompositeKeyHint", "InvalidCompositeKeyHint",
"InvalidDataWhileDecoding", "InvalidDataWhileDecoding",
"KeePass1xHint", "KeePass1xHint",
"KeyBits",
"KeyFileDbSel", "KeyFileDbSel",
"MasterSeedLengthInvalid", "MasterSeedLengthInvalid",
"OldFormat", "OldFormat",
@ -95,6 +100,7 @@ namespace KeePassLib.Resources
"Timeout", "Timeout",
"TryAgainSecs", "TryAgainSecs",
"UnknownHeaderId", "UnknownHeaderId",
"UnknownKdf",
"UserAccountKeyError", "UserAccountKeyError",
"UserAgent" "UserAgent"
}; };
@ -115,15 +121,15 @@ namespace KeePassLib.Resources
get { return m_strCryptoStreamFailed; } get { return m_strCryptoStreamFailed; }
} }
private static string m_strEncAlgorithmAes = private static string m_strEncDataTooLarge =
@"AES/Rijndael (256-Bit Key)"; @"The data is too large to be encrypted/decrypted securely using {PARAM}.";
/// <summary> /// <summary>
/// Look up a localized string similar to /// Look up a localized string similar to
/// 'AES/Rijndael (256-Bit Key)'. /// 'The data is too large to be encrypted/decrypted securely using {PARAM}.'.
/// </summary> /// </summary>
public static string EncAlgorithmAes public static string EncDataTooLarge
{ {
get { return m_strEncAlgorithmAes; } get { return m_strEncDataTooLarge; }
} }
private static string m_strErrorInClipboard = private static string m_strErrorInClipboard =
@ -214,6 +220,17 @@ namespace KeePassLib.Resources
get { return m_strFileLockedWrite; } get { return m_strFileLockedWrite; }
} }
private static string m_strFileNewVerOrPlgReq =
@"A newer KeePass version or a plugin is required to open this file.";
/// <summary>
/// Look up a localized string similar to
/// 'A newer KeePass version or a plugin is required to open this file.'.
/// </summary>
public static string FileNewVerOrPlgReq
{
get { return m_strFileNewVerOrPlgReq; }
}
private static string m_strFileNewVerReq = private static string m_strFileNewVerReq =
@"A newer KeePass version is required to open this file."; @"A newer KeePass version is required to open this file.";
/// <summary> /// <summary>
@ -368,6 +385,17 @@ namespace KeePassLib.Resources
get { return m_strKeePass1xHint; } get { return m_strKeePass1xHint; }
} }
private static string m_strKeyBits =
@"{PARAM}-bit key";
/// <summary>
/// Look up a localized string similar to
/// '{PARAM}-bit key'.
/// </summary>
public static string KeyBits
{
get { return m_strKeyBits; }
}
private static string m_strKeyFileDbSel = private static string m_strKeyFileDbSel =
@"Database files cannot be used as key files."; @"Database files cannot be used as key files.";
/// <summary> /// <summary>
@ -456,6 +484,17 @@ namespace KeePassLib.Resources
get { return m_strUnknownHeaderId; } get { return m_strUnknownHeaderId; }
} }
private static string m_strUnknownKdf =
@"Unknown key derivation function!";
/// <summary>
/// Look up a localized string similar to
/// 'Unknown key derivation function!'.
/// </summary>
public static string UnknownKdf
{
get { return m_strUnknownKdf; }
}
private static string m_strUserAccountKeyError = 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."; @"The operating system did not grant KeePass read/write access to the user profile folder, where the protected user key is stored.";
/// <summary> /// <summary>

View File

@ -76,7 +76,7 @@ namespace KeePassLib.Security
{ {
None = 0, None = 0,
ProtectedMemory, ProtectedMemory,
Salsa20, ChaCha20,
ExtCrypt ExtCrypt
} }
@ -166,7 +166,7 @@ namespace KeePassLib.Security
/// </summary> /// </summary>
public ProtectedBinary() public ProtectedBinary()
{ {
Init(false, new byte[0]); Init(false, MemUtil.EmptyByteArray);
} }
/// <summary> /// <summary>
@ -263,11 +263,13 @@ namespace KeePassLib.Security
if(pbUpd != null) pbKey32 = pbUpd; if(pbUpd != null) pbKey32 = pbUpd;
} }
Salsa20Cipher s = new Salsa20Cipher(pbKey32, byte[] pbIV = new byte[12];
BitConverter.GetBytes(m_lID)); MemUtil.UInt64ToBytesEx((ulong)m_lID, pbIV, 4);
s.Encrypt(m_pbData, m_pbData.Length, true); using(ChaCha20Cipher c = new ChaCha20Cipher(pbKey32, pbIV, true))
s.Dispose(); {
m_mp = PbMemProt.Salsa20; c.Encrypt(m_pbData, 0, m_pbData.Length);
}
m_mp = PbMemProt.ChaCha20;
} }
private void Decrypt() private void Decrypt()
@ -276,12 +278,14 @@ namespace KeePassLib.Security
if(m_mp == PbMemProt.ProtectedMemory) if(m_mp == PbMemProt.ProtectedMemory)
ProtectedMemory.Unprotect(m_pbData, MemoryProtectionScope.SameProcess); ProtectedMemory.Unprotect(m_pbData, MemoryProtectionScope.SameProcess);
else if(m_mp == PbMemProt.Salsa20) else if(m_mp == PbMemProt.ChaCha20)
{ {
Salsa20Cipher s = new Salsa20Cipher(g_pbKey32, byte[] pbIV = new byte[12];
BitConverter.GetBytes(m_lID)); MemUtil.UInt64ToBytesEx((ulong)m_lID, pbIV, 4);
s.Encrypt(m_pbData, m_pbData.Length, true); using(ChaCha20Cipher c = new ChaCha20Cipher(g_pbKey32, pbIV, true))
s.Dispose(); {
c.Decrypt(m_pbData, 0, m_pbData.Length);
}
} }
else if(m_mp == PbMemProt.ExtCrypt) else if(m_mp == PbMemProt.ExtCrypt)
m_fExtCrypt(m_pbData, PbCryptFlags.Decrypt, m_lID); m_fExtCrypt(m_pbData, PbCryptFlags.Decrypt, m_lID);
@ -300,7 +304,7 @@ namespace KeePassLib.Security
/// protected data and can therefore be cleared safely.</returns> /// protected data and can therefore be cleared safely.</returns>
public byte[] ReadData() public byte[] ReadData()
{ {
if(m_uDataLen == 0) return new byte[0]; if(m_uDataLen == 0) return MemUtil.EmptyByteArray;
byte[] pbReturn = new byte[m_uDataLen]; byte[] pbReturn = new byte[m_uDataLen];

View File

@ -152,7 +152,7 @@ namespace KeePassLib.Serialization
try try
{ {
byte[] pbID = CryptoRandom.Instance.GetRandomBytes(16); 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, lfi = new LockFileInfo(Convert.ToBase64String(pbID), strTime,
#if KeePassUAP #if KeePassUAP

View File

@ -22,10 +22,7 @@ using System.Diagnostics;
using System.IO; using System.IO;
using System.Text; using System.Text;
#if !KeePassUAP using KeePassLib.Cryptography;
using System.Security.Cryptography;
#endif
using KeePassLib.Native; using KeePassLib.Native;
using KeePassLib.Utility; using KeePassLib.Utility;
@ -37,7 +34,7 @@ namespace KeePassLib.Serialization
{ {
public sealed class HashedBlockStream : Stream 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 Stream m_sBaseStream;
private bool m_bWriting; private bool m_bWriting;
@ -50,7 +47,7 @@ namespace KeePassLib.Serialization
private byte[] m_pbBuffer; private byte[] m_pbBuffer;
private int m_nBufferPos = 0; private int m_nBufferPos = 0;
private uint m_uBufferIndex = 0; private uint m_uBlockIndex = 0;
public override bool CanRead public override bool CanRead
{ {
@ -69,13 +66,13 @@ namespace KeePassLib.Serialization
public override long Length public override long Length
{ {
get { throw new NotSupportedException(); } get { Debug.Assert(false); throw new NotSupportedException(); }
} }
public override long Position public override long Position
{ {
get { throw new NotSupportedException(); } get { Debug.Assert(false); throw new NotSupportedException(); }
set { throw new NotSupportedException(); } set { Debug.Assert(false); throw new NotSupportedException(); }
} }
public HashedBlockStream(Stream sBaseStream, bool bWriting) public HashedBlockStream(Stream sBaseStream, bool bWriting)
@ -100,7 +97,7 @@ namespace KeePassLib.Serialization
if(sBaseStream == null) throw new ArgumentNullException("sBaseStream"); if(sBaseStream == null) throw new ArgumentNullException("sBaseStream");
if(nBufferSize < 0) throw new ArgumentOutOfRangeException("nBufferSize"); if(nBufferSize < 0) throw new ArgumentOutOfRangeException("nBufferSize");
if(nBufferSize == 0) nBufferSize = m_nDefaultBufferSize; if(nBufferSize == 0) nBufferSize = NbDefaultBufferSize;
m_sBaseStream = sBaseStream; m_sBaseStream = sBaseStream;
m_bWriting = bWriting; m_bWriting = bWriting;
@ -114,7 +111,7 @@ namespace KeePassLib.Serialization
m_brInput = new BinaryReader(sBaseStream, utf8); m_brInput = new BinaryReader(sBaseStream, utf8);
m_pbBuffer = new byte[0]; m_pbBuffer = MemUtil.EmptyByteArray;
} }
else // Writing mode else // Writing mode
{ {
@ -142,7 +139,7 @@ namespace KeePassLib.Serialization
#endif #endif
if(m_sBaseStream != null) if(m_sBaseStream != null)
{ {
if(m_bWriting == false) // Reading mode if(!m_bWriting) // Reading mode
{ {
m_brInput.Close(); m_brInput.Close();
m_brInput = null; m_brInput = null;
@ -209,9 +206,9 @@ namespace KeePassLib.Serialization
m_nBufferPos = 0; m_nBufferPos = 0;
if(m_brInput.ReadUInt32() != m_uBufferIndex) if(m_brInput.ReadUInt32() != m_uBlockIndex)
throw new InvalidDataException(); throw new InvalidDataException();
++m_uBufferIndex; ++m_uBlockIndex;
byte[] pbStoredHash = m_brInput.ReadBytes(32); byte[] pbStoredHash = m_brInput.ReadBytes(32);
if((pbStoredHash == null) || (pbStoredHash.Length != 32)) if((pbStoredHash == null) || (pbStoredHash.Length != 32))
@ -236,7 +233,7 @@ namespace KeePassLib.Serialization
} }
m_bEos = true; m_bEos = true;
m_pbBuffer = new byte[0]; m_pbBuffer = MemUtil.EmptyByteArray;
return false; return false;
} }
@ -246,16 +243,12 @@ namespace KeePassLib.Serialization
if(m_bVerify) if(m_bVerify)
{ {
SHA256Managed sha256 = new SHA256Managed(); byte[] pbComputedHash = CryptoUtil.HashSha256(m_pbBuffer);
byte[] pbComputedHash = sha256.ComputeHash(m_pbBuffer);
if((pbComputedHash == null) || (pbComputedHash.Length != 32)) if((pbComputedHash == null) || (pbComputedHash.Length != 32))
throw new InvalidOperationException(); throw new InvalidOperationException();
for(int iHashPos = 0; iHashPos < 32; ++iHashPos) if(!MemUtil.ArraysEqual(pbStoredHash, pbComputedHash))
{ throw new InvalidDataException();
if(pbStoredHash[iHashPos] != pbComputedHash[iHashPos])
throw new InvalidDataException();
}
} }
return true; return true;
@ -283,26 +276,24 @@ namespace KeePassLib.Serialization
private void WriteHashedBlock() private void WriteHashedBlock()
{ {
m_bwOutput.Write(m_uBufferIndex); m_bwOutput.Write(m_uBlockIndex);
++m_uBufferIndex; ++m_uBlockIndex;
if(m_nBufferPos > 0) if(m_nBufferPos > 0)
{ {
SHA256Managed sha256 = new SHA256Managed(); byte[] pbHash = CryptoUtil.HashSha256(m_pbBuffer, 0, m_nBufferPos);
#if !KeePassLibSD // For KeePassLibSD:
byte[] pbHash = sha256.ComputeHash(m_pbBuffer, 0, m_nBufferPos); // SHA256Managed sha256 = new SHA256Managed();
#else // byte[] pbHash;
byte[] pbHash; // if(m_nBufferPos == m_pbBuffer.Length)
if(m_nBufferPos == m_pbBuffer.Length) // pbHash = sha256.ComputeHash(m_pbBuffer);
pbHash = sha256.ComputeHash(m_pbBuffer); // else
else // {
{ // byte[] pbData = new byte[m_nBufferPos];
byte[] pbData = new byte[m_nBufferPos]; // Array.Copy(m_pbBuffer, 0, pbData, 0, m_nBufferPos);
Array.Copy(m_pbBuffer, 0, pbData, 0, m_nBufferPos); // pbHash = sha256.ComputeHash(pbData);
pbHash = sha256.ComputeHash(pbData); // }
}
#endif
m_bwOutput.Write(pbHash); m_bwOutput.Write(pbHash);
} }

View File

@ -0,0 +1,325 @@
/*
KeePass Password Safe - The Open-Source Password Manager
Copyright (C) 2003-2016 Dominik Reichl <dominik.reichl@t-online.de>
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;
}
}
}

View File

@ -58,13 +58,17 @@ namespace KeePassLib.Serialization
DeletedObject, DeletedObject,
Group, Group,
GroupTimes, GroupTimes,
GroupCustomData,
GroupCustomDataItem,
Entry, Entry,
EntryTimes, EntryTimes,
EntryString, EntryString,
EntryBinary, EntryBinary,
EntryAutoType, EntryAutoType,
EntryAutoTypeItem, EntryAutoTypeItem,
EntryHistory EntryHistory,
EntryCustomData,
EntryCustomDataItem
} }
private bool m_bReadNextNode = true; private bool m_bReadNextNode = true;
@ -84,10 +88,14 @@ namespace KeePassLib.Serialization
private byte[] m_pbCustomIconData = null; private byte[] m_pbCustomIconData = null;
private string m_strCustomDataKey = null; private string m_strCustomDataKey = null;
private string m_strCustomDataValue = 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() internal static XmlReaderSettings CreateStdXmlReaderSettings()
@ -215,15 +223,25 @@ namespace KeePassLib.Serialization
ReadString(xr); // Ignore ReadString(xr); // Ignore
else if(xr.Name == ElemHeaderHash) 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); string strHash = ReadString(xr);
if(!string.IsNullOrEmpty(strHash) && (m_pbHashOfHeader != null) && if(!string.IsNullOrEmpty(strHash) && (m_pbHashOfHeader != null) &&
!m_bRepairMode) !m_bRepairMode)
{ {
Debug.Assert(m_uFileVersion <= FileVersion32_3);
byte[] pbHash = Convert.FromBase64String(strHash); byte[] pbHash = Convert.FromBase64String(strHash);
if(!MemUtil.ArraysEqual(pbHash, m_pbHashOfHeader)) if(!MemUtil.ArraysEqual(pbHash, m_pbHashOfHeader))
throw new IOException(KLRes.FileCorrupted); throw new IOException(KLRes.FileCorrupted);
} }
} }
else if(xr.Name == ElemSettingsChanged)
m_pwDatabase.SettingsChanged = ReadTime(xr);
else if(xr.Name == ElemDbName) else if(xr.Name == ElemDbName)
m_pwDatabase.Name = ReadString(xr); m_pwDatabase.Name = ReadString(xr);
else if(xr.Name == ElemDbNameChanged) else if(xr.Name == ElemDbNameChanged)
@ -383,6 +401,8 @@ namespace KeePassLib.Serialization
m_ctxGroup.EnableSearching = StrUtil.StringToBoolEx(ReadString(xr)); m_ctxGroup.EnableSearching = StrUtil.StringToBoolEx(ReadString(xr));
else if(xr.Name == ElemLastTopVisibleEntry) else if(xr.Name == ElemLastTopVisibleEntry)
m_ctxGroup.LastTopVisibleEntry = ReadUuid(xr); m_ctxGroup.LastTopVisibleEntry = ReadUuid(xr);
else if(xr.Name == ElemCustomData)
return SwitchContext(ctx, KdbContext.GroupCustomData, xr);
else if(xr.Name == ElemGroup) else if(xr.Name == ElemGroup)
{ {
m_ctxGroup = new PwGroup(false, false); m_ctxGroup = new PwGroup(false, false);
@ -403,6 +423,20 @@ namespace KeePassLib.Serialization
else ReadUnknown(xr); else ReadUnknown(xr);
break; 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: case KdbContext.Entry:
if(xr.Name == ElemUuid) if(xr.Name == ElemUuid)
m_ctxEntry.Uuid = ReadUuid(xr); m_ctxEntry.Uuid = ReadUuid(xr);
@ -434,6 +468,8 @@ namespace KeePassLib.Serialization
return SwitchContext(ctx, KdbContext.EntryBinary, xr); return SwitchContext(ctx, KdbContext.EntryBinary, xr);
else if(xr.Name == ElemAutoType) else if(xr.Name == ElemAutoType)
return SwitchContext(ctx, KdbContext.EntryAutoType, xr); return SwitchContext(ctx, KdbContext.EntryAutoType, xr);
else if(xr.Name == ElemCustomData)
return SwitchContext(ctx, KdbContext.EntryCustomData, xr);
else if(xr.Name == ElemHistory) else if(xr.Name == ElemHistory)
{ {
Debug.Assert(m_bEntryInHistory == false); Debug.Assert(m_bEntryInHistory == false);
@ -508,6 +544,20 @@ namespace KeePassLib.Serialization
else ReadUnknown(xr); else ReadUnknown(xr);
break; 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: case KdbContext.EntryHistory:
if(xr.Name == ElemEntry) if(xr.Name == ElemEntry)
{ {
@ -609,6 +659,19 @@ namespace KeePassLib.Serialization
} }
else if((ctx == KdbContext.GroupTimes) && (xr.Name == ElemTimes)) else if((ctx == KdbContext.GroupTimes) && (xr.Name == ElemTimes))
return KdbContext.Group; 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)) else if((ctx == KdbContext.Entry) && (xr.Name == ElemEntry))
{ {
// Create new UUID if absent // Create new UUID if absent
@ -659,6 +722,19 @@ namespace KeePassLib.Serialization
m_ctxATSeq = null; m_ctxATSeq = null;
return KdbContext.EntryAutoType; 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)) else if((ctx == KdbContext.EntryHistory) && (xr.Name == ElemHistory))
{ {
m_bEntryInHistory = false; m_bEntryInHistory = false;
@ -883,7 +959,7 @@ namespace KeePassLib.Serialization
byte[] pbEncrypted; byte[] pbEncrypted;
if(strEncrypted.Length > 0) if(strEncrypted.Length > 0)
pbEncrypted = Convert.FromBase64String(strEncrypted); pbEncrypted = Convert.FromBase64String(strEncrypted);
else pbEncrypted = new byte[0]; else pbEncrypted = MemUtil.EmptyByteArray;
byte[] pbPad = m_randomStream.GetRandomBytes((uint)pbEncrypted.Length); byte[] pbPad = m_randomStream.GetRandomBytes((uint)pbEncrypted.Length);

View File

@ -35,8 +35,10 @@ using System.IO.Compression;
using KeePassLibSD; using KeePassLibSD;
#endif #endif
using KeePassLib.Collections;
using KeePassLib.Cryptography; using KeePassLib.Cryptography;
using KeePassLib.Cryptography.Cipher; using KeePassLib.Cryptography.Cipher;
using KeePassLib.Cryptography.KeyDerivation;
using KeePassLib.Interfaces; using KeePassLib.Interfaces;
using KeePassLib.Keys; using KeePassLib.Keys;
using KeePassLib.Resources; using KeePassLib.Resources;
@ -50,74 +52,108 @@ namespace KeePassLib.Serialization
public sealed partial class KdbxFile public sealed partial class KdbxFile
{ {
/// <summary> /// <summary>
/// Load a KDB file from a file. /// Load a KDBX file.
/// </summary> /// </summary>
/// <param name="strFilePath">File to load.</param> /// <param name="strFilePath">File to load.</param>
/// <param name="kdbFormat">Format specifier.</param> /// <param name="fmt">Format.</param>
/// <param name="slLogger">Status logger (optional).</param> /// <param name="slLogger">Status logger (optional).</param>
public void Load(string strFilePath, KdbxFormat kdbFormat, IStatusLogger slLogger) public void Load(string strFilePath, KdbxFormat fmt, IStatusLogger slLogger)
{ {
IOConnectionInfo ioc = IOConnectionInfo.FromPath(strFilePath); IOConnectionInfo ioc = IOConnectionInfo.FromPath(strFilePath);
Load(IOConnection.OpenRead(ioc), kdbFormat, slLogger); Load(IOConnection.OpenRead(ioc), fmt, slLogger);
} }
/// <summary> /// <summary>
/// Load a KDB file from a stream. /// Load a KDBX file from a stream.
/// </summary> /// </summary>
/// <param name="sSource">Stream to read the data from. Must contain /// <param name="sSource">Stream to read the data from. Must contain
/// a KDBX stream.</param> /// a KDBX stream.</param>
/// <param name="kdbFormat">Format specifier.</param> /// <param name="fmt">Format.</param>
/// <param name="slLogger">Status logger (optional).</param> /// <param name="slLogger">Status logger (optional).</param>
public void Load(Stream sSource, KdbxFormat kdbFormat, IStatusLogger slLogger) public void Load(Stream sSource, KdbxFormat fmt, IStatusLogger slLogger)
{ {
Debug.Assert(sSource != null); Debug.Assert(sSource != null);
if(sSource == null) throw new ArgumentNullException("sSource"); if(sSource == null) throw new ArgumentNullException("sSource");
m_format = kdbFormat; m_format = fmt;
m_slLogger = slLogger; m_slLogger = slLogger;
HashingStreamEx hashedStream = new HashingStreamEx(sSource, false, null);
UTF8Encoding encNoBom = StrUtil.Utf8; UTF8Encoding encNoBom = StrUtil.Utf8;
byte[] pbCipherKey = null;
byte[] pbHmacKey64 = null;
List<Stream> lStreams = new List<Stream>();
lStreams.Add(sSource);
HashingStreamEx sHashing = new HashingStreamEx(sSource, false, null);
lStreams.Add(sHashing);
try try
{ {
BinaryReaderEx br = null; Stream sXml;
BinaryReaderEx brDecrypted = null; if(fmt == KdbxFormat.Default)
Stream readerStream = null;
if(kdbFormat == KdbxFormat.Default)
{ {
br = new BinaryReaderEx(hashedStream, encNoBom, KLRes.FileCorrupted); BinaryReaderEx br = new BinaryReaderEx(sHashing,
ReadHeader(br); encNoBom, KLRes.FileCorrupted);
byte[] pbHeader = LoadHeader(br);
Stream sDecrypted = AttachStreamDecryptor(hashedStream); int cbEncKey, cbEncIV;
if((sDecrypted == null) || (sDecrypted == hashedStream)) ICipherEngine iCipher = GetCipher(out cbEncKey, out cbEncIV);
throw new SecurityException(KLRes.CryptoStreamFailed);
brDecrypted = new BinaryReaderEx(sDecrypted, encNoBom, KLRes.FileCorrupted); ComputeKeys(out pbCipherKey, cbEncKey, out pbHmacKey64);
byte[] pbStoredStartBytes = brDecrypted.ReadBytes(32);
if((m_pbStreamStartBytes == null) || (m_pbStreamStartBytes.Length != 32)) Stream sPlain;
throw new InvalidDataException(); if(m_uFileVersion <= FileVersion32_3)
for(int iStart = 0; iStart < 32; ++iStart)
{ {
if(pbStoredStartBytes[iStart] != m_pbStreamStartBytes[iStart]) Stream sDecrypted = EncryptStream(sHashing, iCipher,
throw new InvalidCompositeKeyException(); pbCipherKey, cbEncIV, false);
} if((sDecrypted == null) || (sDecrypted == sHashing))
throw new SecurityException(KLRes.CryptoStreamFailed);
lStreams.Add(sDecrypted);
Stream sHashed = new HashedBlockStream(sDecrypted, false, 0, BinaryReaderEx brDecrypted = new BinaryReaderEx(sDecrypted,
!m_bRepairMode); 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) 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) else if(fmt == KdbxFormat.PlainXml)
readerStream = hashedStream; sXml = sHashing;
else { Debug.Assert(false); throw new FormatException("KdbFormat"); } else { Debug.Assert(false); throw new ArgumentOutOfRangeException("fmt"); }
if(kdbFormat != KdbxFormat.PlainXml) // Is an encrypted format if(fmt == KdbxFormat.Default)
{ {
if(m_pbProtectedStreamKey == null) if(m_pbProtectedStreamKey == null)
{ {
@ -137,7 +173,7 @@ namespace KeePassLib.Serialization
// { // {
// while(true) // while(true)
// { // {
// int b = readerStream.ReadByte(); // int b = sXml.ReadByte();
// if(b == -1) break; // if(b == -1) break;
// fsOut.WriteByte((byte)b); // fsOut.WriteByte((byte)b);
// } // }
@ -146,26 +182,29 @@ namespace KeePassLib.Serialization
// fsOut.Close(); // fsOut.Close();
#endif #endif
ReadXmlStreamed(readerStream, hashedStream); ReadXmlStreamed(sXml, sHashing);
// ReadXmlDom(readerStream); // ReadXmlDom(sXml);
readerStream.Close();
// GC.KeepAlive(br);
// GC.KeepAlive(brDecrypted);
} }
catch(CryptographicException) // Thrown on invalid padding catch(CryptographicException) // Thrown on invalid padding
{ {
throw new CryptographicException(KLRes.FileCorrupted); 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<Stream> lStreams, HashingStreamEx sHashing)
{ {
hashedStream.Close(); CloseStreams(lStreams);
m_pbHashOfFileOnDisk = hashedStream.Hash;
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 // Reset memory protection settings (to always use reasonable
// defaults) // defaults)
@ -187,7 +226,7 @@ namespace KeePassLib.Serialization
m_pbHashOfHeader = null; m_pbHashOfHeader = null;
} }
private void ReadHeader(BinaryReaderEx br) private byte[] LoadHeader(BinaryReaderEx br)
{ {
MemoryStream msHeader = new MemoryStream(); MemoryStream msHeader = new MemoryStream();
Debug.Assert(br.CopyDataTo == null); Debug.Assert(br.CopyDataTo == null);
@ -212,18 +251,19 @@ namespace KeePassLib.Serialization
if((uVersion & FileVersionCriticalMask) > (FileVersion32 & FileVersionCriticalMask)) if((uVersion & FileVersionCriticalMask) > (FileVersion32 & FileVersionCriticalMask))
throw new FormatException(KLRes.FileVersionUnsupported + throw new FormatException(KLRes.FileVersionUnsupported +
MessageService.NewParagraph + KLRes.FileNewVerReq); MessageService.NewParagraph + KLRes.FileNewVerReq);
m_uFileVersion = uVersion;
while(true) while(true)
{ {
if(ReadHeaderField(br) == false) if(!ReadHeaderField(br)) break;
break;
} }
br.CopyDataTo = null; br.CopyDataTo = null;
byte[] pbHeader = msHeader.ToArray(); byte[] pbHeader = msHeader.ToArray();
msHeader.Close(); msHeader.Close();
SHA256Managed sha256 = new SHA256Managed();
m_pbHashOfHeader = sha256.ComputeHash(pbHeader); m_pbHashOfHeader = CryptoUtil.HashSha256(pbHeader);
return pbHeader;
} }
private bool ReadHeaderField(BinaryReaderEx brSource) private bool ReadHeaderField(BinaryReaderEx brSource)
@ -232,15 +272,21 @@ namespace KeePassLib.Serialization
if(brSource == null) throw new ArgumentNullException("brSource"); if(brSource == null) throw new ArgumentNullException("brSource");
byte btFieldID = brSource.ReadByte(); byte btFieldID = brSource.ReadByte();
ushort uSize = MemUtil.BytesToUInt16(brSource.ReadBytes(2));
byte[] pbData = null; int cbSize;
if(uSize > 0) 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; string strPrevExcpText = brSource.ReadExceptionText;
brSource.ReadExceptionText = KLRes.FileHeaderEndEarly; brSource.ReadExceptionText = KLRes.FileHeaderEndEarly;
pbData = brSource.ReadBytes(uSize); pbData = brSource.ReadBytes(cbSize);
brSource.ReadExceptionText = strPrevExcpText; brSource.ReadExceptionText = strPrevExcpText;
} }
@ -266,13 +312,27 @@ namespace KeePassLib.Serialization
CryptoRandom.Instance.AddEntropy(pbData); CryptoRandom.Instance.AddEntropy(pbData);
break; break;
// Obsolete; for backward compatibility only
case KdbxHeaderFieldID.TransformSeed: 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); CryptoRandom.Instance.AddEntropy(pbData);
break; break;
// Obsolete; for backward compatibility only
case KdbxHeaderFieldID.TransformRounds: 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; break;
case KdbxHeaderFieldID.EncryptionIV: case KdbxHeaderFieldID.EncryptionIV:
@ -285,6 +345,7 @@ namespace KeePassLib.Serialization
break; break;
case KdbxHeaderFieldID.StreamStartBytes: case KdbxHeaderFieldID.StreamStartBytes:
Debug.Assert(m_uFileVersion <= FileVersion32_3);
m_pbStreamStartBytes = pbData; m_pbStreamStartBytes = pbData;
break; break;
@ -292,6 +353,15 @@ namespace KeePassLib.Serialization
SetInnerRandomStreamID(pbData); SetInnerRandomStreamID(pbData);
break; 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: default:
Debug.Assert(false); Debug.Assert(false);
if(m_slLogger != null) if(m_slLogger != null)
@ -305,7 +375,7 @@ namespace KeePassLib.Serialization
private void SetCipher(byte[] pbID) private void SetCipher(byte[] pbID)
{ {
if((pbID == null) || (pbID.Length != 16)) if((pbID == null) || (pbID.Length != (int)PwUuid.UuidSize))
throw new FormatException(KLRes.FileUnknownCipher); throw new FormatException(KLRes.FileUnknownCipher);
m_pwDatabase.DataCipherUuid = new PwUuid(pbID); m_pwDatabase.DataCipherUuid = new PwUuid(pbID);
@ -329,35 +399,6 @@ namespace KeePassLib.Serialization
m_craInnerRandomStream = (CrsAlgorithm)uID; 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] [Obsolete]
public static List<PwEntry> ReadEntries(PwDatabase pwDatabase, Stream msData) public static List<PwEntry> ReadEntries(PwDatabase pwDatabase, Stream msData)
{ {

View File

@ -40,6 +40,7 @@ using System.IO.Compression;
using KeePassLib.Collections; using KeePassLib.Collections;
using KeePassLib.Cryptography; using KeePassLib.Cryptography;
using KeePassLib.Cryptography.Cipher; using KeePassLib.Cryptography.Cipher;
using KeePassLib.Cryptography.KeyDerivation;
using KeePassLib.Delegates; using KeePassLib.Delegates;
using KeePassLib.Interfaces; using KeePassLib.Interfaces;
using KeePassLib.Keys; using KeePassLib.Keys;
@ -54,7 +55,7 @@ namespace KeePassLib.Serialization
/// </summary> /// </summary>
public sealed partial class KdbxFile 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) // IStatusLogger slLogger)
// { // {
// bool bMadeUnhidden = UrlUtil.UnhideFile(strFile); // bool bMadeUnhidden = UrlUtil.UnhideFile(strFile);
@ -72,56 +73,117 @@ namespace KeePassLib.Serialization
/// <param name="pgDataSource">Group containing all groups and /// <param name="pgDataSource">Group containing all groups and
/// entries to write. If <c>null</c>, the complete database will /// entries to write. If <c>null</c>, the complete database will
/// be written.</param> /// be written.</param>
/// <param name="format">Format of the file to create.</param> /// <param name="fmt">Format of the file to create.</param>
/// <param name="slLogger">Logger that recieves status information.</param> /// <param name="slLogger">Logger that recieves status information.</param>
public void Save(Stream sSaveTo, PwGroup pgDataSource, KdbxFormat format, public void Save(Stream sSaveTo, PwGroup pgDataSource, KdbxFormat fmt,
IStatusLogger slLogger) IStatusLogger slLogger)
{ {
Debug.Assert(sSaveTo != null); Debug.Assert(sSaveTo != null);
if(sSaveTo == null) throw new ArgumentNullException("sSaveTo"); if(sSaveTo == null) throw new ArgumentNullException("sSaveTo");
m_format = format; m_format = fmt;
m_slLogger = slLogger; m_slLogger = slLogger;
HashingStreamEx hashedStream = new HashingStreamEx(sSaveTo, true, null);
UTF8Encoding encNoBom = StrUtil.Utf8; UTF8Encoding encNoBom = StrUtil.Utf8;
CryptoRandom cr = CryptoRandom.Instance; CryptoRandom cr = CryptoRandom.Instance;
byte[] pbCipherKey = null;
byte[] pbHmacKey64 = null;
List<Stream> lStreams = new List<Stream>();
lStreams.Add(sSaveTo);
HashingStreamEx sHashing = new HashingStreamEx(sSaveTo, true, null);
lStreams.Add(sHashing);
try try
{ {
m_pbMasterSeed = cr.GetRandomBytes(32); m_uFileVersion = GetMinKdbxVersion();
m_pbTransformSeed = cr.GetRandomBytes(32);
m_pbEncryptionIV = cr.GetRandomBytes(16);
m_pbProtectedStreamKey = cr.GetRandomBytes(32); int cbEncKey, cbEncIV;
m_craInnerRandomStream = CrsAlgorithm.Salsa20; 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_randomStream = new CryptoRandomStream(m_craInnerRandomStream,
m_pbProtectedStreamKey); 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) if(m_format == KdbxFormat.Default)
{ {
WriteHeader(hashedStream); // Also flushes the stream byte[] pbHeader = GenerateHeader();
m_pbHashOfHeader = CryptoUtil.HashSha256(pbHeader);
Stream sEncrypted = AttachStreamEncryptor(hashedStream); MemUtil.Write(sHashing, pbHeader);
if((sEncrypted == null) || (sEncrypted == hashedStream)) sHashing.Flush();
throw new SecurityException(KLRes.CryptoStreamFailed);
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) if(m_pwDatabase.Compression == PwCompressionAlgorithm.GZip)
writerStream = new GZipStream(sHashed, CompressionMode.Compress); {
else sXml = new GZipStream(sPlain, CompressionMode.Compress);
writerStream = sHashed; lStreams.Add(sXml);
}
else sXml = sPlain;
} }
else if(m_format == KdbxFormat.PlainXml) else if(m_format == KdbxFormat.PlainXml)
writerStream = hashedStream; sXml = sHashing;
else { Debug.Assert(false); throw new FormatException("KdbFormat"); } else
{
Debug.Assert(false);
throw new ArgumentOutOfRangeException("fmt");
}
#if KeePassUAP #if KeePassUAP
XmlWriterSettings xws = new XmlWriterSettings(); XmlWriterSettings xws = new XmlWriterSettings();
@ -130,122 +192,125 @@ namespace KeePassLib.Serialization
xws.IndentChars = "\t"; xws.IndentChars = "\t";
xws.NewLineOnAttributes = false; xws.NewLineOnAttributes = false;
XmlWriter xw = XmlWriter.Create(writerStream, xws); XmlWriter xw = XmlWriter.Create(sXml, xws);
#else #else
XmlTextWriter xw = new XmlTextWriter(writerStream, encNoBom); XmlTextWriter xw = new XmlTextWriter(sXml, encNoBom);
xw.Formatting = Formatting.Indented; xw.Formatting = Formatting.Indented;
xw.IndentChar = '\t'; xw.IndentChar = '\t';
xw.Indentation = 1; xw.Indentation = 1;
#endif #endif
m_xmlWriter = xw; m_xmlWriter = xw;
WriteDocument(pgDataSource); WriteDocument(pgDataSource);
m_xmlWriter.Flush(); m_xmlWriter.Flush();
m_xmlWriter.Close(); 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<Stream> lStreams, HashingStreamEx sHashing)
{ {
hashedStream.Close(); CloseStreams(lStreams);
m_pbHashOfFileOnDisk = hashedStream.Hash;
sSaveTo.Close(); Debug.Assert(lStreams.Contains(sHashing)); // sHashing must be closed
m_pbHashOfFileOnDisk = sHashing.Hash;
Debug.Assert(m_pbHashOfFileOnDisk != null);
m_xmlWriter = null; m_xmlWriter = null;
m_pbHashOfHeader = 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)); WriteHeaderField(ms, KdbxHeaderFieldID.CipherID,
MemUtil.Write(ms, MemUtil.UInt32ToBytes(FileSignature2)); m_pwDatabase.DataCipherUuid.UuidBytes);
MemUtil.Write(ms, MemUtil.UInt32ToBytes(FileVersion32));
WriteHeaderField(ms, KdbxHeaderFieldID.CipherID, int nCprID = (int)m_pwDatabase.Compression;
m_pwDatabase.DataCipherUuid.UuidBytes); WriteHeaderField(ms, KdbxHeaderFieldID.CompressionFlags,
MemUtil.UInt32ToBytes((uint)nCprID));
int nCprID = (int)m_pwDatabase.Compression; WriteHeaderField(ms, KdbxHeaderFieldID.MasterSeed, m_pbMasterSeed);
WriteHeaderField(ms, KdbxHeaderFieldID.CompressionFlags,
MemUtil.UInt32ToBytes((uint)nCprID));
WriteHeaderField(ms, KdbxHeaderFieldID.MasterSeed, m_pbMasterSeed); if(m_uFileVersion <= FileVersion32_3)
WriteHeaderField(ms, KdbxHeaderFieldID.TransformSeed, m_pbTransformSeed); {
WriteHeaderField(ms, KdbxHeaderFieldID.TransformRounds, Debug.Assert(m_pwDatabase.KdfParameters.KdfUuid.Equals(
MemUtil.UInt64ToBytes(m_pwDatabase.KeyEncryptionRounds)); (new AesKdf()).Uuid));
WriteHeaderField(ms, KdbxHeaderFieldID.EncryptionIV, m_pbEncryptionIV); WriteHeaderField(ms, KdbxHeaderFieldID.TransformSeed,
WriteHeaderField(ms, KdbxHeaderFieldID.ProtectedStreamKey, m_pbProtectedStreamKey); m_pwDatabase.KdfParameters.GetByteArray(AesKdf.ParamSeed));
WriteHeaderField(ms, KdbxHeaderFieldID.StreamStartBytes, m_pbStreamStartBytes); 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; if(m_pbEncryptionIV.Length > 0)
WriteHeaderField(ms, KdbxHeaderFieldID.InnerRandomStreamID, WriteHeaderField(ms, KdbxHeaderFieldID.EncryptionIV, m_pbEncryptionIV);
MemUtil.UInt32ToBytes((uint)nIrsID));
WriteHeaderField(ms, KdbxHeaderFieldID.EndOfHeader, new byte[]{ WriteHeaderField(ms, KdbxHeaderFieldID.ProtectedStreamKey, m_pbProtectedStreamKey);
(byte)'\r', (byte)'\n', (byte)'\r', (byte)'\n' });
byte[] pbHeader = ms.ToArray(); if(m_uFileVersion <= FileVersion32_3)
ms.Close(); WriteHeaderField(ms, KdbxHeaderFieldID.StreamStartBytes,
m_pbStreamStartBytes);
SHA256Managed sha256 = new SHA256Managed(); int nIrsID = (int)m_craInnerRandomStream;
m_pbHashOfHeader = sha256.ComputeHash(pbHeader); WriteHeaderField(ms, KdbxHeaderFieldID.InnerRandomStreamID,
MemUtil.Int32ToBytes(nIrsID));
s.Write(pbHeader, 0, pbHeader.Length); // Write public custom data only when there is at least one item,
s.Flush(); // 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) byte[] pbData)
{ {
s.WriteByte((byte)kdbID); 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; if(cb > (int)ushort.MaxValue)
MemUtil.Write(s, MemUtil.UInt16ToBytes(uLength)); {
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) if(cb > 0) s.Write(pb, 0, cb);
{
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);
} }
private void WriteDocument(PwGroup pgDataSource) private void WriteDocument(PwGroup pgDataSource)
@ -331,12 +396,15 @@ namespace KeePassLib.Serialization
{ {
m_xmlWriter.WriteStartElement(ElemMeta); 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( WriteObject(ElemHeaderHash, Convert.ToBase64String(
m_pbHashOfHeader), false); m_pbHashOfHeader), false);
if(m_uFileVersion > FileVersion32_3)
WriteObject(ElemSettingsChanged, m_pwDatabase.SettingsChanged);
WriteObject(ElemDbName, m_pwDatabase.Name, true); WriteObject(ElemDbName, m_pwDatabase.Name, true);
WriteObject(ElemDbNameChanged, m_pwDatabase.NameChanged); WriteObject(ElemDbNameChanged, m_pwDatabase.NameChanged);
WriteObject(ElemDbDesc, m_pwDatabase.Description, true); WriteObject(ElemDbDesc, m_pwDatabase.Description, true);
@ -387,6 +455,9 @@ namespace KeePassLib.Serialization
WriteObject(ElemEnableAutoType, StrUtil.BoolToStringEx(pg.EnableAutoType), false); WriteObject(ElemEnableAutoType, StrUtil.BoolToStringEx(pg.EnableAutoType), false);
WriteObject(ElemEnableSearching, StrUtil.BoolToStringEx(pg.EnableSearching), false); WriteObject(ElemEnableSearching, StrUtil.BoolToStringEx(pg.EnableSearching), false);
WriteObject(ElemLastTopVisibleEntry, pg.LastTopVisibleEntry); WriteObject(ElemLastTopVisibleEntry, pg.LastTopVisibleEntry);
if(pg.CustomData.Count > 0)
WriteList(ElemCustomData, pg.CustomData);
} }
private void EndGroup() private void EndGroup()
@ -402,7 +473,7 @@ namespace KeePassLib.Serialization
WriteObject(ElemUuid, pe.Uuid); WriteObject(ElemUuid, pe.Uuid);
WriteObject(ElemIcon, (int)pe.IconId); WriteObject(ElemIcon, (int)pe.IconId);
if(!pe.CustomIconUuid.Equals(PwUuid.Zero)) if(!pe.CustomIconUuid.Equals(PwUuid.Zero))
WriteObject(ElemCustomIconID, pe.CustomIconUuid); WriteObject(ElemCustomIconID, pe.CustomIconUuid);
@ -417,6 +488,9 @@ namespace KeePassLib.Serialization
WriteList(pe.Binaries); WriteList(pe.Binaries);
WriteList(ElemAutoType, pe.AutoType); WriteList(ElemAutoType, pe.AutoType);
if(pe.CustomData.Count > 0)
WriteList(ElemCustomData, pe.CustomData);
if(!bIsHistory) WriteList(ElemHistory, pe.History, true); if(!bIsHistory) WriteList(ElemHistory, pe.History, true);
else { Debug.Assert(pe.History.UCount == 0); } else { Debug.Assert(pe.History.UCount == 0); }

View File

@ -22,13 +22,21 @@ using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Security;
using System.Text; using System.Text;
using System.Xml; using System.Xml;
#if !KeePassUAP
using System.Security.Cryptography;
#endif
using KeePassLib.Collections; using KeePassLib.Collections;
using KeePassLib.Cryptography; using KeePassLib.Cryptography;
using KeePassLib.Cryptography.Cipher;
using KeePassLib.Cryptography.KeyDerivation;
using KeePassLib.Delegates; using KeePassLib.Delegates;
using KeePassLib.Interfaces; using KeePassLib.Interfaces;
using KeePassLib.Resources;
using KeePassLib.Security; using KeePassLib.Security;
using KeePassLib.Utility; using KeePassLib.Utility;
@ -73,7 +81,8 @@ namespace KeePassLib.Serialization
/// The first 2 bytes are critical (i.e. loading will fail, if the /// The first 2 bytes are critical (i.e. loading will fail, if the
/// file version is too high), the last 2 bytes are informational. /// file version is too high), the last 2 bytes are informational.
/// </summary> /// </summary>
private const uint FileVersion32 = 0x00030001; private const uint FileVersion32 = 0x00040000;
private const uint FileVersion32_3 = 0x00030001; // Old format
private const uint FileVersionCriticalMask = 0xFFFF0000; private const uint FileVersionCriticalMask = 0xFFFF0000;
@ -92,6 +101,7 @@ namespace KeePassLib.Serialization
private const string ElemGenerator = "Generator"; private const string ElemGenerator = "Generator";
private const string ElemHeaderHash = "HeaderHash"; private const string ElemHeaderHash = "HeaderHash";
private const string ElemSettingsChanged = "SettingsChanged";
private const string ElemDbName = "DatabaseName"; private const string ElemDbName = "DatabaseName";
private const string ElemDbNameChanged = "DatabaseNameChanged"; private const string ElemDbNameChanged = "DatabaseNameChanged";
private const string ElemDbDesc = "DatabaseDescription"; private const string ElemDbDesc = "DatabaseDescription";
@ -192,14 +202,15 @@ namespace KeePassLib.Serialization
private KdbxFormat m_format = KdbxFormat.Default; private KdbxFormat m_format = KdbxFormat.Default;
private IStatusLogger m_slLogger = null; private IStatusLogger m_slLogger = null;
private uint m_uFileVersion = 0;
private byte[] m_pbMasterSeed = null; private byte[] m_pbMasterSeed = null;
private byte[] m_pbTransformSeed = null; // private byte[] m_pbTransformSeed = null;
private byte[] m_pbEncryptionIV = null; private byte[] m_pbEncryptionIV = null;
private byte[] m_pbProtectedStreamKey = null; private byte[] m_pbProtectedStreamKey = null;
private byte[] m_pbStreamStartBytes = null; private byte[] m_pbStreamStartBytes = null;
// ArcFourVariant only for compatibility; KeePass will default to a // ArcFourVariant only for backward compatibility; KeePass defaults
// different (more secure) algorithm when *writing* databases // to a more secure algorithm when *writing* databases
private CrsAlgorithm m_craInnerRandomStream = CrsAlgorithm.ArcFourVariant; private CrsAlgorithm m_craInnerRandomStream = CrsAlgorithm.ArcFourVariant;
private Dictionary<string, ProtectedBinary> m_dictBinPool = private Dictionary<string, ProtectedBinary> m_dictBinPool =
@ -222,12 +233,14 @@ namespace KeePassLib.Serialization
CipherID = 2, CipherID = 2,
CompressionFlags = 3, CompressionFlags = 3,
MasterSeed = 4, MasterSeed = 4,
TransformSeed = 5, TransformSeed = 5, // KDBX 3.1, for backward compatibility only
TransformRounds = 6, TransformRounds = 6, // KDBX 3.1, for backward compatibility only
EncryptionIV = 7, EncryptionIV = 7,
ProtectedStreamKey = 8, ProtectedStreamKey = 8,
StreamStartBytes = 9, StreamStartBytes = 9, // KDBX 3.1, for backward compatibility only
InnerRandomStreamID = 10 InnerRandomStreamID = 10,
KdfParameters = 11, // KDBX 4
PublicCustomData = 12 // KDBX 4
} }
public byte[] HashOfFileOnDisk 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<Stream> 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) private void BinPoolBuild(PwGroup pgDataSource)
{ {
m_dictBinPool = new Dictionary<string, ProtectedBinary>(); m_dictBinPool = new Dictionary<string, ProtectedBinary>();

View File

@ -27,10 +27,10 @@ using System.Xml.Serialization;
#if !KeePassUAP #if !KeePassUAP
using System.Drawing; using System.Drawing;
using System.Security.Cryptography;
using System.Windows.Forms; using System.Windows.Forms;
#endif #endif
using KeePassLib.Cryptography;
using KeePassLib.Utility; using KeePassLib.Utility;
namespace KeePassLib.Translation namespace KeePassLib.Translation
@ -333,9 +333,7 @@ namespace KeePassLib.Translation
WriteControlDependentParams(sb, c); WriteControlDependentParams(sb, c);
byte[] pb = StrUtil.Utf8.GetBytes(sb.ToString()); byte[] pb = StrUtil.Utf8.GetBytes(sb.ToString());
byte[] pbSha = CryptoUtil.HashSha256(pb);
SHA256Managed sha256 = new SHA256Managed();
byte[] pbSha = sha256.ComputeHash(pb);
// Also see MatchHash // Also see MatchHash
return "v1:" + Convert.ToBase64String(pbSha, 0, 3, return "v1:" + Convert.ToBase64String(pbSha, 0, 3,

View File

@ -214,14 +214,11 @@ namespace KeePassLib.Utility
const int SizeICONDIR = 6; const int SizeICONDIR = 6;
const int SizeICONDIRENTRY = 16; 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(pb.Length < SizeICONDIR) return null;
if(BitConverter.ToUInt16(pb, 0) != 0) return null; // Reserved, 0 if(MemUtil.BytesToUInt16(pb, 0) != 0) return null; // Reserved, 0
if(BitConverter.ToUInt16(pb, 2) != 1) return null; // ICO type, 1 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; } if(n < 0) { Debug.Assert(false); return null; }
int cbDir = SizeICONDIR + (n * SizeICONDIRENTRY); int cbDir = SizeICONDIR + (n * SizeICONDIRENTRY);
@ -235,10 +232,10 @@ namespace KeePassLib.Utility
int h = pb[iOffset + 1]; int h = pb[iOffset + 1];
if((w < 0) || (h < 0)) { Debug.Assert(false); return null; } 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) 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 < cbDir) return null;
if((p + cb) > pb.Length) return null; if((p + cb) > pb.Length) return null;

View File

@ -37,6 +37,8 @@ namespace KeePassLib.Utility
/// </summary> /// </summary>
public static class MemUtil public static class MemUtil
{ {
internal static readonly byte[] EmptyByteArray = new byte[0];
private static readonly uint[] m_vSBox = new uint[256] { private static readonly uint[] m_vSBox = new uint[256] {
0xCD2FACB3, 0xE78A7F5C, 0x6F0803FC, 0xBCF6E230, 0xCD2FACB3, 0xE78A7F5C, 0x6F0803FC, 0xBCF6E230,
0x3A321712, 0x06403DB1, 0xD2F84B95, 0xDF22A6E4, 0x3A321712, 0x06403DB1, 0xD2F84B95, 0xDF22A6E4,
@ -262,6 +264,22 @@ namespace KeePassLib.Utility
Array.Clear(pbArray, 0, pbArray.Length); Array.Clear(pbArray, 0, pbArray.Length);
} }
/// <summary>
/// Set all elements of an array to the default value.
/// </summary>
/// <param name="v">Input array.</param>
#if KeePassLibSD
[MethodImpl(MethodImplOptions.NoInlining)]
#else
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
#endif
public static void ZeroArray<T>(T[] v)
{
if(v == null) { Debug.Assert(false); throw new ArgumentNullException("v"); }
Array.Clear(v, 0, v.Length);
}
/// <summary> /// <summary>
/// Convert 2 bytes to a 16-bit unsigned integer (little-endian). /// Convert 2 bytes to a 16-bit unsigned integer (little-endian).
/// </summary> /// </summary>
@ -269,11 +287,26 @@ namespace KeePassLib.Utility
{ {
Debug.Assert((pb != null) && (pb.Length == 2)); Debug.Assert((pb != null) && (pb.Length == 2));
if(pb == null) throw new ArgumentNullException("pb"); 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)); return (ushort)((ushort)pb[0] | ((ushort)pb[1] << 8));
} }
/// <summary>
/// Convert 2 bytes to a 16-bit unsigned integer (little-endian).
/// </summary>
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));
}
/// <summary> /// <summary>
/// Convert 4 bytes to a 32-bit unsigned integer (little-endian). /// Convert 4 bytes to a 32-bit unsigned integer (little-endian).
/// </summary> /// </summary>
@ -281,12 +314,28 @@ namespace KeePassLib.Utility
{ {
Debug.Assert((pb != null) && (pb.Length == 4)); Debug.Assert((pb != null) && (pb.Length == 4));
if(pb == null) throw new ArgumentNullException("pb"); 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) | return ((uint)pb[0] | ((uint)pb[1] << 8) | ((uint)pb[2] << 16) |
((uint)pb[3] << 24)); ((uint)pb[3] << 24));
} }
/// <summary>
/// Convert 4 bytes to a 32-bit unsigned integer (little-endian).
/// </summary>
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));
}
/// <summary> /// <summary>
/// Convert 8 bytes to a 64-bit unsigned integer (little-endian). /// Convert 8 bytes to a 64-bit unsigned integer (little-endian).
/// </summary> /// </summary>
@ -294,13 +343,54 @@ namespace KeePassLib.Utility
{ {
Debug.Assert((pb != null) && (pb.Length == 8)); Debug.Assert((pb != null) && (pb.Length == 8));
if(pb == null) throw new ArgumentNullException("pb"); 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) | 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[3] << 24) | ((ulong)pb[4] << 32) | ((ulong)pb[5] << 40) |
((ulong)pb[6] << 48) | ((ulong)pb[7] << 56)); ((ulong)pb[6] << 48) | ((ulong)pb[7] << 56));
} }
/// <summary>
/// Convert 8 bytes to a 64-bit unsigned integer (little-endian).
/// </summary>
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);
}
/// <summary> /// <summary>
/// Convert a 16-bit unsigned integer to 2 bytes (little-endian). /// Convert a 16-bit unsigned integer to 2 bytes (little-endian).
/// </summary> /// </summary>
@ -335,6 +425,27 @@ namespace KeePassLib.Utility
return pb; return pb;
} }
/// <summary>
/// Convert a 32-bit unsigned integer to 4 bytes (little-endian).
/// </summary>
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);
}
}
/// <summary> /// <summary>
/// Convert a 64-bit unsigned integer to 8 bytes (little-endian). /// Convert a 64-bit unsigned integer to 8 bytes (little-endian).
/// </summary> /// </summary>
@ -357,6 +468,61 @@ namespace KeePassLib.Utility
return pb; return pb;
} }
/// <summary>
/// Convert a 64-bit unsigned integer to 8 bytes (little-endian).
/// </summary>
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) public static bool ArraysEqual(byte[] x, byte[] y)
{ {
// Return false if one of them is null (not comparable)! // Return false if one of them is null (not comparable)!
@ -372,19 +538,21 @@ namespace KeePassLib.Utility
return true; return true;
} }
public static void XorArray(byte[] pbSource, int nSourceOffset, public static void XorArray(byte[] pbSource, int iSourceOffset,
byte[] pbBuffer, int nBufferOffset, int nLength) byte[] pbBuffer, int iBufferOffset, int cb)
{ {
if(pbSource == null) throw new ArgumentNullException("pbSource"); 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(pbBuffer == null) throw new ArgumentNullException("pbBuffer");
if(nBufferOffset < 0) throw new ArgumentException(); if(iBufferOffset < 0) throw new ArgumentOutOfRangeException("iBufferOffset");
if(nLength < 0) throw new ArgumentException(); if(cb < 0) throw new ArgumentOutOfRangeException("cb");
if((nSourceOffset + nLength) > pbSource.Length) throw new ArgumentException(); if(iSourceOffset > (pbSource.Length - cb))
if((nBufferOffset + nLength) > pbBuffer.Length) throw new ArgumentException(); throw new ArgumentOutOfRangeException("cb");
if(iBufferOffset > (pbBuffer.Length - cb))
throw new ArgumentOutOfRangeException("cb");
for(int i = 0; i < nLength; ++i) for(int i = 0; i < cb; ++i)
pbBuffer[nBufferOffset + i] ^= pbSource[nSourceOffset + i]; pbBuffer[iBufferOffset + i] ^= pbSource[iSourceOffset + i];
} }
/// <summary> /// <summary>
@ -463,7 +631,8 @@ namespace KeePassLib.Utility
if(s == null) { Debug.Assert(false); return; } if(s == null) { Debug.Assert(false); return; }
if(pbData == 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) public static byte[] Compress(byte[] pbData)

View File

@ -70,6 +70,12 @@ namespace KeePassLib.Utility
// 1418: // 1418:
// Minimizing a form while loading it doesn't work. // Minimizing a form while loading it doesn't work.
// https://sourceforge.net/p/keepass/bugs/1418/ // 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: // 5795:
// Text in input field is incomplete. // Text in input field is incomplete.
// https://bugzilla.xamarin.com/show_bug.cgi?id=5795 // https://bugzilla.xamarin.com/show_bug.cgi?id=5795

View File

@ -978,13 +978,12 @@ namespace KeePassLib.Utility
public static bool IsHexString(string str, bool bStrict) public static bool IsHexString(string str, bool bStrict)
{ {
if(str == null) throw new ArgumentNullException("str"); if(str == null) throw new ArgumentNullException("str");
if(str.Length == 0) return true;
foreach(char ch in str) foreach(char ch in str)
{ {
if((ch >= '0') && (ch <= '9')) continue; if((ch >= '0') && (ch <= '9')) continue;
if((ch >= 'a') && (ch <= 'z')) continue; if((ch >= 'a') && (ch <= 'f')) continue;
if((ch >= 'A') && (ch <= 'Z')) continue; if((ch >= 'A') && (ch <= 'F')) continue;
if(bStrict) return false; if(bStrict) return false;
@ -997,8 +996,31 @@ namespace KeePassLib.Utility
return true; 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 #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, public static bool SimplePatternMatch(string strPattern, string strText,
StringComparison sc) StringComparison sc)
{ {