Would anyone care to elaborate my approach to a multi-layer encryption scheme. It encrypts with four layers and gets shuffled with a random key as well. This is used for a password vault application.
/// <summary>
/// Encrypts the contents of a file using Argon2 key derivation and four layers of encryption.
/// </summary>
/// <param name="userName">The username associated with the user's salt for key derivation.</param>
/// <param name="passWord">The user's password used for key derivation.</param>
/// <param name="plainText">The plaintext to encrypt.</param>
/// <returns>
/// A Task that completes with the encrypted content of the specified file.
/// If any error occurs during the process, returns an empty byte array.
/// </returns>
/// <remarks>
/// This method performs the following steps:
/// 1. Validates input parameters to ensure they are not null or empty.
/// 2. Retrieves the user-specific salt for key derivation.
/// 3. Derives an encryption key from the user's password and the obtained salt using Argon2id.
/// 4. Extracts key components for encryption, including two keys and an HMAC key.
/// 5. Reads and encodes the content of the specified file.
/// 6. Encrypts the file content using XChaCha20-Poly1305 encryption.
/// 7. Clears sensitive information, such as the user's password, from memory.
/// </remarks>
public static async Task<byte[]> EncryptFile(string userName, byte[] passWord, string plainText)
{
if (string.IsNullOrEmpty(userName))
throw new ArgumentException("Value was empty.", nameof(userName));
if (passWord.Length == 0)
throw new ArgumentException("Value was empty.", nameof(passWord));
if (string.IsNullOrEmpty(plainText))
throw new ArgumentException("Value was empty.", nameof(plainText));
var salt = Authentication.GetUserSalt(userName);
var bytes = await HashingMethods.Argon2Id(passWord, salt, 544);
if (bytes == Array.Empty<byte>())
throw new Exception("Value was empty.");
var fileBytes = DataConversionHelpers.StringToByteArray(await File.ReadAllTextAsync(plainText));
if (fileBytes == null || fileBytes.Length == 0 || salt == null || salt.Length == 0)
throw new ArgumentException("Value was empty.");
var (key, key2, key3, key4, key5, hMacKey, hMacKey2, hMacKey3) = BufferInit.InitBuffers(bytes);
var compressedText = await CryptoUtilities.CompressText(fileBytes);
var encryptedFile = EncryptionDecryption.EncryptV3(ref compressedText, ref key, ref key2, ref key3, ref key4,
ref key5, ref hMacKey,
ref hMacKey2, ref hMacKey3);
var arrays = new[]
{
key, key2, key3, key4, key5, hMacKey, hMacKey2, hMacKey3, bytes
};
CryptoUtilities.ClearMemory(arrays);
return encryptedFile;
}
/// <summary>
/// Decrypts the contents of an encrypted file using Argon2 key derivation and four layers of decryption.
/// </summary>
/// <param name="userName">The username associated with the user's salt for key derivation.</param>
/// <param name="passWord">The user's password used for key derivation.</param>
/// <param name="cipherText">The ciphertext to decrypt.</param>
/// <returns>
/// A Task that completes with the decrypted content of the specified encrypted file.
/// If any error occurs during the process, returns an empty byte array.
/// </returns>
/// <remarks>
/// This method performs the following steps:
/// 1. Validates input parameters to ensure they are not null or empty.
/// 2. Retrieves the user-specific salts for key derivation.
/// 3. Derives an encryption key from the user's password and the obtained salt using Argon2id.
/// 4. Extracts key components for decryption, including two keys and an HMAC key.
/// 5. Reads and decodes the content of the encrypted file.
/// 6. Decrypts the file content using ChaCha20-Poly1305 decryption.
/// 7. Clears sensitive information, such as the user's password, from memory.
/// </remarks>
public static async Task<byte[]> DecryptFile(string userName, byte[] passWord, string cipherText)
{
if (string.IsNullOrEmpty(userName))
throw new ArgumentException("Value was empty.", nameof(userName));
if (passWord.Length == 0)
throw new ArgumentException("Value was empty.", nameof(passWord));
if (string.IsNullOrEmpty(cipherText))
throw new ArgumentException("Value was empty.", nameof(cipherText));
var salt = Authentication.GetUserSalt(userName);
var bytes = await HashingMethods.Argon2Id(passWord, salt, 544);
var (key, key2, key3, key4, key5, hMacKey, hMacKey2, hMacKey3) = BufferInit.InitBuffers(bytes);
var fileStr = await File.ReadAllTextAsync(cipherText);
var fileBytes = DataConversionHelpers.Base64StringToByteArray(fileStr);
if (fileBytes == Array.Empty<byte>() || salt == Array.Empty<byte>())
throw new ArgumentException("Value was empty.");
var decryptedFile =
EncryptionDecryption.DecryptV3(ref fileBytes, ref key, ref key2, ref key3, ref key4, ref key5, ref hMacKey,
ref hMacKey2, ref hMacKey3);
var decompressedText = await CryptoUtilities.DecompressText(decryptedFile);
var arrays = new[]
{
key, key2, key3, key4, key5, hMacKey, hMacKey2, hMacKey3, bytes
};
CryptoUtilities.ClearMemory(arrays);
return decompressedText;
}
private static partial class Memset
{
[LibraryImport("msvcrt.dll", EntryPoint = "memset", SetLastError = false)]
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
public static partial void MemSet(IntPtr dest, int c, int byteCount);
}
/// <summary>
/// Utility class for cryptographic settings and initialization.
/// </summary>
public static class CryptoConstants
{
public const int SaltSize = 128;
public const int HmacLength = 64;
public const int ChaChaNonceSize = 24;
public const int KeySize = 32;
public const int Iv = 16;
public const int ThreeFish = 128;
public const int ShuffleKey = 128;
public const int BlockBitSize = 128;
public const int KeyBitSize = 256;
public static int Iterations = Settings.Default.Iterations;
public static double MemorySize = Settings.Default.MemorySize * Math.Pow(1024, 2);
public static int Parallelism = Settings.Default.Parallelism;
public static readonly RandomNumberGenerator RndNum = RandomNumberGenerator.Create();
public static byte[] SecurePasswordSalt { get; set; } = [];
#pragma warning disable CA2211
public static byte[] Hash = [];
public static byte[] SecurePassword = [];
#pragma warning restore CA2211
}
public static class HashingMethods
{
/// <summary>
/// Hashes a password inside a char array or derives a key from a password.
/// </summary>
/// <param name="passWord">The char array to hash.</param>
/// <param name="salt">The salt used during the argon2id hashing process.</param>
/// <param name="outputSize">The output size in bytes.</param>
/// <returns>Either a derived key or password hash byte array.</returns>
public static async Task<byte[]> Argon2Id(byte[] passWord, byte[] salt, int outputSize)
{
if (passWord == null || passWord.Length == 0)
throw new ArgumentException("Password cannot be null or empty.", nameof(passWord));
if (salt == null || salt.Length == 0)
throw new ArgumentException("Salt cannot be null or empty.", nameof(salt));
using var argon2 = new Argon2id(passWord);
argon2.Salt = salt;
argon2.DegreeOfParallelism = CryptoConstants.Parallelism;
argon2.Iterations = CryptoConstants.Iterations;
argon2.MemorySize = (int)CryptoConstants.MemorySize;
var result = await argon2.GetBytesAsync(outputSize);
return result;
}
/// <summary>
/// Computes the Hash-based Message Authentication Code (HMAC) using the SHA3-512 algorithm.
/// </summary>
/// <param name="input">The byte array to be authenticated.</param>
/// <param name="key">The key used for HMAC computation.</param>
/// <returns>The HMAC-SHA3-512 authentication code as a byte array.</returns>
public static byte[] HmacSha3(byte[] input, byte[] key)
{
var digest = new Sha3Digest(512);
var hMac = new HMac(digest);
hMac.Init(new KeyParameter(key));
hMac.BlockUpdate(input, 0, input.Length);
var result = new byte[hMac.GetMacSize()];
hMac.DoFinal(result, 0);
return result;
}
/// <summary>
/// Computes the SHA3-512 hash for the given input byte array.
/// </summary>
/// <param name="input">The input byte array for which the hash needs to be computed.</param>
/// <returns>A byte array representing the SHA3-512 hash of the input.</returns>
public static byte[] Sha3Hash(byte[] input)
{
var digest = new Sha3Digest(512);
digest.BlockUpdate(input, 0, input.Length);
var result = new byte[digest.GetDigestSize()];
digest.DoFinal(result, 0);
return result;
}
}
public static class CryptoUtilities
{
/// <summary>
/// Generates a random integer using a cryptographically secure random number generator.
/// </summary>
/// <returns>A random integer value.</returns>
/// <remarks>
/// This method generates a random integer by obtaining random bytes from a cryptographically secure
/// random number generator and converting them to an integer using the BitConverter class.
/// </remarks>
private static int RndInt()
{
var buffer = new byte[sizeof(int)];
CryptoConstants.RndNum.GetBytes(buffer);
var result = BitConverter.ToInt32(buffer, 0);
return result;
}
/// <summary>
/// Generates a random integer within the specified inclusive range.
/// </summary>
/// <param name="min">The minimum value of the range (inclusive).</param>
/// <param name="max">The maximum value of the range (inclusive).</param>
/// <returns>A random integer within the specified range.</returns>
/// <exception cref="ArgumentException">
/// Thrown if the specified minimum value is greater than or equal to the maximum
/// value.
/// </exception>
/// <remarks>
/// This method generates a random integer within the specified inclusive range using the RndInt method.
/// The generated integer is constrained to the provided range by applying modular arithmetic.
/// </remarks>
public static int BoundedInt(int min, int max)
{
if (min >= max)
throw new ArgumentException("Min must be less than max.");
var value = RndInt();
var range = max - min;
var result = min + Math.Abs(value) % range;
return result;
}
/// <summary>
/// Generates a random byte array of the specified size using a cryptographically secure random number generator.
/// </summary>
/// <param name="size">The size of the byte array to generate.</param>
/// <returns>A random byte array of the specified size.</returns>
/// <remarks>
/// This method generates a random byte array by obtaining random bytes from a cryptographically secure
/// random number generator. The size of the byte array is determined by the input parameter 'size'.
/// </remarks>
public static byte[] RndByteSized(int size)
{
var buffer = new byte[size];
CryptoConstants.RndNum.GetBytes(buffer);
return buffer;
}
/// <summary>
/// Compares two password hashes in a secure manner using fixed-time comparison.
/// </summary>
/// <param name="hash1">The first password hash.</param>
/// <param name="hash2">The second password hash.</param>
/// <returns>
/// A bool that completes with 'true' if the hashes are equal, and 'false' otherwise.
/// </returns>
/// <exception cref="ArgumentException">
/// Thrown if either hash is null or empty.
/// </exception>
/// <remarks>
/// This method uses fixed-time comparison to mitigate certain types of timing attacks.
/// </remarks>
public static bool ComparePassword(byte[] hash1, byte[] hash2)
{
if (hash1.Length == 0 || hash1 == null)
throw new ArgumentException("Value was empty or null.", nameof(hash1));
if (hash2.Length == 0 || hash2 == null)
throw new ArgumentException("Value was empty or null.", nameof(hash2));
return CryptographicOperations.FixedTimeEquals(hash1, hash2);
}
/// <summary>
/// Clears the sensitive information stored in one or more byte arrays using memset.
/// </summary>
/// <remarks>
/// This method uses a pinned array and the SecureMemoryClear function to overwrite the memory
/// containing sensitive information, enhancing security by preventing the information from being
/// easily accessible in memory.
/// </remarks>
/// <param name="arrays">The byte arrays containing sensitive information to be cleared.</param>
/// <exception cref="ArgumentNullException">Thrown if any of the input arrays is null.</exception>
public static void ClearMemory(byte[][] arrays)
{
if (arrays == null)
throw new ArgumentNullException(nameof(arrays), "Input cannot be null.");
foreach (var byteArray in arrays)
{
var handle = GCHandle.Alloc(byteArray, GCHandleType.Pinned);
try
{
Memset.MemSet(handle.AddrOfPinnedObject(), 0, byteArray.Length);
}
catch (AccessViolationException ex)
{
ErrorLogging.ErrorLog(ex);
MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
finally
{
handle.Free();
GC.Collect(GC.MaxGeneration, GCCollectionMode.Aggressive, true);
GC.WaitForPendingFinalizers();
}
}
}
public static void ClearMemory(byte[] array)
{
var handle = GCHandle.Alloc(array, GCHandleType.Pinned);
try
{
Memset.MemSet(handle.AddrOfPinnedObject(), 0, array.Length * sizeof(byte));
}
catch (AccessViolationException ex)
{
ErrorLogging.ErrorLog(ex);
MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
finally
{
handle.Free();
}
}
/// <summary>
/// Clears the sensitive information stored in one or more char arrays securely.
/// </summary>
/// <remarks>
/// This method uses a pinned array and the SecureMemoryClear function to overwrite the memory
/// containing sensitive information, enhancing security by preventing the information from being
/// easily accessible in memory.
/// </remarks>
/// <param name="arrays">The char arrays containing sensitive information to be cleared.</param>
/// <exception cref="ArgumentNullException">Thrown if any of the input arrays is null.</exception>
public static void ClearMemory(char[][] arrays)
{
if (arrays == null)
throw new ArgumentNullException(nameof(arrays), "Input strings cannot be null.");
foreach (var value in arrays)
{
var handle = GCHandle.Alloc(value, GCHandleType.Pinned);
try
{
Memset.MemSet(handle.AddrOfPinnedObject(), 0, value.Length * sizeof(char));
}
catch (AccessViolationException ex)
{
ErrorLogging.ErrorLog(ex);
MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
finally
{
handle.Free();
}
}
}
public static void ClearMemory(char[] array)
{
var handle = GCHandle.Alloc(array, GCHandleType.Pinned);
try
{
Memset.MemSet(handle.AddrOfPinnedObject(), 0, array.Length * sizeof(char));
}
catch (AccessViolationException ex)
{
ErrorLogging.ErrorLog(ex);
MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
finally
{
handle.Free();
}
}
public static void ClearMemory(char c)
{
var handle = GCHandle.Alloc(c, GCHandleType.Pinned);
try
{
Memset.MemSet(handle.AddrOfPinnedObject(), 0, 1);
}
catch (AccessViolationException ex)
{
ErrorLogging.ErrorLog(ex);
MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
finally
{
handle.Free();
}
}
/// <summary>
/// Clears the sensitive information stored in one or more strings securely.
/// </summary>
/// <remarks>
/// This method uses a pinned string and the SecureMemoryClear function to overwrite the memory
/// containing sensitive information, enhancing security by preventing the information from being
/// easily accessible in memory.
/// </remarks>
/// <param name="str">The strings containing sensitive information to be cleared.</param>
/// <exception cref="ArgumentNullException">Thrown if any of the input strings is null.</exception>
public static void ClearMemory(params string[][] str)
{
if (str == null)
throw new ArgumentNullException(nameof(str), "Input strings cannot be null.");
foreach (var value in str)
{
var handle = GCHandle.Alloc(value, GCHandleType.Pinned);
try
{
Memset.MemSet(handle.AddrOfPinnedObject(), 0, value.Length * 2);
}
catch (AccessViolationException ex)
{
ErrorLogging.ErrorLog(ex);
MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
finally
{
handle.Free();
}
}
}
/// <summary>
/// Clears the sensitive information stored in one or more pointers securely.
/// </summary>
/// <remarks>
/// This method uses a pinned string and the SecureMemoryClear function to overwrite the memory
/// containing sensitive information, enhancing security by preventing the information from being
/// easily accessible in memory.
/// </remarks>
/// <exception cref="ArgumentNullException">Thrown if any of the input strings is null.</exception>
public static void ClearMemory(int size, ref IntPtr ptr)
{
if (ptr == IntPtr.Zero)
throw new ArgumentNullException(nameof(ptr), "Invalid ptr.");
var handle = GCHandle.Alloc(ptr, GCHandleType.Pinned);
try
{
Memset.MemSet(handle.AddrOfPinnedObject(), 0, size);
}
catch (AccessViolationException ex)
{
ErrorLogging.ErrorLog(ex);
MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
finally
{
handle.Free();
}
}
/// <summary>
/// Compresses a byte array using the GZip compression algorithm.
/// </summary>
/// <remarks>
/// This method takes a byte array as input, compresses it using the GZip compression algorithm,
/// and returns the compressed byte array. The compression level used is <see cref="CompressionLevel.SmallestSize" />.
/// </remarks>
/// <param name="inputText">The byte array representing the uncompressed text to be compressed.</param>
/// <returns>A compressed byte array using the GZip compression algorithm.</returns>
/// <exception cref="ArgumentNullException">Thrown if the input byte array is null.</exception>
public static async Task<byte[]> CompressText(byte[] inputText)
{
if (inputText == null)
throw new ArgumentNullException(nameof(inputText), "Input byte array cannot be null.");
using var outputStream = new MemoryStream();
await using (var zipStream = new GZipStream(outputStream, CompressionLevel.SmallestSize, true))
{
await zipStream.WriteAsync(inputText).ConfigureAwait(false);
}
return outputStream.ToArray();
}
/// <summary>
/// Decompresses a byte array that was compressed using the GZip compression algorithm.
/// </summary>
/// <remarks>
/// This method takes a compressed byte array as input, decompresses it using the GZip compression algorithm,
/// and returns the decompressed byte array.
/// </remarks>
/// <param name="inputText">The byte array representing the compressed text to be decompressed.</param>
/// <returns>A decompressed byte array using the GZip compression algorithm.</returns>
/// <exception cref="ArgumentNullException">Thrown if the input byte array is null.</exception>
public static async Task<byte[]> DecompressText(byte[] inputText)
{
if (inputText == null)
throw new ArgumentNullException(nameof(inputText), "Input byte array cannot be null.");
using var inputStream = new MemoryStream(inputText);
await using var zipStream = new GZipStream(inputStream, CompressionMode.Decompress);
using var outputStream = new MemoryStream();
await zipStream.CopyToAsync(outputStream);
return outputStream.ToArray();
}
}
/// <summary>
/// Utility class for buffer initialization that contains the necessary keys for cryptographic functions.
/// </summary>
private static class BufferInit
{
/// <summary>
/// Initializes multiple byte arrays from a source byte array for cryptographic operations.
/// </summary>
/// <remarks>
/// This method takes a source byte array and extracts key components for encryption and HMAC operations.
/// It initializes multiple byte arrays for different cryptographic purposes and returns them as a tuple.
/// </remarks>
/// <param name="src">The source byte array used for initializing cryptographic buffers.</param>
/// <returns>
/// A tuple containing byte arrays for encryption keys (key, key2, key3, key4, key5)
/// and HMAC keys (hMacKey, hMackey2, hMacKey3).
/// </returns>
/// <exception cref="ArgumentNullException">Thrown if the source byte array is null.</exception>
public static (byte[] key, byte[] key2, byte[] key3, byte[] key4, byte[] key5, byte[] hMacKey, byte[]
hMackey2, byte[] hMacKey3)
InitBuffers(byte[] src)
{
if (src == null)
throw new ArgumentNullException(nameof(src), "Source byte array cannot be null.");
var key = new byte[CryptoConstants.KeySize];
var key2 = new byte[CryptoConstants.ThreeFish];
var key3 = new byte[CryptoConstants.KeySize];
var key4 = new byte[CryptoConstants.KeySize];
var key5 = new byte[CryptoConstants.ShuffleKey];
var hMacKey = new byte[CryptoConstants.HmacLength];
var hMackey2 = new byte[CryptoConstants.HmacLength];
var hMacKey3 = new byte[CryptoConstants.HmacLength];
CopyBytes(src, key, key2, key3, key4, key5, hMacKey, hMackey2, hMacKey3);
return (key, key2, key3, key4, key5, hMacKey, hMackey2, hMacKey3);
}
/// <summary>
/// Copies bytes from a source byte array to multiple destination byte arrays.
/// </summary>
/// <remarks>
/// This method copies bytes from a source byte array to multiple destination byte arrays.
/// It uses Buffer.BlockCopy for efficient copying and advances the offset for each destination array.
/// </remarks>
/// <param name="src">The source byte array from which bytes are copied.</param>
/// <param name="dest">The destination byte arrays where bytes are copied to.</param>
/// <exception cref="ArgumentNullException">Thrown if the source byte array or any destination byte array is null.</exception>
/// <exception cref="ArgumentException">
/// Thrown if the total length of destination arrays exceeds the length of the source
/// array.
/// </exception>
private static void CopyBytes(byte[] src, params byte[][] dest)
{
if (src == null)
throw new ArgumentNullException(nameof(src), "Source byte array cannot be null.");
var currentIndex = 0;
foreach (var dst in dest)
{
if (dst == null)
throw new ArgumentNullException(nameof(dest), "Destination byte array cannot be null.");
if (src.Length < dst.Length)
throw new ArgumentException(
"Length of the destination array cannot exceed the length of the source array.");
Buffer.BlockCopy(src, currentIndex, dst, 0, dst.Length);
currentIndex += dst.Length;
}
}
}
/// <summary>
/// A class that contains different algorithms for encrypting and decrypting.
/// </summary>
private static class Algorithms
{
/// <summary>
/// Generates an array of random indices for shuffling based on a given size and key.
/// </summary>
/// <param name="size">The size of the array for which shuffle exchanges are generated.</param>
/// <param name="key">The key used for generating random indices.</param>
/// <returns>An array of random indices for shuffling.</returns>
/// <remarks>
/// The method uses a random number generator with the specified key to generate
/// unique indices for shuffling a byte array of the given size.
/// </remarks>
private static int[] GetShuffleExchanges(int size, byte[] key)
{
var exchanges = new int[size - 1];
var rand = new Random(BitConverter.ToInt32(key));
for (var i = size - 1; i > 0; i--) exchanges[size - 1 - i] = rand.Next(i + 1);
return exchanges;
}
/// <summary>
/// Shuffles a byte array based on a given key using a custom exchange algorithm.
/// </summary>
/// <param name="input">The byte array to be shuffled.</param>
/// <param name="key">The key used for shuffling.</param>
/// <returns>The shuffled byte array.</returns>
/// <remarks>
/// The shuffling is performed using a custom exchange algorithm based on the specified key.
/// </remarks>
public static byte[] Shuffle(byte[] input, byte[] key)
{
var size = input.Length;
var exchanges = GetShuffleExchanges(size, key);
for (var i = size - 1; i > 0; i--)
{
var n = exchanges[size - 1 - i];
(input[i], input[n]) = (input[n], input[i]);
}
return input;
}
/// <summary>
/// Shuffles a char array based on a given key using a custom exchange algorithm.
/// </summary>
/// <param name="input">The char array to be shuffled.</param>
/// <param name="key">The key used for shuffling.</param>
/// <returns>The shuffled char array.</returns>
/// <remarks>
/// The shuffling is performed using a custom exchange algorithm based on the specified key.
/// </remarks>
public static char[] Shuffle(char[] input, byte[] key)
{
var size = input.Length;
var exchanges = GetShuffleExchanges(size, key);
for (var i = size - 1; i > 0; i--)
{
var n = exchanges[size - 1 - i];
(input[i], input[n]) = (input[n], input[i]);
}
return input;
}
/// <summary>
/// De-shuffles a byte array based on a given key using a custom exchange algorithm.
/// </summary>
/// <param name="input">The byte array to be de-shuffled.</param>
/// <param name="key">The key used for de-shuffling.</param>
/// <returns>The de-shuffled byte array.</returns>
/// <remarks>
/// The de-shuffling is performed using a custom exchange algorithm based on the specified key.
/// </remarks>
public static byte[] DeShuffle(byte[] input, byte[] key)
{
var size = input.Length;
var exchanges = GetShuffleExchanges(size, key);
for (var i = 1; i < size; i++)
{
var n = exchanges[size - i - 1];
(input[i], input[n]) = (input[n], input[i]);
}
return input;
}
/// <summary>
/// De-shuffles a char array based on a given key using a custom exchange algorithm.
/// </summary>
/// <param name="input">The char array to be de-shuffled.</param>
/// <param name="key">The key used for de-shuffling.</param>
/// <returns>The de-shuffled char array.</returns>
/// <remarks>
/// The de-shuffling is performed using a custom exchange algorithm based on the specified key.
/// </remarks>
public static char[] DeShuffle(char[] input, byte[] key)
{
var size = input.Length;
var exchanges = GetShuffleExchanges(size, key);
for (var i = 1; i < size; i++)
{
var n = exchanges[size - i - 1];
(input[i], input[n]) = (input[n], input[i]);
}
return input;
}
/// <summary>
/// Encrypts data using the XChaCha20-Poly1305 authenticated encryption algorithm.
/// </summary>
/// <param name="input">The data to be encrypted.</param>
/// <param name="key">The encryption key.</param>
/// <param name="nonce">The nonce (number used once) for encryption.</param>
/// <returns>A byte array representing the encrypted data.</returns>
public static byte[] EncryptXChaCha20Poly1305(byte[] input, byte[] key, byte[] nonce)
{
var result = SecretAeadXChaCha20Poly1305.Encrypt(input, key, nonce);
return result;
}
/// <summary>
/// Decrypts data encrypted using the XChaCha20-Poly1305 authenticated encryption algorithm.
/// </summary>
/// <param name="input">The encrypted data.</param>
/// <param name="key">The decryption key.</param>
/// <param name="nonce">The nonce used during encryption.</param>
/// <returns>A byte array representing the decrypted data.</returns>
public static byte[] DecryptXChaCha20Poly1305(byte[] input, byte[] nonce, byte[] key)
{
var result = SecretAeadXChaCha20Poly1305.Decrypt(input, nonce, key);
return result;
}
/// <summary>
/// Decrypts a byte array that has been encrypted using the AES block cipher in Cipher Block Chaining
/// (CBC) mode
/// with HMAC-SHA3 authentication.
/// </summary>
/// <param name="inputText">The byte array to be decrypted.</param>
/// <param name="key">The key used for decryption.</param>
/// <param name="iv"></param>
/// <param name="hMacKey">The key used for HMAC-SHA3 authentication.</param>
/// <returns>The decrypted byte array.</returns>
/// <exception cref="ArgumentException">Thrown when the provided inputText, key, or hMacKey is empty or null.</exception>
/// <exception cref="CryptographicException">Thrown when the authentication tag does not match.</exception>
public static byte[] EncryptAes(byte[] inputText, byte[] key, byte[] iv, byte[] hMacKey)
{
if (inputText == Array.Empty<byte>())
throw new ArgumentException("Value was empty or null.", nameof(inputText));
if (key == Array.Empty<byte>())
throw new ArgumentException("Value was empty or null.", nameof(key));
if (iv == Array.Empty<byte>())
throw new ArgumentException("Value was empty or null.", nameof(iv));
if (hMacKey == Array.Empty<byte>())
throw new ArgumentException("Value was empty or null.", nameof(hMacKey));
using var aes = Aes.Create();
aes.BlockSize = CryptoConstants.BlockBitSize;
aes.KeySize = CryptoConstants.KeyBitSize;
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
byte[] cipherText;
using (var encryptor = aes.CreateEncryptor(key, iv))
using (var memStream = new MemoryStream())
{
using (var cryptoStream = new CryptoStream(memStream, encryptor, CryptoStreamMode.Write))
using (var cipherStream = new MemoryStream(inputText))
{
cipherStream.CopyTo(cryptoStream, (int)cipherStream.Length);
cryptoStream.FlushFinalBlock();
}
cipherText = memStream.ToArray();
}
var prependItems = new byte[cipherText.Length + iv.Length];
Buffer.BlockCopy(iv, 0, prependItems, 0, iv.Length);
Buffer.BlockCopy(cipherText, 0, prependItems, iv.Length, cipherText.Length);
var tag = HashingMethods.HmacSha3(prependItems, hMacKey);
var authenticatedBuffer = new byte[prependItems.Length + tag.Length];
Buffer.BlockCopy(prependItems, 0, authenticatedBuffer, 0, prependItems.Length);
Buffer.BlockCopy(tag, 0, authenticatedBuffer, prependItems.Length, tag.Length);
return authenticatedBuffer;
}
/// <summary>
/// Decrypts a byte array that has been encrypted using the AES block cipher in Cipher Block Chaining
/// (CBC) mode
/// with HMAC-SHA3 authentication.
/// </summary>
/// <param name="inputText">The byte array to be decrypted.</param>
/// <param name="key">The key used for decryption.</param>
/// <param name="hMacKey">The key used for HMAC-SHA3 authentication.</param>
/// <returns>The decrypted byte array.</returns>
/// <exception cref="ArgumentException">Thrown when the provided inputText, key, or hMacKey is empty or null.</exception>
/// <exception cref="CryptographicException">Thrown when the authentication tag does not match.</exception>
public static byte[] DecryptAes(byte[] inputText, byte[] key, byte[] hMacKey)
{
if (inputText == Array.Empty<byte>())
throw new ArgumentException("Value was empty or null.", nameof(inputText));
if (key == Array.Empty<byte>())
throw new ArgumentException("Value was empty or null.", nameof(key));
if (hMacKey == Array.Empty<byte>())
throw new ArgumentException("Value was empty or null.", nameof(hMacKey));
using var aes = Aes.Create();
aes.BlockSize = CryptoConstants.BlockBitSize;
aes.KeySize = CryptoConstants.KeyBitSize;
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
var receivedHash = new byte[CryptoConstants.HmacLength];
Buffer.BlockCopy(inputText, inputText.Length - CryptoConstants.HmacLength, receivedHash, 0,
CryptoConstants.HmacLength);
var cipherWithIv = new byte[inputText.Length - CryptoConstants.HmacLength];
Buffer.BlockCopy(inputText, 0, cipherWithIv, 0, inputText.Length - CryptoConstants.HmacLength);
var hashedInput = HashingMethods.HmacSha3(cipherWithIv, hMacKey);
var isMatch = CryptographicOperations.FixedTimeEquals(receivedHash, hashedInput);
if (!isMatch)
throw new CryptographicException("Authentication tag does not match.");
var iv = new byte[CryptoConstants.Iv];
var cipherResult = new byte[inputText.Length - CryptoConstants.Iv - CryptoConstants.HmacLength];
Buffer.BlockCopy(inputText, 0, iv, 0, iv.Length);
Buffer.BlockCopy(inputText, iv.Length, cipherResult, 0, cipherResult.Length);
using var decryptor = aes.CreateDecryptor(key, iv);
using var memStream = new MemoryStream();
using (var decryptStream = new CryptoStream(memStream, decryptor, CryptoStreamMode.Write))
using (var plainStream = new MemoryStream(cipherResult))
{
plainStream.CopyTo(decryptStream, (int)plainStream.Length);
plainStream.Flush();
decryptStream.FlushFinalBlock();
}
return memStream.ToArray();
}
/// <summary>
/// Encrypts a byte array using the ThreeFish block cipher in Cipher Block Chaining (CBC) mode with HMAC-SHA3
/// authentication.
/// </summary>
/// <param name="inputText">The byte array to be encrypted.</param>
/// <param name="key">The key used for encryption.</param>
/// <param name="iv">The initialization vector used in CBC mode.</param>
/// <param name="hMacKey">The key used for HMAC-SHA3 authentication.</param>
/// <returns>The encrypted and authenticated byte array.</returns>
public static byte[] EncryptThreeFish(byte[] inputText, byte[] key, byte[] iv, byte[] hMacKey)
{
// Initialize ThreeFish block cipher with CBC mode and PKCS7 padding
var threeFish = new ThreefishEngine(1024);
var cipherMode = new CbcBlockCipher(threeFish);
var padding = new Pkcs7Padding();
var cbcCipher = new PaddedBufferedBlockCipher(cipherMode, padding);
var keyParam = new KeyParameter(key);
cbcCipher.Init(true, new ParametersWithIV(keyParam, iv));
var cipherText = new byte[cbcCipher.GetOutputSize(inputText.Length)];
var processLength = cbcCipher.ProcessBytes(inputText, 0, inputText.Length, cipherText, 0);
var finalLength = cbcCipher.DoFinal(cipherText, processLength);
var finalCipherText = new byte[finalLength + processLength];
Buffer.BlockCopy(cipherText, 0, finalCipherText, 0, finalCipherText.Length);
var prependItems = new byte[finalCipherText.Length + iv.Length];
Buffer.BlockCopy(iv, 0, prependItems, 0, iv.Length);
Buffer.BlockCopy(finalCipherText, 0, prependItems, iv.Length, finalCipherText.Length);
var tag = HashingMethods.HmacSha3(prependItems, hMacKey);
var authenticatedBuffer = new byte[prependItems.Length + tag.Length];
Buffer.BlockCopy(prependItems, 0, authenticatedBuffer, 0, prependItems.Length);
Buffer.BlockCopy(tag, 0, authenticatedBuffer, prependItems.Length, tag.Length);
return authenticatedBuffer;
}
/// <summary>
/// Decrypts a byte array that has been encrypted using the ThreeFish block cipher in Cipher Block Chaining (CBC) mode
/// with HMAC-SHA3 authentication.
/// </summary>
/// <param name="inputText">The byte array to be decrypted.</param>
/// <param name="key">The key used for decryption.</param>
/// <param name="hMacKey">The key used for HMAC-SHA3 authentication.</param>
/// <returns>The decrypted byte array.</returns>
/// <exception cref="ArgumentException">Thrown when the provided inputText, key, or hMacKey is empty or null.</exception>
/// <exception cref="CryptographicException">Thrown when the authentication tag does not match.</exception>
public static byte[] DecryptThreeFish(byte[] inputText, byte[] key, byte[] hMacKey)
{
if (inputText == Array.Empty<byte>())
throw new ArgumentException("Value was empty or null.", nameof(inputText));
if (key == Array.Empty<byte>())
throw new ArgumentException("Value was empty or null.", nameof(key));
if (hMacKey == Array.Empty<byte>())
throw new ArgumentException("Value was empty or null.", nameof(hMacKey));
var receivedHash = new byte[CryptoConstants.HmacLength];
Buffer.BlockCopy(inputText, inputText.Length - CryptoConstants.HmacLength, receivedHash, 0,
CryptoConstants.HmacLength);
var cipherWithIv = new byte[inputText.Length - CryptoConstants.HmacLength];
Buffer.BlockCopy(inputText, 0, cipherWithIv, 0, inputText.Length - CryptoConstants.HmacLength);
var hashedInput = HashingMethods.HmacSha3(cipherWithIv, hMacKey);
var isMatch = CryptographicOperations.FixedTimeEquals(receivedHash, hashedInput);
if (!isMatch)
throw new CryptographicException("Authentication tag does not match.");
var iv = new byte[CryptoConstants.ThreeFish];
var cipherResult = new byte[inputText.Length - CryptoConstants.ThreeFish - CryptoConstants.HmacLength];
Buffer.BlockCopy(inputText, 0, iv, 0, iv.Length);
Buffer.BlockCopy(inputText, iv.Length, cipherResult, 0, cipherResult.Length);
var threeFish = new ThreefishEngine(1024);
var cipherMode = new CbcBlockCipher(threeFish);
var padding = new Pkcs7Padding();
var cbcCipher = new PaddedBufferedBlockCipher(cipherMode, padding);
var keyParam = new KeyParameter(key);
cbcCipher.Init(false, new ParametersWithIV(keyParam, iv));
var plainText = new byte[cbcCipher.GetOutputSize(cipherResult.Length)];
var processLength = cbcCipher.ProcessBytes(cipherResult, 0, cipherResult.Length, plainText, 0);
var finalLength = cbcCipher.DoFinal(plainText, processLength);
var finalPlainText = new byte[finalLength + processLength];
Buffer.BlockCopy(plainText, 0, finalPlainText, 0, finalPlainText.Length);
return finalPlainText;
}
/// <summary>
/// Encrypts a byte array using the Serpent block cipher in Cipher Block Chaining (CBC) mode with HMAC-SHA3
/// authentication.
/// </summary>
/// <param name="inputText">The byte array to be encrypted.</param>
/// <param name="key">The key used for encryption.</param>
/// <param name="iv">The initialization vector used in CBC mode.</param>
/// <param name="hMacKey">The key used for HMAC-SHA3 authentication.</param>
/// <returns>The encrypted and authenticated byte array.</returns>
public static byte[] EncryptSerpent(byte[] inputText, byte[] key, byte[] iv, byte[] hMacKey)
{
var serpent = new SerpentEngine();
var cipherMode = new CbcBlockCipher(serpent);
var padding = new Pkcs7Padding();
var cbcCipher = new PaddedBufferedBlockCipher(cipherMode, padding);
var keyParam = new KeyParameter(key);
cbcCipher.Init(true, new ParametersWithIV(keyParam, iv));
var cipherText = new byte[cbcCipher.GetOutputSize(inputText.Length)];
var processLength = cbcCipher.ProcessBytes(inputText, 0, inputText.Length, cipherText, 0);
var finalLength = cbcCipher.DoFinal(cipherText, processLength);
var finalCipherText = new byte[finalLength + processLength];
Buffer.BlockCopy(cipherText, 0, finalCipherText, 0, finalCipherText.Length);
var prependItems = new byte[finalCipherText.Length + iv.Length];
Buffer.BlockCopy(iv, 0, prependItems, 0, iv.Length);
Buffer.BlockCopy(finalCipherText, 0, prependItems, iv.Length, finalCipherText.Length);
var tag = HashingMethods.HmacSha3(prependItems, hMacKey);
var authenticatedBuffer = new byte[prependItems.Length + tag.Length];
Buffer.BlockCopy(prependItems, 0, authenticatedBuffer, 0, prependItems.Length);
Buffer.BlockCopy(tag, 0, authenticatedBuffer, prependItems.Length, tag.Length);
return authenticatedBuffer;
}
/// <summary>
/// Decrypts a byte array that has been encrypted using the Serpent block cipher in Cipher Block Chaining (CBC) mode
/// with HMAC-SHA3 authentication.
/// </summary>
/// <param name="inputText">The byte array to be decrypted.</param>
/// <param name="key">The key used for decryption.</param>
/// <param name="hMacKey">The key used for HMAC-SHA3 authentication.</param>
/// <returns>The decrypted byte array.</returns>
/// <exception cref="ArgumentException">Thrown when the provided inputText, key, or hMacKey is empty or null.</exception>
/// <exception cref="CryptographicException">Thrown when the authentication tag does not match.</exception>
public static byte[] DecryptSerpent(byte[] inputText, byte[] key, byte[] hMacKey)
{
if (inputText == Array.Empty<byte>())
throw new ArgumentException("Value was empty or null.", nameof(inputText));
if (key == Array.Empty<byte>())
throw new ArgumentException("Value was empty or null.", nameof(key));
if (hMacKey == Array.Empty<byte>())
throw new ArgumentException("Value was empty or null.", nameof(hMacKey));
var receivedHash = new byte[CryptoConstants.HmacLength];
Buffer.BlockCopy(inputText, inputText.Length - CryptoConstants.HmacLength, receivedHash, 0,
CryptoConstants.HmacLength);
var cipherWithIv = new byte[inputText.Length - CryptoConstants.HmacLength];
Buffer.BlockCopy(inputText, 0, cipherWithIv, 0, inputText.Length - CryptoConstants.HmacLength);
var hashedInput = HashingMethods.HmacSha3(cipherWithIv, hMacKey);
var isMatch = CryptographicOperations.FixedTimeEquals(receivedHash, hashedInput);
if (!isMatch)
throw new CryptographicException("Authentication tag does not match.");
var iv = new byte[CryptoConstants.Iv];
var cipherResult = new byte[inputText.Length - CryptoConstants.Iv - CryptoConstants.HmacLength];
Buffer.BlockCopy(inputText, 0, iv, 0, iv.Length);
Buffer.BlockCopy(inputText, iv.Length, cipherResult, 0, cipherResult.Length);
var serpent = new SerpentEngine();
var cipherMode = new CbcBlockCipher(serpent);
var padding = new Pkcs7Padding();
var cbcCipher = new PaddedBufferedBlockCipher(cipherMode, padding);
var keyParam = new KeyParameter(key);
cbcCipher.Init(false, new ParametersWithIV(keyParam, iv));
var plainText = new byte[cbcCipher.GetOutputSize(cipherResult.Length)];
var processLength = cbcCipher.ProcessBytes(cipherResult, 0, cipherResult.Length, plainText, 0);
var finalLength = cbcCipher.DoFinal(plainText, processLength);
var finalPlainText = new byte[finalLength + processLength];
Buffer.BlockCopy(plainText, 0, finalPlainText, 0, finalPlainText.Length);
return finalPlainText;
}
}
/// <summary>
/// A class that contains encryption and decryption methods.
/// </summary>
private static class EncryptionDecryption
{
/// <summary>
/// Asynchronously encrypts a byte array using a multi-layer encryption approach.
/// </summary>
/// <param name="plaintext">The byte array to be encrypted.</param>
/// <param name="key">The key used for the first layer of encryption (XChaCha20-Poly1305).</param>
/// <param name="key2">The key used for the second layer of encryption (ThreeFish).</param>
/// <param name="key3">The key used for the third layer of encryption.</param>
/// <param name="key4">The key used for the fourth layer of encryption.</param>
/// <param name="key5">The key used for shuffling the final encrypted result.</param>
/// <param name="hMacKey">The key used for HMAC in the second layer of encryption.</param>
/// <param name="hMacKey2">The key used for HMAC in the third layer of encryption.</param>
/// <param name="hMacKey3">The key used for HMAC in the fourth layer of encryption.</param>
/// <returns>A shuffled byte array containing nonces and the final encrypted result.</returns>
/// <exception cref="ArgumentException">Thrown when any of the input parameters is an empty array.</exception>
/// <exception cref="Exception">Thrown when any intermediate or final encrypted value is empty.</exception>
public static byte[] EncryptV3(ref byte[] plaintext,
ref byte[] key, ref byte[] key2, ref byte[] key3, ref byte[] key4, ref byte[] key5, ref byte[] hMacKey,
ref byte[] hMacKey2,
ref byte[] hMacKey3)
{
if (plaintext == Array.Empty<byte>())
throw new ArgumentException("Value was empty.", nameof(plaintext));
if (key == Array.Empty<byte>())
throw new ArgumentException("Value was empty.", nameof(key));
if (key2 == Array.Empty<byte>())
throw new ArgumentException("Value was empty.", nameof(key2));
if (key3 == Array.Empty<byte>())
throw new ArgumentException("Value was empty.", nameof(key3));
if (key4 == Array.Empty<byte>())
throw new ArgumentException("Value was empty.", nameof(key4));
if (key5 == Array.Empty<byte>())
throw new ArgumentException("Value was empty.", nameof(key5));
if (hMacKey == Array.Empty<byte>())
throw new ArgumentException("Value was empty.", nameof(hMacKey));
if (hMacKey2 == Array.Empty<byte>())
throw new ArgumentException("Value was empty.", nameof(hMacKey2));
if (hMacKey3 == Array.Empty<byte>())
throw new ArgumentException("Value was empty.", nameof(hMacKey3));
var nonce = CryptoUtilities.RndByteSized(CryptoConstants.ChaChaNonceSize);
var nonce2 = CryptoUtilities.RndByteSized(CryptoConstants.ThreeFish);
var nonce3 = CryptoUtilities.RndByteSized(CryptoConstants.Iv);
var nonce4 = CryptoUtilities.RndByteSized(CryptoConstants.Iv);
var cipherText = Algorithms.EncryptXChaCha20Poly1305(plaintext, nonce, key) ??
throw new Exception("Value was empty.");
var cipherTextL2 = Algorithms.EncryptThreeFish(cipherText, key2, nonce2, hMacKey) ??
throw new Exception("Value was empty.");
var cipherTextL3 = Algorithms.EncryptSerpent(cipherTextL2, key3, nonce3, hMacKey2) ??
throw new Exception("Value was empty.");
var cipherTextL4 = Algorithms.EncryptAes(cipherTextL3, key4, nonce4, hMacKey3) ??
throw new Exception("Value was empty.");
var result = nonce.Concat(nonce2).Concat(nonce3).Concat(nonce4).Concat(cipherTextL4).ToArray();
var shuffledResult = Algorithms.Shuffle(result, key5);
return shuffledResult;
}
/// <summary>
/// Asynchronously decrypts a byte array that has been encrypted using a multi-layer encryption approach.
/// </summary>
/// <param name="cipherText">The byte array to be decrypted.</param>
/// <param name="key">The key used for the first layer of decryption (XChaCha20-Poly1305).</param>
/// <param name="key2">The key used for the second layer of decryption (ThreeFish).</param>
/// <param name="key3">The key used for the third layer of decryption.</param>
/// <param name="key4">The key used for the fourth layer of encryption.</param>
/// <param name="key5">The key used for unshuffling the ciphertext.</param>
/// <param name="hMacKey">The key used for HMAC in the second layer of decryption.</param>
/// <param name="hMacKey2">The key used for HMAC in the third layer of decryption.</param>
/// <param name="hMacKey3">The key used for HMAC in the fourth layer of encryption.</param>
/// <returns>The decrypted byte array.</returns>
/// <exception cref="ArgumentException">Thrown when any of the input parameters is an empty array.</exception>
/// <exception cref="Exception">Thrown when any intermediate or final decrypted value is empty.</exception>
public static byte[] DecryptV3(ref byte[] cipherText,
ref byte[] key, ref byte[] key2, ref byte[] key3, ref byte[] key4, ref byte[] key5, ref byte[] hMacKey,
ref byte[] hMacKey2,
ref byte[] hMacKey3)
{
if (cipherText == Array.Empty<byte>())
throw new ArgumentException("Value was empty.", nameof(cipherText));
if (key == Array.Empty<byte>())
throw new ArgumentException("Value was empty.", nameof(key));
if (key2 == Array.Empty<byte>())
throw new ArgumentException("Value was empty.", nameof(key2));
if (key3 == Array.Empty<byte>())
throw new ArgumentException("Value was empty.", nameof(key3));
if (key4 == Array.Empty<byte>())
throw new ArgumentException("Value was empty.", nameof(key4));
if (key5 == Array.Empty<byte>())
throw new ArgumentException("Value was empty.", nameof(key5));
if (hMacKey == Array.Empty<byte>())
throw new ArgumentException("Value was empty.", nameof(hMacKey));
if (hMacKey2 == Array.Empty<byte>())
throw new ArgumentException("Value was empty.", nameof(hMacKey2));
if (hMacKey3 == Array.Empty<byte>())
throw new ArgumentException("Value was empty.", nameof(hMacKey3));
var unshuffledResult = Algorithms.DeShuffle(cipherText, key5);
var nonce = new byte[CryptoConstants.ChaChaNonceSize];
Buffer.BlockCopy(unshuffledResult, 0, nonce, 0, nonce.Length);
var nonce2 = new byte[CryptoConstants.ThreeFish];
Buffer.BlockCopy(unshuffledResult, nonce.Length, nonce2, 0, nonce2.Length);
var nonce3 = new byte[CryptoConstants.Iv];
Buffer.BlockCopy(unshuffledResult, nonce.Length + nonce2.Length, nonce3, 0, nonce3.Length);
var nonce4 = new byte[CryptoConstants.Iv];
Buffer.BlockCopy(unshuffledResult, nonce.Length + nonce2.Length + nonce3.Length, nonce4, 0, nonce4.Length);
var cipherResult =
new byte[unshuffledResult.Length - nonce4.Length - nonce3.Length - nonce2.Length - nonce.Length];
Buffer.BlockCopy(unshuffledResult, nonce.Length + nonce2.Length + nonce3.Length + nonce4.Length,
cipherResult,
0,
cipherResult.Length);
var resultL4 = Algorithms.DecryptAes(cipherResult, key4, hMacKey3) ??
throw new Exception("Value was empty.");
var resultL3 = Algorithms.DecryptSerpent(resultL4, key3, hMacKey2) ??
throw new Exception("Value was empty.");
var resultL2 = Algorithms.DecryptThreeFish(resultL3, key2, hMacKey) ??
throw new Exception("Value was empty.");
var result = Algorithms.DecryptXChaCha20Poly1305(resultL2, nonce, key) ??
throw new Exception("Value was empty.");
return result;
}
}
I have most of it documented. I realize this is overkill, but I'd like anyone with knowledge of cryptography to chime in here.
serious security professionalwithout asking questions? \$\endgroup\$