This commit is contained in:
Philipp Crocoll
2020-12-07 10:43:46 +01:00
parent b7e4bf0be5
commit eee24819d5
7 changed files with 323 additions and 92 deletions

View File

@@ -367,5 +367,27 @@ namespace KeePassLib.Cryptography
Debug.Assert(iPos == pbRes.Length);
return pbRes;
}
private static int g_iWeakSeed = 0;
public static Random NewWeakRandom()
{
long s64 = DateTime.UtcNow.ToBinary();
int s32 = (int)((s64 >> 32) ^ s64);
lock (g_oSyncRoot)
{
unchecked
{
g_iWeakSeed += 0x78A8C4B7; // Prime number
s32 ^= g_iWeakSeed;
}
}
// Prevent overflow in the Random constructor of .NET 2.0
if (s32 == int.MinValue) s32 = int.MaxValue;
return new Random(s32);
}
}
}

View File

@@ -1,6 +1,6 @@
/*
KeePass Password Safe - The Open-Source Password Manager
Copyright (C) 2003-2017 Dominik Reichl <dominik.reichl@t-online.de>
Copyright (C) 2003-2020 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
@@ -20,18 +20,57 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Text;
#if !KeePassUAP
using System.Security.Cryptography;
#endif
using KeePassLib.Native;
using KeePassLib.Utility;
namespace KeePassLib.Cryptography
{
public static class CryptoUtil
{
private static bool? g_obProtData = null;
public static bool IsProtectedDataSupported
{
get
{
if (g_obProtData.HasValue) return g_obProtData.Value;
bool b = false;
try
{
Random r = CryptoRandom.NewWeakRandom();
byte[] pbData = new byte[137];
r.NextBytes(pbData);
byte[] pbEnt = new byte[41];
r.NextBytes(pbEnt);
byte[] pbEnc = ProtectedData.Protect(pbData, pbEnt,
DataProtectionScope.CurrentUser);
if ((pbEnc != null) && !MemUtil.ArraysEqual(pbEnc, pbData))
{
byte[] pbDec = ProtectedData.Unprotect(pbEnc, pbEnt,
DataProtectionScope.CurrentUser);
if ((pbDec != null) && MemUtil.ArraysEqual(pbDec, pbData))
b = true;
}
}
catch (Exception) { Debug.Assert(false); }
Debug.Assert(b); // Should be supported on all systems
g_obProtData = b;
return b;
}
}
public static byte[] HashSha256(byte[] pbData)
{
if (pbData == null) throw new ArgumentNullException("pbData");
@@ -66,6 +105,22 @@ namespace KeePassLib.Cryptography
return pbHash;
}
internal static byte[] HashSha256(string strFilePath)
{
byte[] pbHash = null;
using (FileStream fs = new FileStream(strFilePath, FileMode.Open,
FileAccess.Read, FileShare.Read))
{
using (SHA256Managed h = new SHA256Managed())
{
pbHash = h.ComputeHash(fs);
}
}
return pbHash;
}
/// <summary>
/// Create a cryptographic key of length <paramref name="cbOut" />
/// (in bytes) from <paramref name="pbIn" />.
@@ -125,5 +180,75 @@ namespace KeePassLib.Cryptography
MemUtil.ZeroByteArray(pbHash);
return pbRet;
}
#if !KeePassUAP
private static bool? g_obAesCsp = null;
internal static SymmetricAlgorithm CreateAes()
{
if (g_obAesCsp.HasValue)
return (g_obAesCsp.Value ? CreateAesCsp() : new RijndaelManaged());
SymmetricAlgorithm a = CreateAesCsp();
g_obAesCsp = (a != null);
return (a ?? new RijndaelManaged());
}
private static SymmetricAlgorithm CreateAesCsp()
{
try
{
// On Windows, the CSP implementation is only minimally
// faster (and for key derivations it's not used anyway,
// as KeePass uses a native implementation based on
// CNG/BCrypt, which is much faster)
if (!NativeLib.IsUnix()) return null;
string strFqn = Assembly.CreateQualifiedName(
"System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",
"System.Security.Cryptography.AesCryptoServiceProvider");
Type t = Type.GetType(strFqn);
if (t == null) return null;
return (Activator.CreateInstance(t) as SymmetricAlgorithm);
}
catch (Exception) { Debug.Assert(false); }
return null;
}
#endif
public static byte[] ProtectData(byte[] pb, byte[] pbOptEntropy,
DataProtectionScope s)
{
return ProtectDataPriv(pb, true, pbOptEntropy, s);
}
public static byte[] UnprotectData(byte[] pb, byte[] pbOptEntropy,
DataProtectionScope s)
{
return ProtectDataPriv(pb, false, pbOptEntropy, s);
}
private static byte[] ProtectDataPriv(byte[] pb, bool bProtect,
byte[] pbOptEntropy, DataProtectionScope s)
{
if (pb == null) throw new ArgumentNullException("pb");
if ((pbOptEntropy != null) && (pbOptEntropy.Length == 0))
pbOptEntropy = null;
if (CryptoUtil.IsProtectedDataSupported)
{
if (bProtect)
return ProtectedData.Protect(pb, pbOptEntropy, s);
return ProtectedData.Unprotect(pb, pbOptEntropy, s);
}
Debug.Assert(false);
byte[] pbCopy = new byte[pb.Length];
Array.Copy(pb, pbCopy, pb.Length);
return pbCopy;
}
}
}

View File

@@ -46,6 +46,8 @@ namespace KeePassLib.Cryptography.KeyDerivation
private const ulong NbBlockSizeInQW = NbBlockSize / 8UL;
private const ulong NbSyncPoints = 4;
private const ulong NbAddressesInBlock = 128;
private const int NbPreHashDigestLength = 64;
private const int NbPreHashSeedLength = NbPreHashDigestLength + 8;
@@ -56,6 +58,7 @@ namespace KeePassLib.Cryptography.KeyDerivation
private sealed class Argon2Ctx
{
public Argon2Type Type = Argon2Type.D;
public uint Version = 0;
public ulong Lanes = 0;
@@ -89,7 +92,7 @@ namespace KeePassLib.Cryptography.KeyDerivation
}
}
private static byte[] Argon2d(byte[] pbMsg, byte[] pbSalt, uint uParallel,
private byte[] Argon2Transform(byte[] pbMsg, byte[] pbSalt, uint uParallel,
ulong uMem, ulong uIt, int cbOut, uint uVersion, byte[] pbSecretKey,
byte[] pbAssocData)
{
@@ -101,6 +104,7 @@ namespace KeePassLib.Cryptography.KeyDerivation
#endif
Argon2Ctx ctx = new Argon2Ctx();
ctx.Type = m_t;
ctx.Version = uVersion;
ctx.Lanes = uParallel;
@@ -137,7 +141,7 @@ namespace KeePassLib.Cryptography.KeyDerivation
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
MemUtil.UInt32ToBytesEx((uint)m_t, pbBuf, 0);
h.TransformBlock(pbBuf, 0, pbBuf.Length, pbBuf, 0);
MemUtil.UInt32ToBytesEx((uint)pbMsg.Length, pbBuf, 0);
h.TransformBlock(pbBuf, 0, pbBuf.Length, pbBuf, 0);
@@ -497,8 +501,33 @@ namespace KeePassLib.Cryptography.KeyDerivation
Debug.Assert(ctx.Version >= MinVersion);
bool bCanXor = (ctx.Version >= 0x13U);
ulong[] pbR = new ulong[NbBlockSizeInQW];
ulong[] pbTmp = new ulong[NbBlockSizeInQW];
ulong[] pbAddrInputZero = null;
bool bDataIndependentAddr = ((ctx.Type == Argon2Type.ID) &&
(ti.Pass == 0) && (ti.Slice < (NbSyncPoints / 2)));
if (bDataIndependentAddr)
{
pbAddrInputZero = new ulong[NbBlockSizeInQW * 3];
const int iInput = (int)NbBlockSizeInQW;
pbAddrInputZero[iInput] = ti.Pass;
pbAddrInputZero[iInput + 1] = ti.Lane;
pbAddrInputZero[iInput + 2] = ti.Slice;
pbAddrInputZero[iInput + 3] = ctx.MemoryBlocks;
pbAddrInputZero[iInput + 4] = ctx.TCost;
pbAddrInputZero[iInput + 5] = (ulong)ctx.Type;
}
ulong uStart = 0;
if((ti.Pass == 0) && (ti.Slice == 0)) uStart = 2;
if ((ti.Pass == 0) && (ti.Slice == 0))
{
uStart = 2;
if (bDataIndependentAddr)
NextAddresses(pbAddrInputZero, pbR, pbTmp);
}
ulong uCur = (ti.Lane * ctx.LaneLength) + (ti.Slice *
ctx.SegmentLength) + uStart;
@@ -506,15 +535,21 @@ namespace KeePassLib.Cryptography.KeyDerivation
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 uPseudoRand;
if (bDataIndependentAddr)
{
ulong iMod = i % NbAddressesInBlock;
if (iMod == 0)
NextAddresses(pbAddrInputZero, pbR, pbTmp);
uPseudoRand = pbAddrInputZero[iMod];
}
else uPseudoRand = ctx.Mem[uPrev * NbBlockSizeInQW];
ulong uRefLane = (uPseudoRand >> 32) % ctx.Lanes;
if ((ti.Pass == 0) && (ti.Slice == 0))
uRefLane = ti.Lane;
@@ -536,6 +571,7 @@ namespace KeePassLib.Cryptography.KeyDerivation
MemUtil.ZeroArray<ulong>(pbR);
MemUtil.ZeroArray<ulong>(pbTmp);
if (pbAddrInputZero != null) MemUtil.ZeroArray<ulong>(pbAddrInputZero);
}
catch (Exception) { Debug.Assert(false); }
@@ -610,6 +646,19 @@ namespace KeePassLib.Cryptography.KeyDerivation
XorBlock(pMem, uNext, pbR, 0);
}
private static void NextAddresses(ulong[] pbAddrInputZero, ulong[] pbR,
ulong[] pbTmp)
{
// pbAddrInputZero contains an address block, an input block and a zero block
const ulong uAddr = 0;
const ulong uInput = NbBlockSizeInQW;
const ulong uZero = NbBlockSizeInQW * 2;
++pbAddrInputZero[uInput + 6];
FillBlock(pbAddrInputZero, uZero, uInput, uAddr, false, pbR, pbTmp);
FillBlock(pbAddrInputZero, uZero, uAddr, uAddr, false, pbR, pbTmp);
}
private static byte[] FinalHash(Argon2Ctx ctx, int cbOut, Blake2b h)
{
ulong[] pqBlockHash = new ulong[NbBlockSizeInQW];

View File

@@ -25,11 +25,22 @@ using System.Text;
namespace KeePassLib.Cryptography.KeyDerivation
{
public enum Argon2Type
{
// The values must be the same as in the Argon2 specification
D = 0,
ID = 2
}
public sealed partial class Argon2Kdf : KdfEngine
{
private static readonly PwUuid g_uuid = new PwUuid(new byte[] {
private static readonly PwUuid g_uuidD = new PwUuid(new byte[] {
0xEF, 0x63, 0x6D, 0xDF, 0x8C, 0x29, 0x44, 0x4B,
0x91, 0xF7, 0xA9, 0xA4, 0x03, 0xE3, 0x0A, 0x0C });
private static readonly PwUuid g_uuidID = new PwUuid(new byte[] {
0x9E, 0x29, 0x8B, 0x19, 0x56, 0xDB, 0x47, 0x73,
0xB2, 0x3D, 0xFC, 0x3E, 0xC6, 0xF0, 0xA1, 0xE6 });
public const string ParamSalt = "S"; // Byte[]
public const string ParamParallelism = "P"; // UInt32
@@ -56,26 +67,35 @@ namespace KeePassLib.Cryptography.KeyDerivation
internal const uint MaxParallelism = (1 << 24) - 1;
internal const ulong DefaultIterations = 2;
internal const ulong DefaultMemory = 1024 * 1024; // 1 MB
internal const ulong DefaultMemory = 64 * 1024 * 1024; // 64 MB
internal const uint DefaultParallelism = 2;
private readonly Argon2Type m_t;
public override PwUuid Uuid
{
get { return g_uuid; }
get { return ((m_t == Argon2Type.D) ? g_uuidD : g_uuidID); }
}
public override string Name
{
get { return "Argon2"; }
get { return ((m_t == Argon2Type.D) ? "Argon2d" : "Argon2id"); }
}
public override byte[] GetSeed(KdfParameters p)
{ return p.GetByteArray(ParamSalt); }
public Argon2Kdf()
public Argon2Kdf() : this(Argon2Type.D)
{
}
public Argon2Kdf(Argon2Type t)
{
if ((t != Argon2Type.D) && (t != Argon2Type.ID))
throw new NotSupportedException();
m_t = t;
}
public override byte[] GetSeed(KdfParameters p)
{ return p.GetByteArray(ParamSalt); }
public override KdfParameters GetDefaultParameters()
{
KdfParameters p = base.GetDefaultParameters();
@@ -92,7 +112,7 @@ namespace KeePassLib.Cryptography.KeyDerivation
public override void Randomize(KdfParameters p)
{
if(p == null) { Debug.Assert(false); return; }
Debug.Assert(g_uuid.Equals(p.KdfUuid));
Debug.Assert(p.KdfUuid.Equals(this.Uuid));
byte[] pb = CryptoRandom.Instance.GetRandomBytes(32);
p.SetByteArray(ParamSalt, pb);
@@ -128,11 +148,22 @@ namespace KeePassLib.Cryptography.KeyDerivation
byte[] pbSecretKey = p.GetByteArray(ParamSecretKey);
byte[] pbAssocData = p.GetByteArray(ParamAssocData);
if (pbSecretKey != null) {
byte[] pbRet;
if (m_t == Argon2Type.ID)
{
pbRet = Argon2Transform(pbMsg, pbSalt, uPar, uMem,
uIt, 32, v, pbSecretKey, pbAssocData);
}
else
{
if (pbSecretKey != null)
{
throw new ArgumentOutOfRangeException("Unsupported configuration: non-null pbSecretKey");
}
if (pbAssocData != null) {
if (pbAssocData != null)
{
throw new ArgumentOutOfRangeException("Unsupported configuration: non-null pbAssocData");
}
@@ -156,16 +187,18 @@ namespace KeePassLib.Cryptography.KeyDerivation
retPtr, (IntPtr)32,
(IntPtr)0, (IntPtr)0, Argon2_d, v);
if (ret != 0) {
if (ret != 0)
{
throw new Exception("argon2_hash failed with " + ret);
}
byte[] pbRet = new byte[32];
pbRet = new byte[32];
Marshal.Copy(retPtr, pbRet, 0, 32);
Marshal.FreeHGlobal(msgPtr);
Marshal.FreeHGlobal(saltPtr);
Marshal.FreeHGlobal(retPtr);
}
if(uMem > (100UL * 1024UL * 1024UL)) GC.Collect();
return pbRet;

View File

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

View File

@@ -1,6 +1,6 @@
/*
KeePass Password Safe - The Open-Source Password Manager
Copyright (C) 2003-2017 Dominik Reichl <dominik.reichl@t-online.de>
Copyright (C) 2003-2020 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
@@ -41,10 +41,11 @@ namespace KeePassLib.Cryptography.KeyDerivation
private static void EnsureInitialized()
{
if(g_l.Count > 0) return;
if(g_l.Count != 0) return;
g_l.Add(new AesKdf());
g_l.Add(new Argon2Kdf());
g_l.Add(new Argon2Kdf(Argon2Type.D));
g_l.Add(new Argon2Kdf(Argon2Type.ID));
}
internal static KdfParameters GetDefaultParameters()

View File

@@ -50,6 +50,7 @@
<HintPath>..\ProtoBuf\protobuf-net.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Security" />
<Reference Include="System.Xml" />
<Reference Include="System.Core" />
<Reference Include="Mono.Android" />