136 lines
5.0 KiB
C#
136 lines
5.0 KiB
C#
using Isopoh.Cryptography.Argon2;
|
|
using Isopoh.Cryptography.SecureArray;
|
|
using System;
|
|
using System.Buffers.Binary;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Security.Cryptography;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace Akari.Prototype.Server.Utils
|
|
{
|
|
public static class Security
|
|
{
|
|
public const int DefaultHashLength = 32;
|
|
public const int DefaultSaltLength = 12;
|
|
|
|
public static string NewArgon2idHash(byte[] password, int hashLength = DefaultHashLength, int saltLength = DefaultSaltLength, bool clear = false, int? threads = null)
|
|
{
|
|
int t = threads ?? Environment.ProcessorCount / 2;
|
|
|
|
if (t < 1)
|
|
{
|
|
t = 1;
|
|
}
|
|
|
|
var salt = new byte[saltLength];
|
|
|
|
RandomNumberGenerator.Fill(salt);
|
|
|
|
var config = new Argon2Config()
|
|
{
|
|
HashLength = hashLength,
|
|
Password = password,
|
|
Salt = salt,
|
|
Lanes = t,
|
|
Threads = t,
|
|
ClearPassword = clear,
|
|
ClearSecret = clear,
|
|
Type = Argon2Type.HybridAddressing,
|
|
Version = Argon2Version.Nineteen
|
|
};
|
|
|
|
return Argon2.Hash(config);
|
|
}
|
|
public static string NewArgon2idHash(string password, int hashLength = DefaultHashLength, int saltLength = DefaultSaltLength, bool clear = false, int? threads = null)
|
|
{
|
|
return NewArgon2idHash(Encoding.UTF8.GetBytes(password), hashLength, saltLength, clear, threads);
|
|
}
|
|
|
|
public static SecureArray<byte> Argon2idDeriveBytes(byte[] password, byte[] salt, int length, bool clear = false, int? threads = null)
|
|
{
|
|
int t = threads ?? Environment.ProcessorCount / 2;
|
|
|
|
if (t < 1)
|
|
{
|
|
t = 1;
|
|
}
|
|
|
|
var config = new Argon2Config()
|
|
{
|
|
HashLength = length,
|
|
Password = password,
|
|
Salt = salt,
|
|
Lanes = t,
|
|
Threads = t,
|
|
ClearPassword = clear,
|
|
ClearSecret = clear,
|
|
Type = Argon2Type.HybridAddressing,
|
|
Version = Argon2Version.Nineteen
|
|
};
|
|
|
|
return new Argon2(config).Hash();
|
|
}
|
|
public static SecureArray<byte> Argon2idDeriveBytes(string password, string salt, int length, bool clear = false, int? threads = null)
|
|
{
|
|
return Argon2idDeriveBytes(Encoding.UTF8.GetBytes(password), Encoding.UTF8.GetBytes(salt), length, clear, threads);
|
|
}
|
|
|
|
// Source: https://stackoverflow.com/a/60891115/7465768
|
|
public static byte[] AesGcmEncrypt(AesGcm key, Span<byte> plain)
|
|
{
|
|
// Get parameter sizes
|
|
int nonceSize = AesGcm.NonceByteSizes.MaxSize;
|
|
int tagSize = AesGcm.TagByteSizes.MaxSize;
|
|
int cipherSize = plain.Length;
|
|
|
|
// We write everything into one big array for easier encoding
|
|
int encryptedDataLength = 4 + nonceSize + 4 + tagSize + cipherSize;
|
|
Span<byte> encryptedData = encryptedDataLength <= 1024
|
|
? stackalloc byte[encryptedDataLength]
|
|
: new byte[encryptedDataLength];
|
|
|
|
// Copy parameters
|
|
BinaryPrimitives.WriteInt32LittleEndian(encryptedData.Slice(0, 4), nonceSize);
|
|
BinaryPrimitives.WriteInt32LittleEndian(encryptedData.Slice(4 + nonceSize, 4), tagSize);
|
|
|
|
var nonce = encryptedData.Slice(4, nonceSize);
|
|
var tag = encryptedData.Slice(4 + nonceSize + 4, tagSize);
|
|
var cipherBytes = encryptedData.Slice(4 + nonceSize + 4 + tagSize, cipherSize);
|
|
|
|
// Generate secure nonce
|
|
RandomNumberGenerator.Fill(nonce);
|
|
|
|
// Encrypt
|
|
key.Encrypt(nonce, plain, cipherBytes, tag);
|
|
|
|
// Encode for transmission
|
|
return encryptedData.ToArray();
|
|
}
|
|
|
|
public static byte[] AesGcmDecrypt(AesGcm key, Span<byte> encryptedData)
|
|
{
|
|
// Extract parameter sizes
|
|
int nonceSize = BinaryPrimitives.ReadInt32LittleEndian(encryptedData.Slice(0, 4));
|
|
int tagSize = BinaryPrimitives.ReadInt32LittleEndian(encryptedData.Slice(4 + nonceSize, 4));
|
|
int cipherSize = encryptedData.Length - 4 - nonceSize - 4 - tagSize;
|
|
|
|
// Extract parameters
|
|
var nonce = encryptedData.Slice(4, nonceSize);
|
|
var tag = encryptedData.Slice(4 + nonceSize + 4, tagSize);
|
|
var cipherBytes = encryptedData.Slice(4 + nonceSize + 4 + tagSize, cipherSize);
|
|
|
|
// Decrypt
|
|
Span<byte> plainBytes = cipherSize <= 1024
|
|
? stackalloc byte[cipherSize]
|
|
: new byte[cipherSize];
|
|
|
|
key.Decrypt(nonce, cipherBytes, tag, plainBytes);
|
|
|
|
// Convert plain bytes back into string
|
|
return plainBytes.ToArray();
|
|
}
|
|
}
|
|
}
|