Updates Argon2 to use Spans/Stackalloc (#140)
This commit is contained in:
parent
2770ab1d50
commit
5d51e97c52
15 changed files with 115 additions and 230 deletions
|
|
@ -1,127 +1,111 @@
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace Server.Accounting.Security
|
namespace System.Security.Cryptography
|
||||||
{
|
{
|
||||||
internal interface IArgon2
|
internal interface IArgon2
|
||||||
{
|
{
|
||||||
public Argon2Error Hash(uint t_cost, uint m_cost, uint parallelism,
|
public Argon2Error Hash(uint t_cost, uint m_cost, uint parallelism,
|
||||||
byte[] pwd,
|
ReadOnlySpan<byte> pwd,
|
||||||
byte[] salt,
|
ReadOnlySpan<byte> salt,
|
||||||
byte[] hash,
|
Span<byte> hash,
|
||||||
byte[] encoded,
|
Span<byte> encoded,
|
||||||
int type, int version);
|
int type, int version);
|
||||||
|
|
||||||
Argon2Error Verify(byte[] encoded, byte[] pwd, int pwdlen, int type);
|
Argon2Error Verify(ReadOnlySpan<byte> encoded, ReadOnlySpan<byte> pwd, int pwdlen, int type);
|
||||||
|
|
||||||
// TODO: Change str to use ReadOnlySpan<char> then convert to pointer later
|
// TODO: Change str to use ReadOnlySpan<char> then convert to pointer later
|
||||||
Argon2Error Decode(Argon2Context ctx, string str, int type);
|
Argon2Error Decode(Argon2Context ctx, ReadOnlySpan<byte> str, int type);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static class Argon2
|
internal static class Argon2
|
||||||
{
|
{
|
||||||
internal static readonly IArgon2 Library;
|
internal static readonly bool IsDarwin = RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
|
||||||
|
internal static readonly bool IsFreeBSD = RuntimeInformation.IsOSPlatform(OSPlatform.FreeBSD);
|
||||||
|
internal static readonly bool IsLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
|
||||||
|
|
||||||
static Argon2()
|
internal static readonly IArgon2 Library =
|
||||||
{
|
IsLinux || IsFreeBSD || IsDarwin ? (IArgon2)new UnixArgon2() : new WindowsArgon2();
|
||||||
if (RuntimeUtility.IsUnix)
|
|
||||||
Library = new UnixArgon2();
|
|
||||||
else
|
|
||||||
Library = new WindowsArgon2();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class WindowsArgon2 : IArgon2
|
internal class WindowsArgon2 : IArgon2
|
||||||
{
|
{
|
||||||
public Argon2Error Hash(uint t_cost, uint m_cost, uint parallelism,
|
public Argon2Error Hash(uint t_cost, uint m_cost, uint parallelism,
|
||||||
byte[] pwd,
|
ReadOnlySpan<byte> pwd,
|
||||||
byte[] salt,
|
ReadOnlySpan<byte> salt,
|
||||||
byte[] hash,
|
Span<byte> hash,
|
||||||
byte[] encoded,
|
Span<byte> encoded,
|
||||||
int type, int version) =>
|
int type, int version) =>
|
||||||
NativeMethods.crypto_argon2_hash(t_cost, m_cost, parallelism,
|
SafeNativeMethods.crypto_argon2_hash(t_cost, m_cost, parallelism,
|
||||||
pwd, pwd.Length,
|
in pwd.GetPinnableReference(), pwd.Length,
|
||||||
salt, salt.Length,
|
in salt.GetPinnableReference(), salt.Length,
|
||||||
hash, hash.Length,
|
ref hash.GetPinnableReference(), hash.Length,
|
||||||
encoded, encoded.Length,
|
ref encoded.GetPinnableReference(), encoded.Length,
|
||||||
type, version
|
type, version
|
||||||
);
|
);
|
||||||
|
|
||||||
public Argon2Error Verify(byte[] encoded, byte[] pwd, int pwdlen, int type) =>
|
public Argon2Error Verify(ReadOnlySpan<byte> encoded, ReadOnlySpan<byte> pwd, int pwdlen, int type) =>
|
||||||
NativeMethods.crypto_argon2_verify(encoded, pwd, pwdlen, type);
|
SafeNativeMethods.crypto_argon2_verify(in encoded.GetPinnableReference(), in pwd.GetPinnableReference(), pwdlen, type);
|
||||||
|
|
||||||
public Argon2Error Decode(Argon2Context ctx, string str, int type)
|
public Argon2Error Decode(Argon2Context ctx, ReadOnlySpan<byte> str, int type) =>
|
||||||
{
|
SafeNativeMethods.crypto_decode_string(ctx, in str.GetPinnableReference(), type);
|
||||||
byte[] bytes = new byte[str.Length];
|
|
||||||
Encoding.ASCII.GetBytes(str, bytes);
|
|
||||||
|
|
||||||
// TODO: Use pointers instead
|
internal static class SafeNativeMethods
|
||||||
return NativeMethods.crypto_decode_string(ctx, bytes, type);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static class NativeMethods
|
|
||||||
{
|
{
|
||||||
[DllImport("argon2.dll", EntryPoint = "crypto_argon2_hash", CallingConvention = CallingConvention.Cdecl)]
|
[DllImport("argon2.dll", EntryPoint = "crypto_argon2_hash", CallingConvention = CallingConvention.Cdecl)]
|
||||||
internal static extern Argon2Error crypto_argon2_hash(uint t_cost, uint m_cost, uint parallelism,
|
internal static extern Argon2Error crypto_argon2_hash(uint t_cost, uint m_cost, uint parallelism,
|
||||||
byte[] pwd, int pwdlen,
|
in byte pwd, int pwdlen,
|
||||||
byte[] salt, int saltlen,
|
in byte salt, int saltlen,
|
||||||
byte[] hash, int hashlen,
|
ref byte hash, int hashlen,
|
||||||
byte[] encoded, int encodedlen,
|
ref byte encoded, int encodedlen,
|
||||||
int type, int version
|
int type, int version
|
||||||
);
|
);
|
||||||
|
|
||||||
[DllImport("argon2.dll", EntryPoint = "crypto_argon2_verify", CallingConvention = CallingConvention.Cdecl)]
|
[DllImport("argon2.dll", EntryPoint = "crypto_argon2_verify", CallingConvention = CallingConvention.Cdecl)]
|
||||||
internal static extern Argon2Error crypto_argon2_verify(byte[] encoded, byte[] pwd, int pwdlen, int type);
|
internal static extern Argon2Error crypto_argon2_verify(in byte encoded, in byte pwd, int pwdlen, int type);
|
||||||
|
|
||||||
[DllImport("argon2.dll", EntryPoint = "crypto_decode_string", CallingConvention = CallingConvention.Cdecl)]
|
[DllImport("argon2.dll", EntryPoint = "crypto_decode_string", CallingConvention = CallingConvention.Cdecl)]
|
||||||
internal static extern Argon2Error crypto_decode_string(Argon2Context ctx, byte[] str, int type);
|
internal static extern Argon2Error crypto_decode_string(Argon2Context ctx, in byte str, int type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class UnixArgon2 : IArgon2
|
internal class UnixArgon2 : IArgon2
|
||||||
{
|
{
|
||||||
public Argon2Error Hash(uint t_cost, uint m_cost, uint parallelism,
|
public Argon2Error Hash(uint t_cost, uint m_cost, uint parallelism,
|
||||||
byte[] pwd,
|
ReadOnlySpan<byte> pwd,
|
||||||
byte[] salt,
|
ReadOnlySpan<byte> salt,
|
||||||
byte[] hash,
|
Span<byte> hash,
|
||||||
byte[] encoded,
|
Span<byte> encoded,
|
||||||
int type, int version) =>
|
int type, int version) =>
|
||||||
NativeMethods.crypto_argon2_hash(t_cost, m_cost, parallelism,
|
SafeNativeMethods.crypto_argon2_hash(t_cost, m_cost, parallelism,
|
||||||
pwd, pwd.Length,
|
in pwd.GetPinnableReference(), pwd.Length,
|
||||||
salt, salt.Length,
|
in salt.GetPinnableReference(), salt.Length,
|
||||||
hash, hash.Length,
|
ref hash.GetPinnableReference(), hash.Length,
|
||||||
encoded, encoded.Length,
|
ref encoded.GetPinnableReference(), encoded.Length,
|
||||||
type, version
|
type, version
|
||||||
);
|
);
|
||||||
|
|
||||||
public Argon2Error Verify(byte[] encoded, byte[] pwd, int pwdlen, int type) =>
|
public Argon2Error Verify(ReadOnlySpan<byte> encoded, ReadOnlySpan<byte> pwd, int pwdlen, int type) =>
|
||||||
NativeMethods.crypto_argon2_verify(encoded, pwd, pwdlen, type);
|
SafeNativeMethods.crypto_argon2_verify(in encoded.GetPinnableReference(), in pwd.GetPinnableReference(), pwdlen, type);
|
||||||
|
|
||||||
public Argon2Error Decode(Argon2Context ctx, string str, int type)
|
public Argon2Error Decode(Argon2Context ctx, ReadOnlySpan<byte> str, int type) =>
|
||||||
{
|
SafeNativeMethods.crypto_decode_string(ctx, in str.GetPinnableReference(), type);
|
||||||
byte[] bytes = new byte[str.Length];
|
|
||||||
Encoding.ASCII.GetBytes(str, bytes);
|
|
||||||
|
|
||||||
// TODO: Use pointers instead
|
internal static class SafeNativeMethods
|
||||||
return NativeMethods.crypto_decode_string(ctx, bytes, type);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static class NativeMethods
|
|
||||||
{
|
{
|
||||||
[DllImport("libargon2", EntryPoint = "argon2_hash")]
|
[DllImport("libargon2", EntryPoint = "argon2_hash")]
|
||||||
internal static extern Argon2Error crypto_argon2_hash(uint t_cost, uint m_cost, uint parallelism,
|
internal static extern Argon2Error crypto_argon2_hash(uint t_cost, uint m_cost, uint parallelism,
|
||||||
byte[] pwd, int pwdlen,
|
in byte pwd, int pwdlen,
|
||||||
byte[] salt, int saltlen,
|
in byte salt, int saltlen,
|
||||||
byte[] hash, int hashlen,
|
ref byte hash, int hashlen,
|
||||||
byte[] encoded, int encodedlen,
|
ref byte encoded, int encodedlen,
|
||||||
int type, int version
|
int type, int version
|
||||||
);
|
);
|
||||||
|
|
||||||
[DllImport("libargon2", EntryPoint = "argon2_verify")]
|
[DllImport("libargon2", EntryPoint = "argon2_verify")]
|
||||||
internal static extern Argon2Error crypto_argon2_verify(byte[] encoded, byte[] pwd, int pwdlen, int type);
|
internal static extern unsafe Argon2Error crypto_argon2_verify(in byte encoded, in byte pwd, int pwdlen, int type);
|
||||||
|
|
||||||
[DllImport("libargon2", EntryPoint = "decode_string")]
|
[DllImport("libargon2", EntryPoint = "decode_string")]
|
||||||
internal static extern Argon2Error crypto_decode_string(Argon2Context ctx, byte[] str, int type);
|
internal static extern unsafe Argon2Error crypto_decode_string(Argon2Context ctx, in byte str, int type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<RuntimeIdentifiers>win-x64;linux-x64;osx-x64</RuntimeIdentifiers>
|
<RuntimeIdentifiers>win-x64;linux-x64;osx-x64</RuntimeIdentifiers>
|
||||||
<PackageVersion>1.1.0</PackageVersion>
|
<PackageVersion>1.1.8</PackageVersion>
|
||||||
<RootNamespace>Server</RootNamespace>
|
<RootNamespace>Server</RootNamespace>
|
||||||
<AssemblyName>Argon2.Bindings</AssemblyName>
|
<AssemblyName>Argon2.Bindings</AssemblyName>
|
||||||
<PlatformTarget>x64</PlatformTarget>
|
<PlatformTarget>x64</PlatformTarget>
|
||||||
|
|
@ -12,7 +12,7 @@
|
||||||
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
|
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
|
||||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||||
<Configurations>Debug;Release;Analyze</Configurations>
|
<Configurations>Debug;Release;Analyze</Configurations>
|
||||||
<AssemblyVersion>1.0.0</AssemblyVersion>
|
<AssemblyVersion>1.1.8</AssemblyVersion>
|
||||||
<Platforms>x64</Platforms>
|
<Platforms>x64</Platforms>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
<package >
|
<package>
|
||||||
<metadata>
|
<metadata>
|
||||||
<id>Argon2.Bindings</id>
|
<id>Argon2.Bindings</id>
|
||||||
<version>1.0.0</version>
|
<version>1.1.6</version>
|
||||||
<authors>Kamron Batman</authors>
|
<authors>Kamron Batman</authors>
|
||||||
<owners>Kamron Batman</owners>
|
<owners>Kamron Batman</owners>
|
||||||
<license type="expression">MIT</license>
|
<license type="expression">MIT</license>
|
||||||
<projectUrl>https://github.com/modernUO/ModernUO</projectUrl>
|
<projectUrl>https://github.com/modernUO/ModernUO</projectUrl>
|
||||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||||
<description>Argon2 C# Bindings for ModernUO</description>
|
<description>Argon2 C# Bindings for ModernUO</description>
|
||||||
<releaseNotes>Initial release</releaseNotes>
|
<releaseNotes>Updated to use spans/pointers</releaseNotes>
|
||||||
<copyright>Copyright 2020</copyright>
|
<copyright>Copyright 2020</copyright>
|
||||||
</metadata>
|
</metadata>
|
||||||
</package>
|
</package>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
using System;
|
using System.Runtime.InteropServices;
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace Server.Accounting.Security
|
namespace System.Security.Cryptography
|
||||||
{
|
{
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
internal class Argon2Context
|
internal class Argon2Context
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
namespace Server.Accounting.Security
|
namespace System.Security.Cryptography
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An enumeration of the possible error codes which are returned from Daniel Dinu and
|
/// An enumeration of the possible error codes which are returned from Daniel Dinu and
|
||||||
|
|
@ -23,6 +23,11 @@
|
||||||
/// </summary>
|
/// </summary>
|
||||||
SALT_TOO_SHORT = -6,
|
SALT_TOO_SHORT = -6,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The salt is too big
|
||||||
|
/// </summary>
|
||||||
|
SALT_TOO_LONG = -7,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The time cost is less than 1
|
/// The time cost is less than 1
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
using System;
|
namespace System.Security.Cryptography
|
||||||
|
|
||||||
namespace Server.Accounting.Security
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An exception class to wrap the errors returned by Daniel Dinu and Dmitry Khovratovich's Argon2 library.
|
/// An exception class to wrap the errors returned by Daniel Dinu and Dmitry Khovratovich's Argon2 library.
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,8 @@
|
||||||
using System;
|
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Security.Cryptography;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
// using System.Text.RegularExpressions;
|
// using System.Text.RegularExpressions;
|
||||||
|
|
||||||
namespace Server.Accounting.Security
|
namespace System.Security.Cryptography
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// PasswordHasher is a class for creating Argon2 hashes and verifying them. This is a wrapper around
|
/// PasswordHasher is a class for creating Argon2 hashes and verifying them. This is a wrapper around
|
||||||
|
|
@ -93,39 +91,11 @@ namespace Server.Accounting.Security
|
||||||
/// <param name="password">A string representing the password to be hashed. The password is first decoded into bytes using StringEncoding (default: Encoding.UTF8)</param>
|
/// <param name="password">A string representing the password to be hashed. The password is first decoded into bytes using StringEncoding (default: Encoding.UTF8)</param>
|
||||||
/// <returns>A formatted string representing the hashed password, encoded with the parameters used to perform the hash</returns>
|
/// <returns>A formatted string representing the hashed password, encoded with the parameters used to perform the hash</returns>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Hash(string password)
|
public string Hash(ReadOnlySpan<char> password)
|
||||||
{
|
{
|
||||||
CheckNull("Hash", "password", password);
|
Span<byte> salt = stackalloc byte[16];
|
||||||
|
Rng.GetBytes(salt);
|
||||||
return Hash(StringEncoding.GetBytes(password));
|
return Hash(password, salt);
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Hash the raw password bytes using Argon2 with a cryptographically-secure, random, 16-byte salt.
|
|
||||||
/// Do not compare two Argon2 hashes directly. Instead, use the Verify or VerifyAndUpdate methods.
|
|
||||||
/// <param name="password">The raw bytes of the password to be hashed</param>
|
|
||||||
/// <returns>A formatted string representing the hashed password, encoded with the parameters used to perform the hash</returns>
|
|
||||||
/// </summary>
|
|
||||||
public string Hash(byte[] password)
|
|
||||||
{
|
|
||||||
CheckNull("Hash", "password", password);
|
|
||||||
|
|
||||||
return Hash(password, GenerateSalt());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Hash the password using Argon2 with the specified salt.
|
|
||||||
/// Unless you need to specify your own salt for interoperability purposes, prefer the Hash(string password) overload instead.
|
|
||||||
/// Do not compare two Argon2 hashes directly. Instead, use the Verify or VerifyAndUpdate methods.
|
|
||||||
/// <param name="password">A string representing the password to be hashed. The password is first decoded into bytes using StringEncoding (default: Encoding.UTF8)</param>
|
|
||||||
/// <param name="salt">A string representing the salt to be used for the hash. The salt must be at least 8 bytes. The salt is first decoded into bytes using StringEncoding (default: Encoding.UTF8)</param>
|
|
||||||
/// <returns>A formatted string representing the hashed password, encoded with the parameters used to perform the hash</returns>
|
|
||||||
/// </summary>
|
|
||||||
public string Hash(string password, string salt)
|
|
||||||
{
|
|
||||||
CheckNull("Hash", "password", password, "salt", salt);
|
|
||||||
|
|
||||||
return Hash(StringEncoding.GetBytes(password), StringEncoding.GetBytes(salt));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -136,17 +106,18 @@ namespace Server.Accounting.Security
|
||||||
/// <param name="salt">The raw salt bytes to be used for the hash. The salt must be at least 8 bytes.</param>
|
/// <param name="salt">The raw salt bytes to be used for the hash. The salt must be at least 8 bytes.</param>
|
||||||
/// <returns>A formatted string representing the hashed password, encoded with the parameters used to perform the hash</returns>
|
/// <returns>A formatted string representing the hashed password, encoded with the parameters used to perform the hash</returns>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Hash(byte[] password, byte[] salt)
|
public string Hash(ReadOnlySpan<char> password, ReadOnlySpan<byte> salt)
|
||||||
{
|
{
|
||||||
CheckNull("Hash", "password", password, "salt", salt);
|
Span<byte> hash = stackalloc byte[(int)HashLength];
|
||||||
|
Span<byte> encoded = stackalloc byte[(int)(39 + ((HashLength + salt.Length) * 4 + 3) / 3)];
|
||||||
|
Span<byte> passwordBytes = stackalloc byte[StringEncoding.GetByteCount(password)];
|
||||||
|
StringEncoding.GetBytes(password, passwordBytes);
|
||||||
|
|
||||||
byte[] hash = new byte[HashLength];
|
|
||||||
byte[] encoded = new byte[39 + ((HashLength + salt.Length) * 4 + 3) / 3];
|
|
||||||
var result = Argon2.Library.Hash(
|
var result = Argon2.Library.Hash(
|
||||||
TimeCost,
|
TimeCost,
|
||||||
MemoryCost,
|
MemoryCost,
|
||||||
Parallelism,
|
Parallelism,
|
||||||
password,
|
passwordBytes,
|
||||||
salt,
|
salt,
|
||||||
hash,
|
hash,
|
||||||
encoded,
|
encoded,
|
||||||
|
|
@ -161,22 +132,7 @@ namespace Server.Accounting.Security
|
||||||
while (encoded[firstNonNull] == 0)
|
while (encoded[firstNonNull] == 0)
|
||||||
firstNonNull--;
|
firstNonNull--;
|
||||||
|
|
||||||
return Encoding.ASCII.GetString(encoded, 0, firstNonNull + 1);
|
return Encoding.ASCII.GetString(encoded.Slice(0, firstNonNull + 1));
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Hash the password using Argon2 with the specified salt. The HashRaw methods may be used for password-based key derivation.
|
|
||||||
/// Unless you're using HashRaw for key deriviation or for interoperability purposes, the Hash methods should be used in favor of the HashRaw methods.
|
|
||||||
/// <param name="password">A string representing the password to be hashed. The password is first decoded into bytes using StringEncoding (default: Encoding.UTF8)</param>
|
|
||||||
/// <param name="salt">A string representing the salt to be used for the hash. The salt must be at least 8 bytes. The salt is first decoded into bytes using StringEncoding (default: Encoding.UTF8)</param>
|
|
||||||
/// <returns>A byte array containing only the resulting hash</returns>
|
|
||||||
/// </summary>
|
|
||||||
public byte[] HashRaw(string password, string salt)
|
|
||||||
{
|
|
||||||
CheckNull("HashRaw", "password", password, "salt", salt);
|
|
||||||
|
|
||||||
return HashRaw(StringEncoding.GetBytes(password), StringEncoding.GetBytes(salt));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -186,15 +142,16 @@ namespace Server.Accounting.Security
|
||||||
/// <param name="salt">The raw salt bytes to be used for the hash. The salt must be at least 8 bytes.</param>
|
/// <param name="salt">The raw salt bytes to be used for the hash. The salt must be at least 8 bytes.</param>
|
||||||
/// <returns>A byte array containing only the resulting hash</returns>
|
/// <returns>A byte array containing only the resulting hash</returns>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public byte[] HashRaw(byte[] password, byte[] salt)
|
public void HashRaw(ReadOnlySpan<char> password, ReadOnlySpan<byte> salt, Span<byte> hash)
|
||||||
{
|
{
|
||||||
byte[] hash = new byte[(int)HashLength];
|
Span<byte> passwordBytes = stackalloc byte[StringEncoding.GetByteCount(password)];
|
||||||
|
StringEncoding.GetBytes(password, passwordBytes);
|
||||||
|
|
||||||
var result = Argon2.Library.Hash(
|
var result = Argon2.Library.Hash(
|
||||||
TimeCost,
|
TimeCost,
|
||||||
MemoryCost,
|
MemoryCost,
|
||||||
Parallelism,
|
Parallelism,
|
||||||
password,
|
passwordBytes,
|
||||||
salt,
|
salt,
|
||||||
hash,
|
hash,
|
||||||
null,
|
null,
|
||||||
|
|
@ -204,8 +161,6 @@ namespace Server.Accounting.Security
|
||||||
|
|
||||||
if (result != Argon2Error.OK)
|
if (result != Argon2Error.OK)
|
||||||
throw new Argon2Exception("raw hashing", result);
|
throw new Argon2Exception("raw hashing", result);
|
||||||
|
|
||||||
return hash;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -217,11 +172,13 @@ namespace Server.Accounting.Security
|
||||||
/// <param name="password">The password to hash and compare its result to expectedHash. The password is first decoded into bytes using StringEncoding (default: Encoding.UTF8)</param>
|
/// <param name="password">The password to hash and compare its result to expectedHash. The password is first decoded into bytes using StringEncoding (default: Encoding.UTF8)</param>
|
||||||
/// <returns>Whether the password results in the expectedHash when hashed</returns>
|
/// <returns>Whether the password results in the expectedHash when hashed</returns>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool Verify(string expectedHash, string password)
|
public bool Verify(ReadOnlySpan<char> expectedHash, ReadOnlySpan<char> password)
|
||||||
{
|
{
|
||||||
CheckNull("Verify", "expectedHash", expectedHash, "password", password);
|
Span<byte> expectedHashBytes = stackalloc byte[StringEncoding.GetByteCount(expectedHash)];
|
||||||
|
StringEncoding.GetBytes(expectedHash, expectedHashBytes);
|
||||||
return Verify(expectedHash, StringEncoding.GetBytes(password));
|
Span<byte> passwordBytes = stackalloc byte[StringEncoding.GetByteCount(password)];
|
||||||
|
StringEncoding.GetBytes(password, passwordBytes);
|
||||||
|
return Verify(expectedHashBytes, passwordBytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -232,11 +189,9 @@ namespace Server.Accounting.Security
|
||||||
/// <param name="password">The raw password bytes to hash and compare its result to expectedHash</param>
|
/// <param name="password">The raw password bytes to hash and compare its result to expectedHash</param>
|
||||||
/// <returns>Whether the password results in the expectedHash when hashed</returns>
|
/// <returns>Whether the password results in the expectedHash when hashed</returns>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool Verify(string expectedHash, byte[] password)
|
public bool Verify(ReadOnlySpan<byte> expectedHash, ReadOnlySpan<byte> password)
|
||||||
{
|
{
|
||||||
CheckNull("Verify", "expectedHash", expectedHash, "password", password);
|
var result = Argon2.Library.Verify(expectedHash, password, password.Length, (int)ArgonType);
|
||||||
|
|
||||||
var result = Argon2.Library.Verify(StringEncoding.GetBytes(expectedHash), password, password.Length, (int)ArgonType);
|
|
||||||
|
|
||||||
if (result == Argon2Error.OK || result == Argon2Error.VERIFY_MISMATCH || result == Argon2Error.DECODING_FAIL)
|
if (result == Argon2Error.OK || result == Argon2Error.VERIFY_MISMATCH || result == Argon2Error.DECODING_FAIL)
|
||||||
return result == Argon2Error.OK;
|
return result == Argon2Error.OK;
|
||||||
|
|
@ -244,26 +199,6 @@ namespace Server.Accounting.Security
|
||||||
throw new Argon2Exception("verifying", result);
|
throw new Argon2Exception("verifying", result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Hashes the password and verifies that the password results in the specified hash. (See Verify method)
|
|
||||||
/// If the password verification is successful, this method checks to see if the memory cost, time cost, and parallelism
|
|
||||||
/// match the parameters the PasswordHasher object was constructed with. If they do not much, then the password is rehashed
|
|
||||||
/// using the new parameters and the result is outputted via the newFormattedHash parameter.
|
|
||||||
/// <param name="expectedHash">Hashing the password should result in this hash</param>
|
|
||||||
/// <param name="password">The password to hash and compare its result to expectedHash. The password is first decoded into bytes using StringEncoding (default: Encoding.UTF8)</param>
|
|
||||||
/// <param name="isUpdated">Whether the cost parameters of expectedHash differ from the PasswordHasher object and if the password was rehashed using th new parameters. This is always false if the password was incorrect.</param>
|
|
||||||
/// <param name="newFormattedHash">If isUpdated is true, then newFormattedHash is the password hashed with the new cost parameters. If isUpdated is false, then newFormattedHash is expectedHash.</param>
|
|
||||||
/// <returns>Whether the password results in the expectedHash when hashed</returns>
|
|
||||||
/// </summary>
|
|
||||||
public bool VerifyAndUpdate(string expectedHash, string password, out bool isUpdated, out string newFormattedHash)
|
|
||||||
{
|
|
||||||
CheckNull("VerifyAndUpdate", "expectedHash", expectedHash, "password", password);
|
|
||||||
|
|
||||||
return VerifyAndUpdate(expectedHash, StringEncoding.GetBytes(password), out isUpdated, out newFormattedHash);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Hashes the password and verifies that the password results in the specified hash. (See Verify method)
|
/// Hashes the password and verifies that the password results in the specified hash. (See Verify method)
|
||||||
/// If the password verification is successful, this method checks to see if the memory cost, time cost, and parallelism
|
/// If the password verification is successful, this method checks to see if the memory cost, time cost, and parallelism
|
||||||
|
|
@ -275,11 +210,11 @@ namespace Server.Accounting.Security
|
||||||
/// <param name="newFormattedHash">If isUpdated is true, then newFormattedHash is the password hashed with the new cost parameters. If isUpdated is false, then newFormattedHash is expectedHash.</param>
|
/// <param name="newFormattedHash">If isUpdated is true, then newFormattedHash is the password hashed with the new cost parameters. If isUpdated is false, then newFormattedHash is expectedHash.</param>
|
||||||
/// <returns>Whether the password results in the expectedHash when hashed</returns>
|
/// <returns>Whether the password results in the expectedHash when hashed</returns>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool VerifyAndUpdate(string expectedHash, byte[] password, out bool isUpdated, out string newFormattedHash)
|
public bool VerifyAndUpdate(ReadOnlySpan<char> expectedHash, ReadOnlySpan<char> password, out bool isUpdated, out string newFormattedHash)
|
||||||
{
|
{
|
||||||
CheckNull("VerifyAndUpdate", "expectedHash", expectedHash, "password", password);
|
bool verified = Verify(expectedHash, password);
|
||||||
|
|
||||||
if (Verify(expectedHash, password))
|
if (verified)
|
||||||
{
|
{
|
||||||
var hashMetadata = ExtractMetadata(expectedHash);
|
var hashMetadata = ExtractMetadata(expectedHash);
|
||||||
|
|
||||||
|
|
@ -288,44 +223,22 @@ namespace Server.Accounting.Security
|
||||||
isUpdated = true;
|
isUpdated = true;
|
||||||
byte[] salt = hashMetadata.Salt;
|
byte[] salt = hashMetadata.Salt;
|
||||||
newFormattedHash = Hash(password, salt);
|
newFormattedHash = Hash(password, salt);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
isUpdated = false;
|
|
||||||
newFormattedHash = expectedHash;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
isUpdated = false;
|
isUpdated = false;
|
||||||
newFormattedHash = expectedHash;
|
newFormattedHash = expectedHash.ToString();
|
||||||
return false;
|
return verified;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Generate salt using a Cryptographically-Secure Pseudo-Random Number Generator
|
|
||||||
/// <param name="byteLength">The number of bytes of salt to generate (default: 16)</param>
|
|
||||||
/// <returns>A array of randomly-generated bytes</returns>
|
|
||||||
/// </summary>
|
|
||||||
public static byte[] GenerateSalt(uint byteLength = 16)
|
|
||||||
{
|
|
||||||
var salt = new byte[byteLength];
|
|
||||||
Rng.GetBytes(salt);
|
|
||||||
return salt;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Extracts the memory cost, time cost, etc. used to generate the Argon2 hash.
|
/// Extracts the memory cost, time cost, etc. used to generate the Argon2 hash.
|
||||||
/// <param name="formattedHash">An encoded Argon2 hash created by the Hash method</param>
|
/// <param name="formattedHash">An encoded Argon2 hash created by the Hash method</param>
|
||||||
/// <returns>The hash metadata or null if the formattedHash was not a valid encoded Argon2 hash</returns>
|
/// <returns>The hash metadata or null if the formattedHash was not a valid encoded Argon2 hash</returns>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static HashMetadata ExtractMetadata(string formattedHash)
|
public static HashMetadata ExtractMetadata(ReadOnlySpan<char> formattedHash)
|
||||||
{
|
{
|
||||||
CheckNull("ExtractMetadata", "formattedHash", formattedHash);
|
|
||||||
|
|
||||||
var context = new Argon2Context
|
var context = new Argon2Context
|
||||||
{
|
{
|
||||||
Out = Marshal.AllocHGlobal(formattedHash.Length), // ensure the space to hold the hash is long enough
|
Out = Marshal.AllocHGlobal(formattedHash.Length), // ensure the space to hold the hash is long enough
|
||||||
|
|
@ -347,7 +260,12 @@ namespace Server.Accounting.Security
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var type = formattedHash.StartsWith("$argon2i") ? Argon2Type.Argon2i : Argon2Type.Argon2d;
|
var type = formattedHash.StartsWith("$argon2i") ? Argon2Type.Argon2i : Argon2Type.Argon2d;
|
||||||
var result = Argon2.Library.Decode(context, $"{formattedHash}\0", (int)type);
|
formattedHash = $"{formattedHash.ToString()}\0";
|
||||||
|
|
||||||
|
Span<byte> bytes = stackalloc byte[formattedHash.Length];
|
||||||
|
Encoding.ASCII.GetBytes(formattedHash, bytes);
|
||||||
|
|
||||||
|
var result = Argon2.Library.Decode(context, bytes, (int)type);
|
||||||
|
|
||||||
if (result != Argon2Error.OK)
|
if (result != Argon2Error.OK)
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -376,13 +294,5 @@ namespace Server.Accounting.Security
|
||||||
Marshal.FreeHGlobal(context.AssocData);
|
Marshal.FreeHGlobal(context.AssocData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static void CheckNull(string methodName, params object[] arguments)
|
|
||||||
{
|
|
||||||
for (var i = 0; i < arguments.Length; i += 2)
|
|
||||||
if (arguments[i + 1] == null)
|
|
||||||
throw new ArgumentNullException(arguments[i].ToString(), string.Format("Argument {0} to method PasswordHasher.{1} is null", arguments[i], methodName));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
namespace Server.Accounting.Security
|
namespace System.Security.Cryptography
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The type of Argon2 hashing algorithm to use.
|
/// The type of Argon2 hashing algorithm to use.
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Server.Accounting.Security
|
namespace System.Security.Cryptography
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// HashMetadata represents the information stored in the encoded Argon2 format
|
/// HashMetadata represents the information stored in the encoded Argon2 format
|
||||||
|
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace Server
|
|
||||||
{
|
|
||||||
internal static class RuntimeUtility
|
|
||||||
{
|
|
||||||
internal static readonly bool IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
|
|
||||||
internal static readonly bool IsDarwin = RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
|
|
||||||
internal static readonly bool IsFreeBSD = RuntimeInformation.IsOSPlatform(OSPlatform.FreeBSD);
|
|
||||||
internal static readonly bool IsLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || IsFreeBSD;
|
|
||||||
internal static readonly bool IsUnix = IsDarwin || IsFreeBSD || IsLinux;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -79,7 +79,7 @@
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv" Version="3.1.3" />
|
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv" Version="3.1.3" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="3.1.3" />
|
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="3.1.3" />
|
||||||
<PackageReference Include="System.IO.Pipelines" Version="4.7.1" />
|
<PackageReference Include="System.IO.Pipelines" Version="4.7.1" />
|
||||||
<PackageReference Include="Zlib.Bindings" Version="1.0.0" />
|
<PackageReference Include="ZLib.Bindings" Version="1.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Condition="'$(Configuration)'=='Analyze'">
|
<ItemGroup Condition="'$(Configuration)'=='Analyze'">
|
||||||
<PackageReference Include="StyleCop.Analyzers">
|
<PackageReference Include="StyleCop.Analyzers">
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,8 @@
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||||
*************************************************************************/
|
*************************************************************************/
|
||||||
|
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
|
||||||
namespace Server.Accounting.Security
|
namespace Server.Accounting.Security
|
||||||
{
|
{
|
||||||
public class Argon2PasswordProtection : IPasswordProtection
|
public class Argon2PasswordProtection : IPasswordProtection
|
||||||
|
|
|
||||||
|
|
@ -80,7 +80,7 @@
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Connections.Abstractions" Version="3.1.3" />
|
<PackageReference Include="Microsoft.AspNetCore.Connections.Abstractions" Version="3.1.3" />
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="3.1.3" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="3.1.3" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="3.1.3" />
|
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="3.1.3" />
|
||||||
<PackageReference Include="Argon2.Bindings" Version="1.1.0" />
|
<PackageReference Include="Argon2.Bindings" Version="1.1.8" />
|
||||||
<PackageReference Include="Zlib.Bindings" Version="1.0.0" />
|
<PackageReference Include="Zlib.Bindings" Version="1.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Condition="'$(Configuration)'=='Analyze'">
|
<ItemGroup Condition="'$(Configuration)'=='Analyze'">
|
||||||
|
|
|
||||||
|
|
@ -7,13 +7,13 @@ if [[ -z $2 ]]
|
||||||
then
|
then
|
||||||
c="-c Release"
|
c="-c Release"
|
||||||
else
|
else
|
||||||
c=-c $2
|
c="-c $2"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
dotnet build ${c} Projects/Argon2/Argon2.csproj
|
dotnet build ${c} Projects/Argon2/Argon2.csproj
|
||||||
dotnet pack -o packages Projects/Argon2/Argon2.csproj
|
dotnet pack ${c} -o packages Projects/Argon2/Argon2.csproj
|
||||||
dotnet build ${c} Projects/ZLib/ZLib.csproj
|
dotnet build ${c} Projects/ZLib/ZLib.csproj
|
||||||
dotnet pack -o packages Projects/ZLib/ZLib.csproj
|
dotnet pack ${c} -o packages Projects/ZLib/ZLib.csproj
|
||||||
exit $?
|
exit $?
|
||||||
|
|
||||||
:CMDSCRIPT
|
:CMDSCRIPT
|
||||||
|
|
@ -24,6 +24,6 @@ IF "%~1" == "" (
|
||||||
)
|
)
|
||||||
|
|
||||||
dotnet build %c% Projects\Argon2\Argon2.csproj
|
dotnet build %c% Projects\Argon2\Argon2.csproj
|
||||||
dotnet pack -o packages Projects\Argon2\Argon2.csproj
|
dotnet pack %c% -o packages Projects\Argon2\Argon2.csproj
|
||||||
dotnet build %c% Projects\ZLib\ZLib.csproj
|
dotnet build %c% Projects\ZLib\ZLib.csproj
|
||||||
dotnet pack -o packages Projects\ZLib\ZLib.csproj
|
dotnet pack %c% -o packages Projects\ZLib\ZLib.csproj
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ if [[ -z $2 ]]
|
||||||
then
|
then
|
||||||
c="-c Release"
|
c="-c Release"
|
||||||
else
|
else
|
||||||
c=-c $2
|
c="-c $2"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
Tools/build-native-libraries.cmd $2
|
Tools/build-native-libraries.cmd $2
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue