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 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 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 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 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 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 plainBytes = cipherSize <= 1024 ? stackalloc byte[cipherSize] : new byte[cipherSize]; key.Decrypt(nonce, cipherBytes, tag, plainBytes); // Convert plain bytes back into string return plainBytes.ToArray(); } } }