mirror of
https://github.com/wismna/ModernKeePassLib.git
synced 2025-10-04 08:00:18 -04:00
400 lines
10 KiB
C#
400 lines
10 KiB
C#
/*
|
|
KeePass Password Safe - The Open-Source Password Manager
|
|
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
|
|
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.Runtime.InteropServices;
|
|
using System.Text;
|
|
using System.Threading;
|
|
|
|
using ModernKeePassLib.Native;
|
|
using ModernKeePassLib.Utility;
|
|
|
|
namespace ModernKeePassLib.Cryptography.KeyDerivation
|
|
{
|
|
public sealed partial class AesKdf : KdfEngine
|
|
{
|
|
private static bool TransformKeyGCrypt(byte[] pbData32, byte[] pbSeed32,
|
|
ulong uRounds)
|
|
{
|
|
byte[] pbNewData32 = null;
|
|
try
|
|
{
|
|
if(GCryptInitLib())
|
|
{
|
|
pbNewData32 = new byte[32];
|
|
Array.Copy(pbData32, pbNewData32, 32);
|
|
|
|
if(TransformKeyGCryptPriv(pbNewData32, pbSeed32, uRounds))
|
|
{
|
|
Array.Copy(pbNewData32, pbData32, 32);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
catch(Exception) { }
|
|
finally { if(pbNewData32 != null) MemUtil.ZeroByteArray(pbNewData32); }
|
|
|
|
return false;
|
|
}
|
|
|
|
private static bool TransformKeyBenchmarkGCrypt(uint uTimeMs, out ulong uRounds)
|
|
{
|
|
uRounds = 0;
|
|
|
|
try
|
|
{
|
|
if(GCryptInitLib())
|
|
return TransformKeyBenchmarkGCryptPriv(uTimeMs, ref uRounds);
|
|
}
|
|
catch(Exception) { }
|
|
|
|
return false;
|
|
}
|
|
|
|
private static bool GCryptInitLib()
|
|
{
|
|
if(!NativeLib.IsUnix()) return false; // Independent of workaround state
|
|
if(!MonoWorkarounds.IsRequired(1468)) return false; // Can be turned off
|
|
|
|
// gcry_check_version initializes the library;
|
|
// throws when LibGCrypt is not available
|
|
NativeMethods.gcry_check_version(IntPtr.Zero);
|
|
return true;
|
|
}
|
|
|
|
// =============================================================
|
|
// Multi-threaded implementation
|
|
|
|
// For some reason, the following multi-threaded implementation
|
|
// is slower than the single-threaded implementation below
|
|
// (threading overhead by Mono? LibGCrypt threading issues?)
|
|
/* private sealed class GCryptTransformInfo : IDisposable
|
|
{
|
|
public IntPtr Data16;
|
|
public IntPtr Seed32;
|
|
public ulong Rounds;
|
|
public uint TimeMs;
|
|
|
|
public bool Success = false;
|
|
|
|
public GCryptTransformInfo(byte[] pbData32, int iDataOffset,
|
|
byte[] pbSeed32, ulong uRounds, uint uTimeMs)
|
|
{
|
|
this.Data16 = Marshal.AllocCoTaskMem(16);
|
|
Marshal.Copy(pbData32, iDataOffset, this.Data16, 16);
|
|
|
|
this.Seed32 = Marshal.AllocCoTaskMem(32);
|
|
Marshal.Copy(pbSeed32, 0, this.Seed32, 32);
|
|
|
|
this.Rounds = uRounds;
|
|
this.TimeMs = uTimeMs;
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
if(this.Data16 != IntPtr.Zero)
|
|
{
|
|
Marshal.WriteInt64(this.Data16, 0);
|
|
Marshal.WriteInt64(this.Data16, 8, 0);
|
|
Marshal.FreeCoTaskMem(this.Data16);
|
|
this.Data16 = IntPtr.Zero;
|
|
}
|
|
|
|
if(this.Seed32 != IntPtr.Zero)
|
|
{
|
|
Marshal.FreeCoTaskMem(this.Seed32);
|
|
this.Seed32 = IntPtr.Zero;
|
|
}
|
|
}
|
|
}
|
|
|
|
private static GCryptTransformInfo[] GCryptRun(byte[] pbData32,
|
|
byte[] pbSeed32, ulong uRounds, uint uTimeMs, ParameterizedThreadStart fL,
|
|
ParameterizedThreadStart fR)
|
|
{
|
|
GCryptTransformInfo tiL = new GCryptTransformInfo(pbData32, 0,
|
|
pbSeed32, uRounds, uTimeMs);
|
|
GCryptTransformInfo tiR = new GCryptTransformInfo(pbData32, 16,
|
|
pbSeed32, uRounds, uTimeMs);
|
|
|
|
Thread th = new Thread(fL);
|
|
th.Start(tiL);
|
|
|
|
fR(tiR);
|
|
|
|
th.Join();
|
|
|
|
Marshal.Copy(tiL.Data16, pbData32, 0, 16);
|
|
Marshal.Copy(tiR.Data16, pbData32, 16, 16);
|
|
|
|
tiL.Dispose();
|
|
tiR.Dispose();
|
|
|
|
if(tiL.Success && tiR.Success)
|
|
return new GCryptTransformInfo[2] { tiL, tiR };
|
|
return null;
|
|
}
|
|
|
|
private static bool TransformKeyGCryptPriv(byte[] pbData32, byte[] pbSeed32,
|
|
ulong uRounds)
|
|
{
|
|
return (GCryptRun(pbData32, pbSeed32, uRounds, 0,
|
|
new ParameterizedThreadStart(AesKdf.GCryptTransformTh),
|
|
new ParameterizedThreadStart(AesKdf.GCryptTransformTh)) != null);
|
|
}
|
|
|
|
private static bool GCryptInitCipher(ref IntPtr h, GCryptTransformInfo ti)
|
|
{
|
|
NativeMethods.gcry_cipher_open(ref h, NativeMethods.GCRY_CIPHER_AES256,
|
|
NativeMethods.GCRY_CIPHER_MODE_ECB, 0);
|
|
if(h == IntPtr.Zero) { Debug.Assert(false); return false; }
|
|
|
|
IntPtr n32 = new IntPtr(32);
|
|
if(NativeMethods.gcry_cipher_setkey(h, ti.Seed32, n32) != 0)
|
|
{
|
|
Debug.Assert(false);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private static void GCryptTransformTh(object o)
|
|
{
|
|
IntPtr h = IntPtr.Zero;
|
|
try
|
|
{
|
|
GCryptTransformInfo ti = (o as GCryptTransformInfo);
|
|
if(ti == null) { Debug.Assert(false); return; }
|
|
|
|
if(!GCryptInitCipher(ref h, ti)) return;
|
|
|
|
IntPtr n16 = new IntPtr(16);
|
|
for(ulong u = 0; u < ti.Rounds; ++u)
|
|
{
|
|
if(NativeMethods.gcry_cipher_encrypt(h, ti.Data16, n16,
|
|
IntPtr.Zero, IntPtr.Zero) != 0)
|
|
{
|
|
Debug.Assert(false);
|
|
return;
|
|
}
|
|
}
|
|
|
|
ti.Success = true;
|
|
}
|
|
catch(Exception) { Debug.Assert(false); }
|
|
finally
|
|
{
|
|
try { if(h != IntPtr.Zero) NativeMethods.gcry_cipher_close(h); }
|
|
catch(Exception) { Debug.Assert(false); }
|
|
}
|
|
}
|
|
|
|
private static bool TransformKeyBenchmarkGCryptPriv(uint uTimeMs, ref ulong uRounds)
|
|
{
|
|
GCryptTransformInfo[] v = GCryptRun(new byte[32], new byte[32],
|
|
0, uTimeMs,
|
|
new ParameterizedThreadStart(AesKdf.GCryptBenchmarkTh),
|
|
new ParameterizedThreadStart(AesKdf.GCryptBenchmarkTh));
|
|
|
|
if(v != null)
|
|
{
|
|
ulong uL = Math.Min(v[0].Rounds, ulong.MaxValue >> 1);
|
|
ulong uR = Math.Min(v[1].Rounds, ulong.MaxValue >> 1);
|
|
uRounds = (uL + uR) / 2;
|
|
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private static void GCryptBenchmarkTh(object o)
|
|
{
|
|
IntPtr h = IntPtr.Zero;
|
|
try
|
|
{
|
|
GCryptTransformInfo ti = (o as GCryptTransformInfo);
|
|
if(ti == null) { Debug.Assert(false); return; }
|
|
|
|
if(!GCryptInitCipher(ref h, ti)) return;
|
|
|
|
ulong r = 0;
|
|
IntPtr n16 = new IntPtr(16);
|
|
int tStart = Environment.TickCount;
|
|
while(true)
|
|
{
|
|
for(ulong j = 0; j < BenchStep; ++j)
|
|
{
|
|
if(NativeMethods.gcry_cipher_encrypt(h, ti.Data16, n16,
|
|
IntPtr.Zero, IntPtr.Zero) != 0)
|
|
{
|
|
Debug.Assert(false);
|
|
return;
|
|
}
|
|
}
|
|
|
|
r += BenchStep;
|
|
if(r < BenchStep) // Overflow check
|
|
{
|
|
r = ulong.MaxValue;
|
|
break;
|
|
}
|
|
|
|
uint tElapsed = (uint)(Environment.TickCount - tStart);
|
|
if(tElapsed > ti.TimeMs) break;
|
|
}
|
|
|
|
ti.Rounds = r;
|
|
ti.Success = true;
|
|
}
|
|
catch(Exception) { Debug.Assert(false); }
|
|
finally
|
|
{
|
|
try { if(h != IntPtr.Zero) NativeMethods.gcry_cipher_close(h); }
|
|
catch(Exception) { Debug.Assert(false); }
|
|
}
|
|
} */
|
|
|
|
// =============================================================
|
|
// Single-threaded implementation
|
|
|
|
private static bool GCryptInitCipher(ref IntPtr h, IntPtr pSeed32)
|
|
{
|
|
NativeMethods.gcry_cipher_open(ref h, NativeMethods.GCRY_CIPHER_AES256,
|
|
NativeMethods.GCRY_CIPHER_MODE_ECB, 0);
|
|
if(h == IntPtr.Zero) { Debug.Assert(false); return false; }
|
|
|
|
IntPtr n32 = new IntPtr(32);
|
|
if(NativeMethods.gcry_cipher_setkey(h, pSeed32, n32) != 0)
|
|
{
|
|
Debug.Assert(false);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private static bool GCryptBegin(byte[] pbData32, byte[] pbSeed32,
|
|
ref IntPtr h, ref IntPtr pData32, ref IntPtr pSeed32)
|
|
{
|
|
pData32 = Marshal.AllocCoTaskMem(32);
|
|
pSeed32 = Marshal.AllocCoTaskMem(32);
|
|
|
|
Marshal.Copy(pbData32, 0, pData32, 32);
|
|
Marshal.Copy(pbSeed32, 0, pSeed32, 32);
|
|
|
|
return GCryptInitCipher(ref h, pSeed32);
|
|
}
|
|
|
|
private static void GCryptEnd(IntPtr h, IntPtr pData32, IntPtr pSeed32)
|
|
{
|
|
NativeMethods.gcry_cipher_close(h);
|
|
|
|
Marshal.WriteInt64(pData32, 0);
|
|
Marshal.WriteInt64(pData32, 8, 0);
|
|
Marshal.WriteInt64(pData32, 16, 0);
|
|
Marshal.WriteInt64(pData32, 24, 0);
|
|
|
|
Marshal.FreeCoTaskMem(pData32);
|
|
Marshal.FreeCoTaskMem(pSeed32);
|
|
}
|
|
|
|
private static bool TransformKeyGCryptPriv(byte[] pbData32, byte[] pbSeed32,
|
|
ulong uRounds)
|
|
{
|
|
IntPtr h = IntPtr.Zero, pData32 = IntPtr.Zero, pSeed32 = IntPtr.Zero;
|
|
if(!GCryptBegin(pbData32, pbSeed32, ref h, ref pData32, ref pSeed32))
|
|
return false;
|
|
|
|
try
|
|
{
|
|
IntPtr n32 = new IntPtr(32);
|
|
for(ulong i = 0; i < uRounds; ++i)
|
|
{
|
|
if(NativeMethods.gcry_cipher_encrypt(h, pData32, n32,
|
|
IntPtr.Zero, IntPtr.Zero) != 0)
|
|
{
|
|
Debug.Assert(false);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
Marshal.Copy(pData32, pbData32, 0, 32);
|
|
return true;
|
|
}
|
|
catch(Exception) { Debug.Assert(false); }
|
|
finally { GCryptEnd(h, pData32, pSeed32); }
|
|
|
|
return false;
|
|
}
|
|
|
|
private static bool TransformKeyBenchmarkGCryptPriv(uint uTimeMs, ref ulong uRounds)
|
|
{
|
|
byte[] pbData32 = new byte[32];
|
|
byte[] pbSeed32 = new byte[32];
|
|
|
|
IntPtr h = IntPtr.Zero, pData32 = IntPtr.Zero, pSeed32 = IntPtr.Zero;
|
|
if(!GCryptBegin(pbData32, pbSeed32, ref h, ref pData32, ref pSeed32))
|
|
return false;
|
|
|
|
uint uMaxMs = uTimeMs;
|
|
ulong uDiv = 1;
|
|
if(uMaxMs <= (uint.MaxValue >> 1)) { uMaxMs *= 2U; uDiv = 2; }
|
|
|
|
try
|
|
{
|
|
ulong r = 0;
|
|
IntPtr n32 = new IntPtr(32);
|
|
int tStart = Environment.TickCount;
|
|
while(true)
|
|
{
|
|
for(ulong j = 0; j < BenchStep; ++j)
|
|
{
|
|
if(NativeMethods.gcry_cipher_encrypt(h, pData32, n32,
|
|
IntPtr.Zero, IntPtr.Zero) != 0)
|
|
{
|
|
Debug.Assert(false);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
r += BenchStep;
|
|
if(r < BenchStep) // Overflow check
|
|
{
|
|
r = ulong.MaxValue;
|
|
break;
|
|
}
|
|
|
|
uint tElapsed = (uint)(Environment.TickCount - tStart);
|
|
if(tElapsed > uMaxMs) break;
|
|
}
|
|
|
|
uRounds = r / uDiv;
|
|
return true;
|
|
}
|
|
catch(Exception) { Debug.Assert(false); }
|
|
finally { GCryptEnd(h, pData32, pSeed32); }
|
|
|
|
return false;
|
|
}
|
|
}
|
|
}
|