/// <summary> /// Verify the given Argon2 hash as being that of the given password. /// </summary> /// <param name="encoded"> /// The Argon2 hash string. This has the actual hash along with other parameters used in the hash. /// </param> /// <param name="password"> /// The password to verify. This gets UTF-8 encoded. /// </param> /// <param name="secret"> /// The secret used in the creation of <paramref name="encoded"/>. UTF-8 encoded to create the byte-buffer actually used in the verification. /// May be null for no secret. <see cref="string"/>.<see cref="string.Empty"/> is treated as null. /// </param> /// <param name="secureArrayCall"> /// The methods that get called to secure arrays. A null value defaults to <see cref="SecureArray"/>.<see cref="SecureArray.DefaultCall"/>. /// </param> /// <returns> /// True on success; false otherwise. /// </returns> public static bool Verify( string encoded, string password, string secret, SecureArrayCall secureArrayCall = null) { var secretBuf = string.IsNullOrEmpty(secret) ? null : SecureArray <byte> .Best(Encoding.UTF8.GetByteCount(secret), secureArrayCall); try { if (secretBuf != null) { Encoding.UTF8.GetBytes(secret, 0, secret.Length, secretBuf.Buffer, 0); } using var passwordBuf = SecureArray <byte> .Best(Encoding.UTF8.GetByteCount(password), secureArrayCall); Encoding.UTF8.GetBytes(password, 0, password.Length, passwordBuf.Buffer, 0); return(Verify(encoded, passwordBuf.Buffer, secretBuf?.Buffer, secureArrayCall)); } finally { secretBuf?.Dispose(); } }
// ReSharper disable once UnusedMember.Global /// <summary> /// Verify the given Argon2 hash as being that of the given password. /// </summary> /// <param name="encoded"> /// The Argon2 hash string. This has the actual hash along with other parameters used in the hash. /// </param> /// <param name="password"> /// The password to verify. This gets UTF-8 encoded. /// </param> /// <param name="secureArrayCall"> /// The methods that get called to secure arrays. A null value defaults to <see cref="SecureArray"/>.<see cref="SecureArray.DefaultCall"/>. /// </param> /// <returns> /// True on success; false otherwise. /// </returns> public static bool Verify( string encoded, string password, SecureArrayCall secureArrayCall = null) { return(Verify(encoded, password, null, secureArrayCall)); }
/// <summary> /// Hash the given password to a Argon2 hash string. /// </summary> /// <param name="password"> /// The password to hash. Gets UTF-8 encoded before hashing. /// </param> /// <param name="secret"> /// The secret to use in creating the hash. UTF-8 encoded before hashing. May be null. A /// <see cref="string"/>.<see cref="string.Empty"/> is treated the same as null. /// </param> /// <param name="timeCost"> /// The time cost to use. Defaults to 3. /// </param> /// <param name="memoryCost"> /// The memory cost to use. Defaults to 65536 (64K). /// </param> /// <param name="parallelism"> /// The parallelism to use. Default to 1 (single threaded). /// </param> /// <param name="type"> /// Data-dependent, data-independent, or hybrid. Defaults to hybrid /// (as recommended for password hashing). /// </param> /// <param name="hashLength"> /// The length of the hash in bytes. Note, the string returned base-64 /// encodes this with other parameters so the resulting string is /// significantly longer. /// </param> /// <param name="secureArrayCall"> /// The methods that get called to secure arrays. A null value defaults to <see cref="SecureArray"/>.<see cref="SecureArray.DefaultCall"/>. /// </param> /// <returns> /// The Argon2 hash of the given password. /// </returns> public static string Hash( byte[] password, byte[] secret, int timeCost = 3, int memoryCost = 65536, int parallelism = 1, Argon2Type type = Argon2Type.HybridAddressing, int hashLength = 32, SecureArrayCall secureArrayCall = null) { byte[] salt = new byte[16]; RandomNumberGenerator.Create().GetBytes(salt); return(Hash( new Argon2Config { TimeCost = timeCost, MemoryCost = memoryCost, Threads = parallelism, Lanes = parallelism, Password = password, Secret = secret, Salt = salt, HashLength = hashLength, Version = Argon2Version.Nineteen, Type = type, })); }
/// <summary> /// Hash the given password to a Argon2 hash string. /// </summary> /// <param name="password"> /// The password to hash. Gets UTF-8 encoded before hashing. /// </param> /// <param name="timeCost"> /// The time cost to use. Defaults to 3. /// </param> /// <param name="memoryCost"> /// The memory cost to use. Defaults to 65536 (64K). /// </param> /// <param name="parallelism"> /// The parallelism to use. Default to 1 (single threaded). /// </param> /// <param name="type"> /// Data-dependent, data-independent, or hybrid. Defaults to hybrid /// (as recommended for password hashing). /// </param> /// <param name="hashLength"> /// The length of the hash in bytes. Note, the string returned base-64 /// encodes this with other parameters so the resulting string is /// significantly longer. /// </param> /// <param name="secureArrayCall"> /// The methods that get called to secure arrays. A null value defaults to <see cref="SecureArray"/>.<see cref="SecureArray.DefaultCall"/>. /// </param> /// <returns> /// The Argon2 hash of the given password. /// </returns> public static string Hash( string password, int timeCost = 3, int memoryCost = 65536, int parallelism = 1, Argon2Type type = Argon2Type.HybridAddressing, int hashLength = 32, SecureArrayCall secureArrayCall = null) { return(Hash(password, null, timeCost, memoryCost, parallelism, type, hashLength, secureArrayCall)); }
static readonly byte[] personalization = new byte[] { 99, 107, 98, 45, 100, 101, 102, 97, 117, 108, 116, 45, 104, 97, 115, 104 }; // ckb-default-hash public static byte[] ComputeHash(byte[] data) { Blake2BConfig config = new Blake2BConfig { Personalization = personalization, OutputSizeInBytes = 32 }; SecureArrayCall secureArrayCall = default; return(Blake2B.ComputeHash(data, config, secureArrayCall)); }
/// <summary> /// Verify the given Argon2 hash as being that of the given password. /// </summary> /// <param name="encoded"> /// The Argon2 hash string. This has the actual hash along with other parameters used in the hash. /// </param> /// <param name="password"> /// The password to verify. /// </param> /// <param name="secret"> /// The secret hashed into the password. /// </param> /// <param name="secureArrayCall"> /// The methods that get called to secure arrays. A null value defaults to <see cref="SecureArray"/>.<see cref="SecureArray.DefaultCall"/>. /// </param> /// <returns> /// True on success; false otherwise. /// </returns> public static bool Verify( string encoded, byte[] password, byte[] secret, SecureArrayCall secureArrayCall = null) { var configToVerify = new Argon2Config { Password = password, Secret = secret, SecureArrayCall = secureArrayCall ?? SecureArray.DefaultCall, }; return(Verify(encoded, configToVerify)); }
/// <summary> /// Initializes a new instance of the <see cref="Blake2BCore"/> class. /// </summary> /// <param name="secureArrayCall"> /// The methods that get called to secure arrays. A null value defaults to <see cref="SecureArray"/>.<see cref="SecureArray.DefaultCall"/>. /// </param> /// <param name="lockMemory"> /// Used to set locking strategy for buffers used in creating the hash. The memory /// will always be zeroed prior to destruction. The memory is also always pinned /// so the CLR can't move it and leave extraneous copies floating around in RAM. /// </param> public Blake2BCore(SecureArrayCall secureArrayCall, LockMemoryPolicy lockMemory = LockMemoryPolicy.BestEffort) { switch (lockMemory) { case LockMemoryPolicy.None: this.buf = new SecureArray <byte>(128, SecureArrayType.ZeroedAndPinned, secureArrayCall); this.mbuf = new SecureArray <ulong>(16, SecureArrayType.ZeroedAndPinned, secureArrayCall); this.hbuf = new SecureArray <ulong>(8, SecureArrayType.ZeroedAndPinned, secureArrayCall); break; case LockMemoryPolicy.BestEffort: try { this.buf = new SecureArray <byte>(128, SecureArrayType.ZeroedPinnedAndNoSwap, secureArrayCall); } catch (LockFailException) { this.buf = new SecureArray <byte>(128, SecureArrayType.ZeroedAndPinned, secureArrayCall); } try { this.mbuf = new SecureArray <ulong>(16, SecureArrayType.ZeroedPinnedAndNoSwap, secureArrayCall); } catch (LockFailException) { this.mbuf = new SecureArray <ulong>(16, SecureArrayType.ZeroedAndPinned, secureArrayCall); } try { this.hbuf = new SecureArray <ulong>(8, SecureArrayType.ZeroedPinnedAndNoSwap, secureArrayCall); } catch (LockFailException) { this.hbuf = new SecureArray <ulong>(8, SecureArrayType.ZeroedAndPinned, secureArrayCall); } break; default: this.buf = new SecureArray <byte>(128, SecureArrayType.ZeroedPinnedAndNoSwap, secureArrayCall); this.mbuf = new SecureArray <ulong>(16, SecureArrayType.ZeroedPinnedAndNoSwap, secureArrayCall); this.hbuf = new SecureArray <ulong>(8, SecureArrayType.ZeroedPinnedAndNoSwap, secureArrayCall); break; } }
/// <summary> /// Does a Blake2 hash with the ability to truncate or extend the hash to any length. /// </summary> /// <param name="hash"> /// The buffer to fill with the hash. /// </param> /// <param name="inputBuffer"> /// What to hash. /// </param> /// <param name="secureArrayCall"> /// The methods that get called to secure arrays. A null value defaults to <see cref="SecureArray"/>.<see cref="SecureArray.DefaultCall"/>. /// </param> private static void Blake2BLong(byte[] hash, byte[] inputBuffer, SecureArrayCall secureArrayCall) { var outputLengthBytes = new byte[4]; using var intermediateHash = SecureArray <byte> .Best(Blake2B.OutputLength, secureArrayCall); var config = new Blake2BConfig { Result64ByteBuffer = intermediateHash.Buffer, OutputSizeInBytes = hash.Length > 64 ? 64 : hash.Length, }; Store32(outputLengthBytes, hash.Length); using (var blakeHash = Blake2B.Create(config, secureArrayCall)) { blakeHash.Update(outputLengthBytes); blakeHash.Update(inputBuffer); blakeHash.Finish(); } if (hash.Length <= intermediateHash.Buffer.Length) { Array.Copy(intermediateHash.Buffer, hash, hash.Length); return; } const int b2B2 = Blake2B.OutputLength / 2; Array.Copy(intermediateHash.Buffer, hash, b2B2); int pos = b2B2; int lastHashIndex = hash.Length - Blake2B.OutputLength; var toHash = new byte[Blake2B.OutputLength]; while (pos < lastHashIndex) { Array.Copy(intermediateHash.Buffer, toHash, intermediateHash.Buffer.Length); Blake2B.ComputeHash(toHash, config, secureArrayCall); Array.Copy(intermediateHash.Buffer, 0, hash, pos, b2B2); pos += b2B2; } Array.Copy(intermediateHash.Buffer, toHash, intermediateHash.Buffer.Length); Blake2B.ComputeHash(toHash, config, secureArrayCall); Array.Copy(intermediateHash.Buffer, 0, hash, pos, hash.Length - pos); }
public Blake2BHasher(Blake2BConfig config, SecureArrayCall secureArrayCall) { if (config == null) { config = DefaultConfig; } this.core = new Blake2BCore(secureArrayCall, config.LockMemoryPolicy); this.rawConfig = Blake2IvBuilder.ConfigB(config, null, secureArrayCall); if (config.Key != null && config.Key.Length != 0) { switch (config.LockMemoryPolicy) { case LockMemoryPolicy.None: this.key = new SecureArray <byte>(128, SecureArrayType.ZeroedAndPinned, secureArrayCall); break; case LockMemoryPolicy.BestEffort: try { this.key = new SecureArray <byte>(128, SecureArrayType.ZeroedPinnedAndNoSwap, secureArrayCall); } catch (LockFailException e) { this.key = new SecureArray <byte>(128, SecureArrayType.ZeroedAndPinned, secureArrayCall); } break; default: this.key = new SecureArray <byte>(128, SecureArrayType.ZeroedPinnedAndNoSwap, secureArrayCall); break; } Array.Copy(config.Key, this.key.Buffer, config.Key.Length); } this.outputSizeInBytes = config.OutputSizeInBytes; this.defaultOutputBuffer = config.Result64ByteBuffer; this.Init(); }
/// <summary> /// Hash the given password to a Argon2 hash string. /// </summary> /// <param name="password"> /// The password to hash. Gets UTF-8 encoded before hashing. /// </param> /// <param name="secret"> /// The secret to use in creating the hash. UTF-8 encoded before hashing. May be null. A /// <see cref="string"/>.<see cref="string.Empty"/> is treated the same as null. /// </param> /// <param name="timeCost"> /// The time cost to use. Defaults to 3. /// </param> /// <param name="memoryCost"> /// The memory cost to use. Defaults to 65536 (64K). /// </param> /// <param name="parallelism"> /// The parallelism to use. Default to 1 (single threaded). /// </param> /// <param name="type"> /// Data-dependent, data-independent, or hybrid. Defaults to hybrid /// (as recommended for password hashing). /// </param> /// <param name="hashLength"> /// The length of the hash in bytes. Note, the string returned base-64 /// encodes this with other parameters so the resulting string is /// significantly longer. /// </param> /// <param name="secureArrayCall"> /// The methods that get called to secure arrays. A null value defaults to <see cref="SecureArray"/>.<see cref="SecureArray.DefaultCall"/>. /// </param> /// <returns> /// The Argon2 hash of the given password. /// </returns> public static string Hash( string password, string secret, int timeCost = 3, int memoryCost = 65536, int parallelism = 1, Argon2Type type = Argon2Type.HybridAddressing, int hashLength = 32, SecureArrayCall secureArrayCall = null) { var secretBuf = string.IsNullOrEmpty(secret) ? null : SecureArray <byte> .Best(Encoding.UTF8.GetByteCount(secret), secureArrayCall); try { if (secretBuf != null) { Encoding.UTF8.GetBytes(secret, 0, secret.Length, secretBuf.Buffer, 0); } using (var passwordBuf = SecureArray <byte> .Best(Encoding.UTF8.GetByteCount(password), secureArrayCall)) { Encoding.UTF8.GetBytes(password, 0, password.Length, passwordBuf.Buffer, 0); return(Hash( passwordBuf.Buffer, secretBuf?.Buffer, timeCost, memoryCost, parallelism, type, hashLength, secureArrayCall)); } } finally { secretBuf?.Dispose(); } }
// ReSharper disable once UnusedMember.Global /// <summary> /// Perform a default Blake2 hash on the given buffer. /// </summary> /// <param name="data"> /// The buffer to hash. /// </param> /// <param name="secureArrayCall"> /// The methods that get called to secure arrays. A null value defaults to <see cref="SecureArray"/>.<see cref="SecureArray.DefaultCall"/>. /// </param> /// <returns> /// The hash of the buffer. /// </returns> public static byte[] ComputeHash(byte[] data, SecureArrayCall secureArrayCall) { return(ComputeHash(data, 0, data.Length, null, secureArrayCall)); }
/// <summary> /// Perform a Blake2 hash on the given buffer using the given Blake2 /// configuration. /// </summary> /// <param name="data"> /// The buffer to hash. /// </param> /// <param name="config"> /// The configuration to use. /// </param> /// <param name="secureArrayCall"> /// The methods that get called to secure arrays. A null value defaults to <see cref="SecureArray"/>.<see cref="SecureArray.DefaultCall"/>. /// </param> /// <returns> /// The hash of the buffer. /// </returns> public static byte[] ComputeHash(byte[] data, Blake2BConfig config, SecureArrayCall secureArrayCall) { return(ComputeHash(data, 0, data.Length, config, secureArrayCall)); }
/// <summary> /// Perform a Blake2 hash on the given buffer using the given Blake2 /// configuration. /// </summary> /// <param name="data"> /// The buffer to hash. /// </param> /// <param name="start"> /// The byte in the buffer to start hashing. /// </param> /// <param name="count"> /// The number of bytes to hash. /// </param> /// <param name="config"> /// The configuration to use. /// </param> /// <param name="secureArrayCall"> /// The methods that get called to secure arrays. A null value defaults to <see cref="SecureArray"/>.<see cref="SecureArray.DefaultCall"/>. /// </param> /// <returns> /// The hash of the buffer. /// </returns> public static byte[] ComputeHash(byte[] data, int start, int count, Blake2BConfig config, SecureArrayCall secureArrayCall) { using (var hasher = Create(config, secureArrayCall)) { hasher.Update(data, start, count); return(hasher.Finish()); } }
// ReSharper disable once UnusedMember.Global /// <summary> /// Create a default Blake2 hash. /// </summary> /// <param name="secureArrayCall"> /// The methods that get called to secure arrays. A null value defaults to <see cref="SecureArray"/>.<see cref="SecureArray.DefaultCall"/>. /// </param> /// <returns> /// A <see cref="Hasher"/> that can be converted to a <see cref="HashAlgorithm"/>. /// </returns> public static Hasher Create(SecureArrayCall secureArrayCall) { return(Create(new Blake2BConfig(), secureArrayCall)); }
/// <summary> /// Create a Blake2 hash with the given configuration. /// </summary> /// <param name="config"> /// The configuration to use. /// </param> /// <param name="secureArrayCall"> /// The methods that get called to secure arrays. A null value defaults to <see cref="SecureArray"/>.<see cref="SecureArray.DefaultCall"/>. /// </param> /// <returns> /// A <see cref="Hasher"/> that can be converted to a <see cref="HashAlgorithm"/>. /// </returns> public static Hasher Create(Blake2BConfig config, SecureArrayCall secureArrayCall) { return(new Blake2BHasher(config, secureArrayCall)); }
/// <summary> /// Look for leaks. /// </summary> /// <returns>String with pass/fail message.</returns> public static string TestLeaks() { var locks = new Dictionary <IntPtr, int>(); int lockCount = 0; var badLocks = new List <int>(); int badUnlockCount = 0; SecureArrayCall secureArrayCall = new SecureArrayCall( SecureArray.DefaultCall.ZeroMemory, (m, l) => { string ret = SecureArray.DefaultCall.LockMemory(m, l); if (ret == null) { lock (locks) { ++lockCount; if (locks.ContainsKey(m)) { badLocks.Add(lockCount); } else { locks.Add(m, lockCount); } } } return(ret); }, (m, l) => { lock (locks) { if (locks.ContainsKey(m)) { locks.Remove(m); SecureArray.DefaultCall.UnlockMemory(m, l); } else { ++badUnlockCount; } } }); var hashString = "$argon2i$v=19$m=65536,t=3,p=1$M2f6+jnVc4dyL3BfMQRzoA==$jO/fOrgqxX90XDVhiYZgIVJJcw0lzIXtRFRCEggXYV8="; var password = "******"; const int maxIteration = 10; var memoryDiff = new long[maxIteration]; for (int i = 0; i < maxIteration; i++) { Console.WriteLine($"TestLeaks: Iteration {i + 1} of {maxIteration}"); var prevTotalMemory = GC.GetTotalMemory(true); Argon2.Verify(hashString, password, secureArrayCall); var postTotalMemory = GC.GetTotalMemory(true); memoryDiff[i] = postTotalMemory - prevTotalMemory; } var errs = new List <string>(); if (memoryDiff.All(v => v > 0)) { errs.Add($"Leaked {memoryDiff.Min()} bytes"); } if (badLocks.Any()) { errs.Add($"{badLocks.Count} bad locks: [{string.Join(", ", badLocks.Select(l => $"{l}"))}]."); } if (badUnlockCount > 0) { errs.Add($"{badUnlockCount} bad unlocks."); } if (locks.Any()) { errs.Add($"Leaked {locks.Count} locks: addresses=[{string.Join(", ", locks.Keys.Select(k => $"0x{k.ToInt64():x8}"))}], lock index=[{string.Join(", ", locks.Keys.Select(k => $"{locks[k]}"))}]."); } return(errs.Any() ? $"Leaks: FAILED: {string.Join(" ", errs)}" : "Leaks: Passed"); }
public static SecureArray <ulong> ConfigB(Blake2BConfig config, Blake2BTreeConfig treeConfig, SecureArrayCall secureArrayCall) { bool isSequential = treeConfig == null; if (isSequential) { treeConfig = SequentialTreeConfig; } SecureArray <ulong> rawConfig; try { rawConfig = new SecureArray <ulong>(8, SecureArrayType.ZeroedPinnedAndNoSwap, secureArrayCall); } catch (LockFailException) { rawConfig = new SecureArray <ulong>(8, SecureArrayType.ZeroedAndPinned, secureArrayCall); } //digest length if (config.OutputSizeInBytes <= 0 | config.OutputSizeInBytes > 64) { throw new ArgumentOutOfRangeException( nameof(config), $"Expected 0 < config.OutputSizeInBytes <= 64, got {config.OutputSizeInBytes}"); } rawConfig[0] |= (uint)config.OutputSizeInBytes; //Key length if (config.Key != null) { if (config.Key.Length > 64) { throw new ArgumentException($"Expected key length <= 64, got {config.Key.Length}", nameof(config)); } rawConfig[0] |= (uint)config.Key.Length << 8; } // FanOut rawConfig[0] |= (uint)treeConfig.FanOut << 16; // Depth rawConfig[0] |= (uint)treeConfig.MaxHeight << 24; // Leaf length rawConfig[0] |= ((ulong)(uint)treeConfig.LeafSize) << 32; // Inner length if (!isSequential && (treeConfig.IntermediateHashSize <= 0 || treeConfig.IntermediateHashSize > 64)) { throw new ArgumentOutOfRangeException( nameof(treeConfig), $"Expected 0 < treeConfig.IntermediateHashSize <= 64, got {treeConfig.IntermediateHashSize}"); } rawConfig[2] |= (uint)treeConfig.IntermediateHashSize << 8; // Salt if (config.Salt != null) { if (config.Salt.Length != 16) { throw new ArgumentException("config.Salt has invalid length"); } rawConfig[4] = Blake2BCore.BytesToUInt64(config.Salt, 0); rawConfig[5] = Blake2BCore.BytesToUInt64(config.Salt, 8); } // Personalization if (config.Personalization != null) { if (config.Personalization.Length != 16) { throw new ArgumentException( $"Expected config.Personalization == 16, got {config.Personalization.Length}", nameof(config)); } rawConfig[6] = Blake2BCore.BytesToUInt64(config.Personalization, 0); rawConfig[7] = Blake2BCore.BytesToUInt64(config.Personalization, 8); } return(rawConfig); }
// ReSharper disable once UnusedMember.Global /// <summary> /// Perform a default Blake2 hash on the given buffer. /// </summary> /// <param name="data"> /// The buffer to hash. /// </param> /// <param name="start"> /// The byte in the buffer to start hashing. /// </param> /// <param name="count"> /// The number of bytes to hash. /// </param> /// <param name="secureArrayCall"> /// The methods that get called to secure arrays. A null value defaults to <see cref="SecureArray"/>.<see cref="SecureArray.DefaultCall"/>. /// </param> /// <returns> /// The hash of the buffer. /// </returns> public static byte[] ComputeHash(byte[] data, int start, int count, SecureArrayCall secureArrayCall) { return(ComputeHash(data, start, count, null, secureArrayCall)); }