Adds Argon2 for password protection (#123)

This commit is contained in:
Kamron Batman 2020-05-01 22:42:39 -07:00 committed by GitHub
parent 29467a3ed9
commit dad8a64fad
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
54 changed files with 1152 additions and 167 deletions

View file

@ -0,0 +1,45 @@
using System;
namespace Server.Accounting.Security
{
public enum PasswordProtectionAlgorithm
{
// Obsolete algorithms. These are not secure!
// They are included for password upgrades only.
None,
MD5,
SHA1,
// Support algorithms
PBKDF2,
Argon2 // Recommended algorithm for real security.
}
public static class AccountSecurity
{
// TODO: Put it in a configuration
public const PasswordProtectionAlgorithm AlgorithmName = PasswordProtectionAlgorithm.Argon2;
public static readonly IPasswordProtection CurrentPasswordProtection = GetPasswordProtection(AlgorithmName);
public static void Configure()
{
if (AlgorithmName < PasswordProtectionAlgorithm.PBKDF2)
throw new Exception($"Security: {AlgorithmName} is obselete and not secure. Do not use it.");
}
public static IPasswordProtection GetPasswordProtection(PasswordProtectionAlgorithm algorithm)
{
var passwordProtection = algorithm switch
{
PasswordProtectionAlgorithm.MD5 => MD5PasswordProtection.Instance,
PasswordProtectionAlgorithm.SHA1 => SHA1PasswordProtection.Instance,
PasswordProtectionAlgorithm.PBKDF2 => PBKDF2PasswordProtection.Instance,
PasswordProtectionAlgorithm.Argon2 => Argon2PasswordProtection.Instance,
_ => null
};
return passwordProtection;
}
}
}

View file

@ -0,0 +1,14 @@
namespace Server.Accounting.Security
{
public class Argon2PasswordProtection : IPasswordProtection
{
public static IPasswordProtection Instance = new Argon2PasswordProtection();
private Argon2PasswordHasher m_PasswordHasher = new Argon2PasswordHasher();
public string EncryptPassword(string plainPassword) =>
m_PasswordHasher.Hash(plainPassword);
public bool ValidatePassword(string encryptedPassword, string plainPassword) =>
m_PasswordHasher.Verify(encryptedPassword, plainPassword);
}
}

View file

@ -0,0 +1,25 @@
using System;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
namespace Server.Accounting.Security
{
public class MD5PasswordProtection : IPasswordProtection
{
public static IPasswordProtection Instance = new MD5PasswordProtection();
private MD5CryptoServiceProvider m_MD5HashProvider = new MD5CryptoServiceProvider();
public string EncryptPassword(string plainPassword)
{
ReadOnlySpan<char> password = plainPassword.AsSpan(0, Math.Min(256, plainPassword.Length));
byte[] bytes = new byte[Encoding.ASCII.GetByteCount(password)];
Encoding.ASCII.GetBytes(password, bytes);
return BitConverter.ToString(m_MD5HashProvider.ComputeHash(bytes));
}
public bool ValidatePassword(string encryptedPassword, string plainPassword) =>
EncryptPassword(plainPassword) == encryptedPassword;
}
}

View file

@ -0,0 +1,46 @@
using System;
using System.Buffers.Binary;
using System.Security.Cryptography;
using Server.Misc;
namespace Server.Accounting.Security
{
public class PBKDF2PasswordProtection : IPasswordProtection
{
public static IPasswordProtection Instance = new PBKDF2PasswordProtection();
private const ushort m_MinIterations = 1024;
private const ushort m_MaxIterations = 1536;
private static readonly HashAlgorithmName m_Algorithm = HashAlgorithmName.SHA256;
private const int m_SaltSize = 8;
private const int m_HashSize = 32;
private const int m_OutputSize = 2 + m_SaltSize + m_HashSize;
public string EncryptPassword(string plainPassword)
{
Span<byte> output = stackalloc byte[m_OutputSize];
int iterations = Utility.RandomMinMax(m_MinIterations, m_MaxIterations);
BinaryPrimitives.WriteUInt16LittleEndian(output.Slice(0, 2), (ushort)iterations);
var rfc2898 = new Rfc2898DeriveBytes(plainPassword, m_SaltSize, iterations, m_Algorithm);
rfc2898.Salt.CopyTo(output.Slice(2, m_SaltSize));
rfc2898.GetBytes(m_HashSize).CopyTo(output.Slice(m_SaltSize + 2));
return HexStringConverter.GetString(output);
}
public bool ValidatePassword(string encryptedPassword, string plainPassword)
{
Span<byte> encryptedBytes = stackalloc byte[m_OutputSize];
HexStringConverter.GetBytes(encryptedPassword, encryptedBytes);
ushort iterations = BinaryPrimitives.ReadUInt16LittleEndian(encryptedBytes.Slice(0, 2));
Span<byte> salt = encryptedBytes.Slice(2, m_SaltSize);
ReadOnlySpan<byte> hash =
new Rfc2898DeriveBytes(plainPassword, salt.ToArray(), iterations, m_Algorithm).GetBytes(m_HashSize);
return hash.SequenceEqual(encryptedBytes.Slice(m_SaltSize + 2));
}
}
}

View file

@ -0,0 +1,25 @@
using System;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
namespace Server.Accounting.Security
{
public class SHA1PasswordProtection : IPasswordProtection
{
public static IPasswordProtection Instance = new SHA1PasswordProtection();
private SHA1CryptoServiceProvider m_SHA1HashProvider = new SHA1CryptoServiceProvider();
public string EncryptPassword(string plainPassword)
{
ReadOnlySpan<char> password = plainPassword.AsSpan(0, Math.Min(256, plainPassword.Length));
byte[] bytes = new byte[Encoding.ASCII.GetByteCount(password)];
Encoding.ASCII.GetBytes(password, bytes);
return BitConverter.ToString(m_SHA1HashProvider.ComputeHash(bytes));
}
public bool ValidatePassword(string encryptedPassword, string plainPassword) =>
EncryptPassword(plainPassword) == encryptedPassword;
}
}