/* KeePass Password Safe - The Open-Source Password Manager Copyright (C) 2003-2019 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ using System; using System.Collections.Generic; using System.Diagnostics; using System.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; } } }