diff --git a/AntiOnlineDecompression.csproj b/AntiOnlineDecompression.csproj
index b2f47b0..aed9c34 100644
--- a/AntiOnlineDecompression.csproj
+++ b/AntiOnlineDecompression.csproj
@@ -2,10 +2,11 @@
Exe
- net7.0
+ net9.0
AntiOnlineDecompression.Program
enable
+ true
true
false
true
@@ -26,7 +27,6 @@
- true
diff --git a/CSChaCha20.cs b/CSChaCha20.cs
index 3c72bc2..06ea4ed 100644
--- a/CSChaCha20.cs
+++ b/CSChaCha20.cs
@@ -1,6 +1,6 @@
/*
* Copyright (c) 2015, 2018 Scott Bennett
- * (c) 2018-2021 Kaarlo Räihä
+ * (c) 2018-2023 Kaarlo Räihä
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@@ -17,645 +17,911 @@
using System;
using System.IO;
-using System.Text;
using System.Threading.Tasks;
-using System.Runtime.CompilerServices; // For MethodImplOptions.AggressiveInlining
+using System.Runtime.Intrinsics;
+using System.Runtime.CompilerServices;
-namespace CSChaCha20
+namespace AntiOnlineDecompression;
+
+///
+/// Chosen SIMD mode
+///
+public enum SimdMode
{
///
- /// Class for ChaCha20 encryption / decryption
- ///
- public sealed class ChaCha20 : IDisposable
- {
- ///
- /// Only allowed key lenght in bytes
- ///
- public const int allowedKeyLength = 32;
-
- ///
- /// Only allowed nonce lenght in bytes
- ///
- public const int allowedNonceLength = 12;
-
- ///
- /// How many bytes are processed per loop
- ///
- public const int processBytesAtTime = 64;
-
- private const int stateLength = 16;
-
- ///
- /// The ChaCha20 state (aka "context")
- ///
- private readonly uint[] state = new uint[stateLength];
-
- ///
- /// Determines if the objects in this class have been disposed of. Set to true by the Dispose() method.
- ///
- private bool isDisposed = false;
-
- ///
- /// Set up a new ChaCha20 state. The lengths of the given parameters are checked before encryption happens.
- ///
- ///
- /// See ChaCha20 Spec Section 2.4 for a detailed description of the inputs.
- ///
- ///
- /// A 32-byte (256-bit) key, treated as a concatenation of eight 32-bit little-endian integers
- ///
- ///
- /// A 12-byte (96-bit) nonce, treated as a concatenation of three 32-bit little-endian integers
- ///
- ///
- /// A 4-byte (32-bit) block counter, treated as a 32-bit little-endian integer
- ///
- public ChaCha20(byte[] key, byte[] nonce, uint counter)
- {
- this.KeySetup(key);
- this.IvSetup(nonce, counter);
- }
-
-#if NET6_0_OR_GREATER
-
- ///
- /// Set up a new ChaCha20 state. The lengths of the given parameters are checked before encryption happens.
- ///
- ///
- /// See ChaCha20 Spec Section 2.4 for a detailed description of the inputs.
- ///
- /// A 32-byte (256-bit) key, treated as a concatenation of eight 32-bit little-endian integers
- /// A 12-byte (96-bit) nonce, treated as a concatenation of three 32-bit little-endian integers
- /// A 4-byte (32-bit) block counter, treated as a 32-bit little-endian integer
- public ChaCha20(ReadOnlySpan key, ReadOnlySpan nonce, uint counter)
- {
- this.KeySetup(key.ToArray());
- this.IvSetup(nonce.ToArray(), counter);
- }
-
-#endif // NET6_0_OR_GREATER
-
- ///
- /// The ChaCha20 state (aka "context"). Read-Only.
- ///
- public uint[] State
- {
- get
- {
- return this.state;
- }
- }
+ /// Autodetect
+ ///
+ AutoDetect = 0,
+
+ ///
+ /// 128 bit SIMD
+ ///
+ V128,
+
+ ///
+ /// 256 bit SIMD
+ ///
+ V256,
+ ///
+ /// 512 bit SIMD
+ ///
+ V512,
+
+ ///
+ /// No SIMD
+ ///
+ None
+}
- // These are the same constants defined in the reference implementation.
- // http://cr.yp.to/streamciphers/timings/estreambench/submissions/salsa20/chacha8/ref/chacha.c
- private static readonly byte[] sigma = Encoding.ASCII.GetBytes("expand 32-byte k");
- private static readonly byte[] tau = Encoding.ASCII.GetBytes("expand 16-byte k");
+///
+/// Class for ChaCha20 encryption / decryption
+///
+public sealed class ChaCha20 : IDisposable
+{
+ ///
+ /// Only allowed key lenght in bytes
+ ///
+ public const int allowedKeyLength = 32;
- ///
- /// Set up the ChaCha state with the given key. A 32-byte key is required and enforced.
- ///
- ///
- /// A 32-byte (256-bit) key, treated as a concatenation of eight 32-bit little-endian integers
- ///
- private void KeySetup(byte[] key)
+ ///
+ /// Only allowed nonce lenght in bytes
+ ///
+ public const int allowedNonceLength = 12;
+
+ ///
+ /// How many bytes are processed per loop
+ ///
+ public const int processBytesAtTime = 64;
+
+ private const int stateLength = 16;
+
+ ///
+ /// The ChaCha20 state (aka "context")
+ ///
+ private readonly uint[] state = new uint[stateLength];
+
+ ///
+ /// Determines if the objects in this class have been disposed of. Set to true by the Dispose() method.
+ ///
+ private bool isDisposed = false;
+
+ ///
+ /// Set up a new ChaCha20 state. The lengths of the given parameters are checked before encryption happens.
+ ///
+ ///
+ /// See ChaCha20 Spec Section 2.4 for a detailed description of the inputs.
+ ///
+ ///
+ /// A 32-byte (256-bit) key, treated as a concatenation of eight 32-bit little-endian integers
+ ///
+ ///
+ /// A 12-byte (96-bit) nonce, treated as a concatenation of three 32-bit little-endian integers
+ ///
+ ///
+ /// A 4-byte (32-bit) block counter, treated as a 32-bit little-endian integer
+ ///
+ public ChaCha20(byte[] key, byte[] nonce, uint counter)
+ {
+ KeySetup(key);
+ IvSetup(nonce, counter);
+ }
+
+ ///
+ /// Set up a new ChaCha20 state. The lengths of the given parameters are checked before encryption happens.
+ ///
+ ///
+ /// See ChaCha20 Spec Section 2.4 for a detailed description of the inputs.
+ ///
+ /// A 32-byte (256-bit) key, treated as a concatenation of eight 32-bit little-endian integers
+ /// A 12-byte (96-bit) nonce, treated as a concatenation of three 32-bit little-endian integers
+ /// A 4-byte (32-bit) block counter, treated as a 32-bit little-endian unsigned integer
+ public ChaCha20(ReadOnlySpan key, ReadOnlySpan nonce, uint counter)
+ {
+ KeySetup(key.ToArray());
+ IvSetup(nonce.ToArray(), counter);
+ }
+
+ ///
+ /// The ChaCha20 state (aka "context"). Read-Only.
+ ///
+ public uint[] State
+ {
+ get
{
- if (key == null)
- {
- throw new ArgumentNullException("Key is null");
- }
+ return state;
+ }
+ }
- if (key.Length != allowedKeyLength)
- {
- throw new ArgumentException($"Key length must be {allowedKeyLength}. Actual: {key.Length}");
- }
- state[4] = Util.U8To32Little(key, 0);
- state[5] = Util.U8To32Little(key, 4);
- state[6] = Util.U8To32Little(key, 8);
- state[7] = Util.U8To32Little(key, 12);
-
- byte[] constants = (key.Length == allowedKeyLength) ? sigma : tau;
- int keyIndex = key.Length - 16;
-
- state[8] = Util.U8To32Little(key, keyIndex + 0);
- state[9] = Util.U8To32Little(key, keyIndex + 4);
- state[10] = Util.U8To32Little(key, keyIndex + 8);
- state[11] = Util.U8To32Little(key, keyIndex + 12);
-
- state[0] = Util.U8To32Little(constants, 0);
- state[1] = Util.U8To32Little(constants, 4);
- state[2] = Util.U8To32Little(constants, 8);
- state[3] = Util.U8To32Little(constants, 12);
- }
-
- ///
- /// Set up the ChaCha state with the given nonce (aka Initialization Vector or IV) and block counter. A 12-byte nonce and a 4-byte counter are required.
- ///
- ///
- /// A 12-byte (96-bit) nonce, treated as a concatenation of three 32-bit little-endian integers
- ///
- ///
- /// A 4-byte (32-bit) block counter, treated as a 32-bit little-endian integer
- ///
- private void IvSetup(byte[] nonce, uint counter)
- {
- if (nonce == null)
- {
- // There has already been some state set up. Clear it before exiting.
- Dispose();
- throw new ArgumentNullException("Nonce is null");
- }
+ // These are the same constants defined in the reference implementation.
+ // http://cr.yp.to/streamciphers/timings/estreambench/submissions/salsa20/chacha8/ref/chacha.c
+ private static readonly byte[] sigma = "expand 32-byte k"u8.ToArray();
+ private static readonly byte[] tau = "expand 16-byte k"u8.ToArray();
- if (nonce.Length != allowedNonceLength)
- {
- // There has already been some state set up. Clear it before exiting.
- Dispose();
- throw new ArgumentException($"Nonce length must be {allowedNonceLength}. Actual: {nonce.Length}");
- }
+ ///
+ /// Set up the ChaCha state with the given key. A 32-byte key is required and enforced.
+ ///
+ ///
+ /// A 32-byte (256-bit) key, treated as a concatenation of eight 32-bit little-endian integers
+ ///
+ private void KeySetup(byte[] key)
+ {
+ ArgumentNullException.ThrowIfNull(key);
- state[12] = counter;
- state[13] = Util.U8To32Little(nonce, 0);
- state[14] = Util.U8To32Little(nonce, 4);
- state[15] = Util.U8To32Little(nonce, 8);
+ if (key.Length != allowedKeyLength)
+ {
+ throw new ArgumentException($"Key length must be {allowedKeyLength}. Actual: {key.Length}");
}
+ state[4] = Util.U8To32Little(key, 0);
+ state[5] = Util.U8To32Little(key, 4);
+ state[6] = Util.U8To32Little(key, 8);
+ state[7] = Util.U8To32Little(key, 12);
+
+ byte[] constants = key.Length == allowedKeyLength ? sigma : tau;
+ int keyIndex = key.Length - 16;
- #region Encryption methods
+ state[8] = Util.U8To32Little(key, keyIndex + 0);
+ state[9] = Util.U8To32Little(key, keyIndex + 4);
+ state[10] = Util.U8To32Little(key, keyIndex + 8);
+ state[11] = Util.U8To32Little(key, keyIndex + 12);
+
+ state[0] = Util.U8To32Little(constants, 0);
+ state[1] = Util.U8To32Little(constants, 4);
+ state[2] = Util.U8To32Little(constants, 8);
+ state[3] = Util.U8To32Little(constants, 12);
+ }
- ///
- /// Encrypt arbitrary-length byte array (input), writing the resulting byte array to preallocated output buffer.
- ///
- /// Since this is symmetric operation, it doesn't really matter if you use Encrypt or Decrypt method
- /// Output byte array, must have enough bytes
- /// Input byte array
- /// Number of bytes to encrypt
- public void EncryptBytes(byte[] output, byte[] input, int numBytes)
+ ///
+ /// Set up the ChaCha state with the given nonce (aka Initialization Vector or IV) and block counter. A 12-byte nonce and a 4-byte counter are required.
+ ///
+ ///
+ /// A 12-byte (96-bit) nonce, treated as a concatenation of three 32-bit little-endian integers
+ ///
+ ///
+ /// A 4-byte (32-bit) block counter, treated as a 32-bit little-endian integer
+ ///
+ private void IvSetup(byte[] nonce, uint counter)
+ {
+ if (nonce == null)
{
- this.WorkBytes(output, input, numBytes);
+ // There has already been some state set up. Clear it before exiting.
+ Dispose();
+ throw new ArgumentNullException(nameof(nonce),"Nonce is null");
}
- ///
- /// Encrypt arbitrary-length byte stream (input), writing the resulting bytes to another stream (output)
- ///
- /// Output stream
- /// Input stream
- /// How many bytes to read and write at time, default is 1024
- public void EncryptStream(Stream output, Stream input, int howManyBytesToProcessAtTime = 1024)
+ if (nonce.Length != allowedNonceLength)
{
- this.WorkStreams(output, input, howManyBytesToProcessAtTime);
+ // There has already been some state set up. Clear it before exiting.
+ Dispose();
+ throw new ArgumentException($"Nonce length must be {allowedNonceLength}. Actual: {nonce.Length}");
}
- ///
- /// Async encrypt arbitrary-length byte stream (input), writing the resulting bytes to another stream (output)
- ///
- /// Output stream
- /// Input stream
- /// How many bytes to read and write at time, default is 1024
- public async Task EncryptStreamAsync(Stream output, Stream input, int howManyBytesToProcessAtTime = 1024)
+ state[12] = counter;
+ state[13] = Util.U8To32Little(nonce, 0);
+ state[14] = Util.U8To32Little(nonce, 4);
+ state[15] = Util.U8To32Little(nonce, 8);
+ }
+
+ private static SimdMode DetectSimdMode()
+ {
+ if (Vector512.IsHardwareAccelerated)
+ {
+ return SimdMode.V512;
+ }
+ else if (Vector256.IsHardwareAccelerated)
+ {
+ return SimdMode.V256;
+ }
+ else if (Vector128.IsHardwareAccelerated)
+ {
+ return SimdMode.V128;
+ }
+
+ return SimdMode.None;
+ }
+
+ #region Encryption methods
+
+ ///
+ /// Encrypt arbitrary-length byte array (input), writing the resulting byte array to preallocated output buffer.
+ ///
+ /// Since this is symmetric operation, it doesn't really matter if you use Encrypt or Decrypt method
+ /// Output byte array, must have enough bytes
+ /// Input byte array
+ /// Number of bytes to encrypt
+ /// Chosen SIMD mode (default is auto-detect)
+ public void EncryptBytes(byte[] output, byte[] input, int numBytes, SimdMode simdMode = SimdMode.AutoDetect)
+ {
+ if (output == null)
{
- await this.WorkStreamsAsync(output, input, howManyBytesToProcessAtTime);
+ throw new ArgumentNullException(nameof(output), "Output cannot be null");
}
- ///
- /// Encrypt arbitrary-length byte array (input), writing the resulting byte array to preallocated output buffer.
- ///
- /// Since this is symmetric operation, it doesn't really matter if you use Encrypt or Decrypt method
- /// Output byte array, must have enough bytes
- /// Input byte array
- public void EncryptBytes(byte[] output, byte[] input)
+ if (input == null)
{
- this.WorkBytes(output, input, input.Length);
+ throw new ArgumentNullException(nameof(input), "Input cannot be null");
}
- ///
- /// Encrypt arbitrary-length byte array (input), writing the resulting byte array that is allocated by method.
- ///
- /// Since this is symmetric operation, it doesn't really matter if you use Encrypt or Decrypt method
- /// Input byte array
- /// Number of bytes to encrypt
- /// Byte array that contains encrypted bytes
- public byte[] EncryptBytes(byte[] input, int numBytes)
+ if (numBytes < 0 || numBytes > input.Length)
{
- byte[] returnArray = new byte[numBytes];
- this.WorkBytes(returnArray, input, numBytes);
- return returnArray;
+ throw new ArgumentOutOfRangeException(nameof(numBytes), "The number of bytes to read must be between [0..input.Length]");
}
- ///
- /// Encrypt arbitrary-length byte array (input), writing the resulting byte array that is allocated by method.
- ///
- /// Since this is symmetric operation, it doesn't really matter if you use Encrypt or Decrypt method
- /// Input byte array
- /// Byte array that contains encrypted bytes
- public byte[] EncryptBytes(byte[] input)
+ if (output.Length < numBytes)
{
- byte[] returnArray = new byte[input.Length];
- this.WorkBytes(returnArray, input, input.Length);
- return returnArray;
+ throw new ArgumentOutOfRangeException(nameof(output), $"Output byte array should be able to take at least {numBytes}");
}
- ///
- /// Encrypt string as UTF8 byte array, returns byte array that is allocated by method.
- ///
- /// Here you can NOT swap encrypt and decrypt methods, because of bytes-string transform
- /// Input string
- /// Byte array that contains encrypted bytes
- public byte[] EncryptString(string input)
+ if (simdMode == SimdMode.AutoDetect)
{
- byte[] utf8Bytes = System.Text.Encoding.UTF8.GetBytes(input);
- byte[] returnArray = new byte[utf8Bytes.Length];
+ simdMode = DetectSimdMode();
+ }
+
+ WorkBytes(output, input, numBytes, simdMode);
+ }
- this.WorkBytes(returnArray, utf8Bytes, utf8Bytes.Length);
- return returnArray;
+ ///
+ /// Encrypt arbitrary-length byte stream (input), writing the resulting bytes to another stream (output)
+ ///
+ /// Output stream
+ /// Input stream
+ /// How many bytes to read and write at time, default is 1024
+ /// Chosen SIMD mode (default is auto-detect)
+ public void EncryptStream(Stream output, Stream input, int howManyBytesToProcessAtTime = 1024, SimdMode simdMode = SimdMode.AutoDetect)
+ {
+ if (simdMode == SimdMode.AutoDetect)
+ {
+ simdMode = DetectSimdMode();
}
- #endregion // Encryption methods
+ WorkStreams(output, input, simdMode, howManyBytesToProcessAtTime);
+ }
+ ///
+ /// Async encrypt arbitrary-length byte stream (input), writing the resulting bytes to another stream (output)
+ ///
+ /// Output stream
+ /// Input stream
+ /// How many bytes to read and write at time, default is 1024
+ /// Chosen SIMD mode (default is auto-detect)
+ public async Task EncryptStreamAsync(Stream output, Stream input, int howManyBytesToProcessAtTime = 1024, SimdMode simdMode = SimdMode.AutoDetect)
+ {
+ if (simdMode == SimdMode.AutoDetect)
+ {
+ simdMode = DetectSimdMode();
+ }
- #region // Decryption methods
+ await WorkStreamsAsync(output, input, simdMode, howManyBytesToProcessAtTime);
+ }
- ///
- /// Decrypt arbitrary-length byte array (input), writing the resulting byte array to the output buffer.
- ///
- /// Since this is symmetric operation, it doesn't really matter if you use Encrypt or Decrypt method
- /// Output byte array
- /// Input byte array
- /// Number of bytes to decrypt
- public void DecryptBytes(byte[] output, byte[] input, int numBytes)
+ ///
+ /// Encrypt arbitrary-length byte array (input), writing the resulting byte array to preallocated output buffer.
+ ///
+ /// Since this is symmetric operation, it doesn't really matter if you use Encrypt or Decrypt method
+ /// Output byte array, must have enough bytes
+ /// Input byte array
+ /// Chosen SIMD mode (default is auto-detect)
+ public void EncryptBytes(byte[] output, byte[] input, SimdMode simdMode = SimdMode.AutoDetect)
+ {
+ if (output == null)
{
- this.WorkBytes(output, input, numBytes);
+ throw new ArgumentNullException(nameof(output), "Output cannot be null");
}
- ///
- /// Decrypt arbitrary-length byte stream (input), writing the resulting bytes to another stream (output)
- ///
- /// Output stream
- /// Input stream
- /// How many bytes to read and write at time, default is 1024
- public void DecryptStream(Stream output, Stream input, int howManyBytesToProcessAtTime = 1024)
+ if (input == null)
{
- this.WorkStreams(output, input, howManyBytesToProcessAtTime);
+ throw new ArgumentNullException(nameof(input), "Input cannot be null");
}
- ///
- /// Async decrypt arbitrary-length byte stream (input), writing the resulting bytes to another stream (output)
- ///
- /// Output stream
- /// Input stream
- /// How many bytes to read and write at time, default is 1024
- public async Task DecryptStreamAsync(Stream output, Stream input, int howManyBytesToProcessAtTime = 1024)
+ if (simdMode == SimdMode.AutoDetect)
{
- await this.WorkStreamsAsync(output, input, howManyBytesToProcessAtTime);
+ simdMode = DetectSimdMode();
}
- ///
- /// Decrypt arbitrary-length byte array (input), writing the resulting byte array to preallocated output buffer.
- ///
- /// Since this is symmetric operation, it doesn't really matter if you use Encrypt or Decrypt method
- /// Output byte array, must have enough bytes
- /// Input byte array
- public void DecryptBytes(byte[] output, byte[] input)
+ WorkBytes(output, input, input.Length, simdMode);
+ }
+
+ ///
+ /// Encrypt arbitrary-length byte array (input), writing the resulting byte array that is allocated by method.
+ ///
+ /// Since this is symmetric operation, it doesn't really matter if you use Encrypt or Decrypt method
+ /// Input byte array
+ /// Number of bytes to encrypt
+ /// Chosen SIMD mode (default is auto-detect)
+ /// Byte array that contains encrypted bytes
+ public byte[] EncryptBytes(byte[] input, int numBytes, SimdMode simdMode = SimdMode.AutoDetect)
+ {
+ if (input == null)
{
- WorkBytes(output, input, input.Length);
+ throw new ArgumentNullException(nameof(input), "Input cannot be null");
}
- ///
- /// Decrypt arbitrary-length byte array (input), writing the resulting byte array that is allocated by method.
- ///
- /// Since this is symmetric operation, it doesn't really matter if you use Encrypt or Decrypt method
- /// Input byte array
- /// Number of bytes to encrypt
- /// Byte array that contains decrypted bytes
- public byte[] DecryptBytes(byte[] input, int numBytes)
+ if (numBytes < 0 || numBytes > input.Length)
{
- byte[] returnArray = new byte[numBytes];
- WorkBytes(returnArray, input, numBytes);
- return returnArray;
+ throw new ArgumentOutOfRangeException(nameof(numBytes), "The number of bytes to read must be between [0..input.Length]");
}
- ///
- /// Decrypt arbitrary-length byte array (input), writing the resulting byte array that is allocated by method.
- ///
- /// Since this is symmetric operation, it doesn't really matter if you use Encrypt or Decrypt method
- /// Input byte array
- /// Byte array that contains decrypted bytes
- public byte[] DecryptBytes(byte[] input)
+ if (simdMode == SimdMode.AutoDetect)
{
- byte[] returnArray = new byte[input.Length];
- WorkBytes(returnArray, input, input.Length);
- return returnArray;
+ simdMode = DetectSimdMode();
}
- ///
- /// Decrypt UTF8 byte array to string.
- ///
- /// Here you can NOT swap encrypt and decrypt methods, because of bytes-string transform
- /// Byte array
- /// Byte array that contains encrypted bytes
- public string DecryptUTF8ByteArray(byte[] input)
+ byte[] returnArray = new byte[numBytes];
+ WorkBytes(returnArray, input, numBytes, simdMode);
+ return returnArray;
+ }
+
+ ///
+ /// Encrypt arbitrary-length byte array (input), writing the resulting byte array that is allocated by method.
+ ///
+ /// Since this is symmetric operation, it doesn't really matter if you use Encrypt or Decrypt method
+ /// Input byte array
+ /// Chosen SIMD mode (default is auto-detect)
+ /// Byte array that contains encrypted bytes
+ public byte[] EncryptBytes(byte[] input, SimdMode simdMode = SimdMode.AutoDetect)
+ {
+ if (input == null)
{
- byte[] tempArray = new byte[input.Length];
+ throw new ArgumentNullException(nameof(input), "Input cannot be null");
+ }
- WorkBytes(tempArray, input, input.Length);
- return System.Text.Encoding.UTF8.GetString(tempArray);
+ if (simdMode == SimdMode.AutoDetect)
+ {
+ simdMode = DetectSimdMode();
}
- #endregion // Decryption methods
+ byte[] returnArray = new byte[input.Length];
+ WorkBytes(returnArray, input, input.Length, simdMode);
+ return returnArray;
+ }
- private void WorkStreams(Stream output, Stream input, int howManyBytesToProcessAtTime = 1024)
+ ///
+ /// Encrypt string as UTF8 byte array, returns byte array that is allocated by method.
+ ///
+ /// Here you can NOT swap encrypt and decrypt methods, because of bytes-string transform
+ /// Input string
+ /// Chosen SIMD mode (default is auto-detect)
+ /// Byte array that contains encrypted bytes
+ public byte[] EncryptString(string input, SimdMode simdMode = SimdMode.AutoDetect)
+ {
+ if (input == null)
{
- int readBytes;
+ throw new ArgumentNullException(nameof(input), "Input cannot be null");
+ }
- byte[] inputBuffer = new byte[howManyBytesToProcessAtTime];
- byte[] outputBuffer = new byte[howManyBytesToProcessAtTime];
+ if (simdMode == SimdMode.AutoDetect)
+ {
+ simdMode = DetectSimdMode();
+ }
- while ((readBytes = input.Read(inputBuffer, 0, howManyBytesToProcessAtTime)) > 0)
- {
- // Encrypt or decrypt
- WorkBytes(output: outputBuffer, input: inputBuffer, numBytes: readBytes);
+ byte[] utf8Bytes = System.Text.Encoding.UTF8.GetBytes(input);
+ byte[] returnArray = new byte[utf8Bytes.Length];
- // Write buffer
- output.Write(outputBuffer, 0, readBytes);
- }
+ WorkBytes(returnArray, utf8Bytes, utf8Bytes.Length, simdMode);
+ return returnArray;
+ }
+
+ #endregion // Encryption methods
+
+
+ #region // Decryption methods
+
+ ///
+ /// Decrypt arbitrary-length byte array (input), writing the resulting byte array to the output buffer.
+ ///
+ /// Since this is symmetric operation, it doesn't really matter if you use Encrypt or Decrypt method
+ /// Output byte array
+ /// Input byte array
+ /// Number of bytes to decrypt
+ /// Chosen SIMD mode (default is auto-detect)
+ public void DecryptBytes(byte[] output, byte[] input, int numBytes, SimdMode simdMode = SimdMode.AutoDetect)
+ {
+ if (output == null)
+ {
+ throw new ArgumentNullException(nameof(output), "Output cannot be null");
}
- private async Task WorkStreamsAsync(Stream output, Stream input, int howManyBytesToProcessAtTime = 1024)
+ if (input == null)
{
- byte[] readBytesBuffer = new byte[howManyBytesToProcessAtTime];
- byte[] writeBytesBuffer = new byte[howManyBytesToProcessAtTime];
- int howManyBytesWereRead = await input.ReadAsync(readBytesBuffer, 0, howManyBytesToProcessAtTime);
+ throw new ArgumentNullException(nameof(input), "Input cannot be null");
+ }
- while (howManyBytesWereRead > 0)
- {
- // Encrypt or decrypt
- WorkBytes(output: writeBytesBuffer, input: readBytesBuffer, numBytes: howManyBytesWereRead);
+ if (numBytes < 0 || numBytes > input.Length)
+ {
+ throw new ArgumentOutOfRangeException(nameof(numBytes), "The number of bytes to read must be between [0..input.Length]");
+ }
- // Write
- await output.WriteAsync(writeBytesBuffer, 0, howManyBytesWereRead);
+ if (output.Length < numBytes)
+ {
+ throw new ArgumentOutOfRangeException(nameof(output), $"Output byte array should be able to take at least {numBytes}");
+ }
- // Read more
- howManyBytesWereRead = await input.ReadAsync(readBytesBuffer, 0, howManyBytesToProcessAtTime);
- }
+ if (simdMode == SimdMode.AutoDetect)
+ {
+ simdMode = DetectSimdMode();
}
- ///
- /// Encrypt or decrypt an arbitrary-length byte array (input), writing the resulting byte array to the output buffer. The number of bytes to read from the input buffer is determined by numBytes.
- ///
- /// Output byte array
- /// Input byte array
- /// How many bytes to process
- private void WorkBytes(byte[] output, byte[] input, int numBytes)
+ WorkBytes(output, input, numBytes, simdMode);
+ }
+
+ ///
+ /// Decrypt arbitrary-length byte stream (input), writing the resulting bytes to another stream (output)
+ ///
+ /// Output stream
+ /// Input stream
+ /// How many bytes to read and write at time, default is 1024
+ /// Chosen SIMD mode (default is auto-detect)
+ public void DecryptStream(Stream output, Stream input, int howManyBytesToProcessAtTime = 1024, SimdMode simdMode = SimdMode.AutoDetect)
+ {
+ if (simdMode == SimdMode.AutoDetect)
{
- if (isDisposed)
- {
- throw new ObjectDisposedException("state", "The ChaCha state has been disposed");
- }
+ simdMode = DetectSimdMode();
+ }
- if (input == null)
- {
- throw new ArgumentNullException("input", "Input cannot be null");
- }
+ WorkStreams(output, input, simdMode, howManyBytesToProcessAtTime);
+ }
- if (output == null)
- {
- throw new ArgumentNullException("output", "Output cannot be null");
- }
+ ///
+ /// Async decrypt arbitrary-length byte stream (input), writing the resulting bytes to another stream (output)
+ ///
+ /// Output stream
+ /// Input stream
+ /// How many bytes to read and write at time, default is 1024
+ /// Chosen SIMD mode (default is auto-detect)
+ public async Task DecryptStreamAsync(Stream output, Stream input, int howManyBytesToProcessAtTime = 1024, SimdMode simdMode = SimdMode.AutoDetect)
+ {
+ if (simdMode == SimdMode.AutoDetect)
+ {
+ simdMode = DetectSimdMode();
+ }
- if (numBytes < 0 || numBytes > input.Length)
- {
- throw new ArgumentOutOfRangeException("numBytes", "The number of bytes to read must be between [0..input.Length]");
- }
+ await WorkStreamsAsync(output, input, simdMode, howManyBytesToProcessAtTime);
+ }
- if (output.Length < numBytes)
- {
- throw new ArgumentOutOfRangeException("output", $"Output byte array should be able to take at least {numBytes}");
- }
+ ///
+ /// Decrypt arbitrary-length byte array (input), writing the resulting byte array to preallocated output buffer.
+ ///
+ /// Since this is symmetric operation, it doesn't really matter if you use Encrypt or Decrypt method
+ /// Output byte array, must have enough bytes
+ /// Input byte array
+ /// Chosen SIMD mode (default is auto-detect)
+ public void DecryptBytes(byte[] output, byte[] input, SimdMode simdMode = SimdMode.AutoDetect)
+ {
+ if (output == null)
+ {
+ throw new ArgumentNullException(nameof(output), "Output cannot be null");
+ }
- uint[] x = new uint[stateLength]; // Working buffer
- byte[] tmp = new byte[processBytesAtTime]; // Temporary buffer
- int offset = 0;
+ if (input == null)
+ {
+ throw new ArgumentNullException(nameof(input), "Input cannot be null");
+ }
- while (numBytes > 0)
- {
- // Copy state to working buffer
- Buffer.BlockCopy(this.state, 0, x, 0, stateLength * sizeof(uint));
+ if (simdMode == SimdMode.AutoDetect)
+ {
+ simdMode = DetectSimdMode();
+ }
- for (int i = 0; i < 10; i++)
- {
- QuarterRound(x, 0, 4, 8, 12);
- QuarterRound(x, 1, 5, 9, 13);
- QuarterRound(x, 2, 6, 10, 14);
- QuarterRound(x, 3, 7, 11, 15);
-
- QuarterRound(x, 0, 5, 10, 15);
- QuarterRound(x, 1, 6, 11, 12);
- QuarterRound(x, 2, 7, 8, 13);
- QuarterRound(x, 3, 4, 9, 14);
- }
+ WorkBytes(output, input, input.Length, simdMode);
+ }
- for (int i = 0; i < stateLength; i++)
- {
- Util.ToBytes(tmp, Util.Add(x[i], this.state[i]), 4 * i);
- }
+ ///
+ /// Decrypt arbitrary-length byte array (input), writing the resulting byte array that is allocated by method.
+ ///
+ /// Since this is symmetric operation, it doesn't really matter if you use Encrypt or Decrypt method
+ /// Input byte array
+ /// Number of bytes to encrypt
+ /// Chosen SIMD mode (default is auto-detect)
+ /// Byte array that contains decrypted bytes
+ public byte[] DecryptBytes(byte[] input, int numBytes, SimdMode simdMode = SimdMode.AutoDetect)
+ {
+ if (input == null)
+ {
+ throw new ArgumentNullException(nameof(input), "Input cannot be null");
+ }
- this.state[12] = Util.AddOne(state[12]);
- if (this.state[12] <= 0)
- {
- /* Stopping at 2^70 bytes per nonce is the user's responsibility */
- this.state[13] = Util.AddOne(state[13]);
- }
+ if (numBytes < 0 || numBytes > input.Length)
+ {
+ throw new ArgumentOutOfRangeException(nameof(numBytes), "The number of bytes to read must be between [0..input.Length]");
+ }
- // In case these are last bytes
- if (numBytes <= processBytesAtTime)
- {
- for (int i = 0; i < numBytes; i++)
- {
- output[i + offset] = (byte)(input[i + offset] ^ tmp[i]);
- }
+ if (simdMode == SimdMode.AutoDetect)
+ {
+ simdMode = DetectSimdMode();
+ }
- return;
- }
+ byte[] returnArray = new byte[numBytes];
+ WorkBytes(returnArray, input, numBytes, simdMode);
+ return returnArray;
+ }
- for (int i = 0; i < processBytesAtTime; i++)
- {
- output[i + offset] = (byte)(input[i + offset] ^ tmp[i]);
- }
+ ///
+ /// Decrypt arbitrary-length byte array (input), writing the resulting byte array that is allocated by method.
+ ///
+ /// Since this is symmetric operation, it doesn't really matter if you use Encrypt or Decrypt method
+ /// Input byte array
+ /// Chosen SIMD mode (default is auto-detect)
+ /// Byte array that contains decrypted bytes
+ public byte[] DecryptBytes(byte[] input, SimdMode simdMode = SimdMode.AutoDetect)
+ {
+ if (input == null)
+ {
+ throw new ArgumentNullException(nameof(input), "Input cannot be null");
+ }
- numBytes -= processBytesAtTime;
- offset += processBytesAtTime;
- }
+ if (simdMode == SimdMode.AutoDetect)
+ {
+ simdMode = DetectSimdMode();
}
- ///
- /// The ChaCha Quarter Round operation. It operates on four 32-bit unsigned integers within the given buffer at indices a, b, c, and d.
- ///
- ///
- /// The ChaCha state does not have four integer numbers: it has 16. So the quarter-round operation works on only four of them -- hence the name. Each quarter round operates on four predetermined numbers in the ChaCha state.
- /// See ChaCha20 Spec Sections 2.1 - 2.2.
- ///
- /// A ChaCha state (vector). Must contain 16 elements.
- /// Index of the first number
- /// Index of the second number
- /// Index of the third number
- /// Index of the fourth number
- private static void QuarterRound(uint[] x, uint a, uint b, uint c, uint d)
+ byte[] returnArray = new byte[input.Length];
+ WorkBytes(returnArray, input, input.Length, simdMode);
+ return returnArray;
+ }
+
+ ///
+ /// Decrypt UTF8 byte array to string.
+ ///
+ /// Here you can NOT swap encrypt and decrypt methods, because of bytes-string transform
+ /// Byte array
+ /// Chosen SIMD mode (default is auto-detect)
+ /// Byte array that contains encrypted bytes
+ public string DecryptUTF8ByteArray(byte[] input, SimdMode simdMode = SimdMode.AutoDetect)
+ {
+ if (input == null)
{
- x[a] = Util.Add(x[a], x[b]);
- x[d] = Util.Rotate(Util.XOr(x[d], x[a]), 16);
+ throw new ArgumentNullException(nameof(input), "Input cannot be null");
+ }
+
+ if (simdMode == SimdMode.AutoDetect)
+ {
+ simdMode = DetectSimdMode();
+ }
+
+ byte[] tempArray = new byte[input.Length];
+
+ WorkBytes(tempArray, input, input.Length, simdMode);
+ return System.Text.Encoding.UTF8.GetString(tempArray);
+ }
+
+ #endregion // Decryption methods
+
+ private void WorkStreams(Stream output, Stream input, SimdMode simdMode, int howManyBytesToProcessAtTime = 1024)
+ {
+ int readBytes;
- x[c] = Util.Add(x[c], x[d]);
- x[b] = Util.Rotate(Util.XOr(x[b], x[c]), 12);
+ byte[] inputBuffer = new byte[howManyBytesToProcessAtTime];
+ byte[] outputBuffer = new byte[howManyBytesToProcessAtTime];
- x[a] = Util.Add(x[a], x[b]);
- x[d] = Util.Rotate(Util.XOr(x[d], x[a]), 8);
+ while ((readBytes = input.Read(inputBuffer, 0, howManyBytesToProcessAtTime)) > 0)
+ {
+ // Encrypt or decrypt
+ WorkBytes(output: outputBuffer, input: inputBuffer, numBytes: readBytes, simdMode);
- x[c] = Util.Add(x[c], x[d]);
- x[b] = Util.Rotate(Util.XOr(x[b], x[c]), 7);
+ // Write buffer
+ output.Write(outputBuffer, 0, readBytes);
}
+ }
- #region Destructor and Disposer
+ private async Task WorkStreamsAsync(Stream output, Stream input, SimdMode simdMode, int howManyBytesToProcessAtTime = 1024)
+ {
+ byte[] readBytesBuffer = new byte[howManyBytesToProcessAtTime];
+ byte[] writeBytesBuffer = new byte[howManyBytesToProcessAtTime];
+ int howManyBytesWereRead = await input.ReadAsync(readBytesBuffer.AsMemory(0, howManyBytesToProcessAtTime));
- ///
- /// Clear and dispose of the internal state. The finalizer is only called if Dispose() was never called on this cipher.
- ///
- ~ChaCha20()
+ while (howManyBytesWereRead > 0)
{
- Dispose(false);
+ // Encrypt or decrypt
+ WorkBytes(output: writeBytesBuffer, input: readBytesBuffer, numBytes: howManyBytesWereRead, simdMode);
+
+ // Write
+ await output.WriteAsync(writeBytesBuffer.AsMemory(0, howManyBytesWereRead));
+
+ // Read more
+ howManyBytesWereRead = await input.ReadAsync(readBytesBuffer.AsMemory(0, howManyBytesToProcessAtTime));
}
+ }
- ///
- /// Clear and dispose of the internal state. Also request the GC not to call the finalizer, because all cleanup has been taken care of.
- ///
- public void Dispose()
+ ///
+ /// Encrypt or decrypt an arbitrary-length byte array (input), writing the resulting byte array to the output buffer. The number of bytes to read from the input buffer is determined by numBytes.
+ ///
+ /// Output byte array
+ /// Input byte array
+ /// How many bytes to process
+ /// Chosen SIMD mode (default is auto-detect)
+ private void WorkBytes(byte[] output, byte[] input, int numBytes, SimdMode simdMode)
+ {
+ if (isDisposed)
{
- Dispose(true);
- /*
- * The Garbage Collector does not need to invoke the finalizer because Dispose(bool) has already done all the cleanup needed.
- */
- GC.SuppressFinalize(this);
+ throw new ObjectDisposedException("state", "The ChaCha state has been disposed");
}
- ///
- /// This method should only be invoked from Dispose() or the finalizer. This handles the actual cleanup of the resources.
- ///
- ///
- /// Should be true if called by Dispose(); false if called by the finalizer
- ///
- private void Dispose(bool disposing)
+ uint[] x = new uint[stateLength]; // Working buffer
+ byte[] tmp = new byte[processBytesAtTime]; // Temporary buffer
+ int offset = 0;
+
+ int howManyFullLoops = numBytes / processBytesAtTime;
+ int tailByteCount = numBytes - howManyFullLoops * processBytesAtTime;
+
+ for (int loop = 0; loop < howManyFullLoops; loop++)
{
- if (!isDisposed)
+ UpdateStateAndGenerateTemporaryBuffer(state, x, tmp);
+
+ if (simdMode == SimdMode.V512)
+ {
+ // 1 x 64 bytes
+ Vector512 inputV = Vector512.Create(input, offset);
+ Vector512 tmpV = Vector512.Create(tmp, 0);
+ Vector512 outputV = inputV ^ tmpV;
+ outputV.CopyTo(output, offset);
+ }
+ else if (simdMode == SimdMode.V256)
+ {
+ // 2 x 32 bytes
+ Vector256 inputV = Vector256.Create(input, offset);
+ Vector256 tmpV = Vector256.Create(tmp, 0);
+ Vector256 outputV = inputV ^ tmpV;
+ outputV.CopyTo(output, offset);
+
+ inputV = Vector256.Create(input, offset + 32);
+ tmpV = Vector256.Create(tmp, 32);
+ outputV = inputV ^ tmpV;
+ outputV.CopyTo(output, offset + 32);
+ }
+ else if (simdMode == SimdMode.V128)
+ {
+ // 4 x 16 bytes
+ Vector128 inputV = Vector128.Create(input, offset);
+ Vector128 tmpV = Vector128.Create(tmp, 0);
+ Vector128 outputV = inputV ^ tmpV;
+ outputV.CopyTo(output, offset);
+
+ inputV = Vector128.Create(input, offset + 16);
+ tmpV = Vector128.Create(tmp, 16);
+ outputV = inputV ^ tmpV;
+ outputV.CopyTo(output, offset + 16);
+
+ inputV = Vector128.Create(input, offset + 32);
+ tmpV = Vector128.Create(tmp, 32);
+ outputV = inputV ^ tmpV;
+ outputV.CopyTo(output, offset + 32);
+
+ inputV = Vector128.Create(input, offset + 48);
+ tmpV = Vector128.Create(tmp, 48);
+ outputV = inputV ^ tmpV;
+ outputV.CopyTo(output, offset + 48);
+ }
+ else
{
- if (disposing)
+ for (int i = 0; i < processBytesAtTime; i += 4)
{
- /* Cleanup managed objects by calling their Dispose() methods */
+ // Small unroll
+ int start = i + offset;
+ output[start] = (byte)(input[start] ^ tmp[i]);
+ output[start + 1] = (byte)(input[start + 1] ^ tmp[i + 1]);
+ output[start + 2] = (byte)(input[start + 2] ^ tmp[i + 2]);
+ output[start + 3] = (byte)(input[start + 3] ^ tmp[i + 3]);
}
+ }
+
+ offset += processBytesAtTime;
+ }
+
+ // In case there are some bytes left
+ if (tailByteCount > 0)
+ {
+ UpdateStateAndGenerateTemporaryBuffer(state, x, tmp);
- /* Cleanup any unmanaged objects here */
- Array.Clear(state, 0, stateLength);
+ for (int i = 0; i < tailByteCount; i++)
+ {
+ output[i + offset] = (byte)(input[i + offset] ^ tmp[i]);
}
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static void UpdateStateAndGenerateTemporaryBuffer(uint[] stateToModify, uint[] workingBuffer, byte[] temporaryBuffer)
+ {
+ // Copy state to working buffer
+ Buffer.BlockCopy(stateToModify, 0, workingBuffer, 0, stateLength * sizeof(uint));
- isDisposed = true;
+ for (int i = 0; i < 10; i++)
+ {
+ QuarterRound(workingBuffer, 0, 4, 8, 12);
+ QuarterRound(workingBuffer, 1, 5, 9, 13);
+ QuarterRound(workingBuffer, 2, 6, 10, 14);
+ QuarterRound(workingBuffer, 3, 7, 11, 15);
+
+ QuarterRound(workingBuffer, 0, 5, 10, 15);
+ QuarterRound(workingBuffer, 1, 6, 11, 12);
+ QuarterRound(workingBuffer, 2, 7, 8, 13);
+ QuarterRound(workingBuffer, 3, 4, 9, 14);
+ }
+
+ for (int i = 0; i < stateLength; i++)
+ {
+ Util.ToBytes(temporaryBuffer, Util.Add(workingBuffer[i], stateToModify[i]), 4 * i);
}
- #endregion // Destructor and Disposer
+ stateToModify[12] = Util.AddOne(stateToModify[12]);
+ if (stateToModify[12] <= 0)
+ {
+ /* Stopping at 2^70 bytes per nonce is the user's responsibility */
+ stateToModify[13] = Util.AddOne(stateToModify[13]);
+ }
}
///
- /// Utilities that are used during compression
+ /// The ChaCha Quarter Round operation. It operates on four 32-bit unsigned integers within the given buffer at indices a, b, c, and d.
///
- public static class Util
+ ///
+ /// The ChaCha state does not have four integer numbers: it has 16. So the quarter-round operation works on only four of them -- hence the name. Each quarter round operates on four predetermined numbers in the ChaCha state.
+ /// See ChaCha20 Spec Sections 2.1 - 2.2.
+ ///
+ /// A ChaCha state (vector). Must contain 16 elements.
+ /// Index of the first number
+ /// Index of the second number
+ /// Index of the third number
+ /// Index of the fourth number
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static void QuarterRound(uint[] x, uint a, uint b, uint c, uint d)
{
- ///
- /// n-bit left rotation operation (towards the high bits) for 32-bit integers.
- ///
- ///
- ///
- /// The result of (v LEFTSHIFT c)
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static uint Rotate(uint v, int c)
+ x[a] = Util.Add(x[a], x[b]);
+ x[d] = Util.Rotate(Util.XOr(x[d], x[a]), 16);
+
+ x[c] = Util.Add(x[c], x[d]);
+ x[b] = Util.Rotate(Util.XOr(x[b], x[c]), 12);
+
+ x[a] = Util.Add(x[a], x[b]);
+ x[d] = Util.Rotate(Util.XOr(x[d], x[a]), 8);
+
+ x[c] = Util.Add(x[c], x[d]);
+ x[b] = Util.Rotate(Util.XOr(x[b], x[c]), 7);
+ }
+
+ #region Destructor and Disposer
+
+ ///
+ /// Clear and dispose of the internal state. The finalizer is only called if Dispose() was never called on this cipher.
+ ///
+ ~ChaCha20()
+ {
+ Dispose(false);
+ }
+
+ ///
+ /// Clear and dispose of the internal state. Also request the GC not to call the finalizer, because all cleanup has been taken care of.
+ ///
+ public void Dispose()
+ {
+ Dispose(true);
+ /*
+ * The Garbage Collector does not need to invoke the finalizer because Dispose(bool) has already done all the cleanup needed.
+ */
+ GC.SuppressFinalize(this);
+ }
+
+ ///
+ /// This method should only be invoked from Dispose() or the finalizer. This handles the actual cleanup of the resources.
+ ///
+ ///
+ /// Should be true if called by Dispose(); false if called by the finalizer
+ ///
+ private void Dispose(bool disposing)
+ {
+ if (!isDisposed)
{
- unchecked
+ if (disposing)
{
- return (v << c) | (v >> (32 - c));
+ /* Cleanup managed objects by calling their Dispose() methods */
}
+
+ /* Cleanup any unmanaged objects here */
+ Array.Clear(state, 0, stateLength);
}
- ///
- /// Unchecked integer exclusive or (XOR) operation.
- ///
- ///
- ///
- /// The result of (v XOR w)
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static uint XOr(uint v, uint w)
- {
- return unchecked(v ^ w);
- }
-
- ///
- /// Unchecked integer addition. The ChaCha spec defines certain operations to use 32-bit unsigned integer addition modulo 2^32.
- ///
- ///
- /// See ChaCha20 Spec Section 2.1.
- ///
- ///
- ///
- /// The result of (v + w) modulo 2^32
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static uint Add(uint v, uint w)
- {
- return unchecked(v + w);
- }
-
- ///
- /// Add 1 to the input parameter using unchecked integer addition. The ChaCha spec defines certain operations to use 32-bit unsigned integer addition modulo 2^32.
- ///
- ///
- /// See ChaCha20 Spec Section 2.1.
- ///
- ///
- /// The result of (v + 1) modulo 2^32
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static uint AddOne(uint v)
- {
- return unchecked(v + 1);
- }
-
- ///
- /// Convert four bytes of the input buffer into an unsigned 32-bit integer, beginning at the inputOffset.
- ///
- ///
- ///
- /// An unsigned 32-bit integer
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static uint U8To32Little(byte[] p, int inputOffset)
- {
- unchecked
- {
- return ((uint)p[inputOffset]
- | ((uint)p[inputOffset + 1] << 8)
- | ((uint)p[inputOffset + 2] << 16)
- | ((uint)p[inputOffset + 3] << 24));
- }
+ isDisposed = true;
+ }
+
+ #endregion // Destructor and Disposer
+}
+
+///
+/// Utilities that are used during compression
+///
+public static class Util
+{
+ ///
+ /// n-bit left rotation operation (towards the high bits) for 32-bit integers.
+ ///
+ ///
+ ///
+ /// The result of (v LEFTSHIFT c)
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static uint Rotate(uint v, int c)
+ {
+ unchecked
+ {
+ return v << c | v >> 32 - c;
+ }
+ }
+
+ ///
+ /// Unchecked integer exclusive or (XOR) operation.
+ ///
+ ///
+ ///
+ /// The result of (v XOR w)
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static uint XOr(uint v, uint w)
+ {
+ return unchecked(v ^ w);
+ }
+
+ ///
+ /// Unchecked integer addition. The ChaCha spec defines certain operations to use 32-bit unsigned integer addition modulo 2^32.
+ ///
+ ///
+ /// See ChaCha20 Spec Section 2.1.
+ ///
+ ///
+ ///
+ /// The result of (v + w) modulo 2^32
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static uint Add(uint v, uint w)
+ {
+ return unchecked(v + w);
+ }
+
+ ///
+ /// Add 1 to the input parameter using unchecked integer addition. The ChaCha spec defines certain operations to use 32-bit unsigned integer addition modulo 2^32.
+ ///
+ ///
+ /// See ChaCha20 Spec Section 2.1.
+ ///
+ ///
+ /// The result of (v + 1) modulo 2^32
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static uint AddOne(uint v)
+ {
+ return unchecked(v + 1);
+ }
+
+ ///
+ /// Convert four bytes of the input buffer into an unsigned 32-bit integer, beginning at the inputOffset.
+ ///
+ ///
+ ///
+ /// An unsigned 32-bit integer
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static uint U8To32Little(byte[] p, int inputOffset)
+ {
+ unchecked
+ {
+ return p[inputOffset]
+ | (uint)p[inputOffset + 1] << 8
+ | (uint)p[inputOffset + 2] << 16
+ | (uint)p[inputOffset + 3] << 24;
}
+ }
- ///
- /// Serialize the input integer into the output buffer. The input integer will be split into 4 bytes and put into four sequential places in the output buffer, starting at the outputOffset.
- ///
- ///
- ///
- ///
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static void ToBytes(byte[] output, uint input, int outputOffset)
+ ///
+ /// Serialize the input integer into the output buffer. The input integer will be split into 4 bytes and put into four sequential places in the output buffer, starting at the outputOffset.
+ ///
+ ///
+ ///
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void ToBytes(byte[] output, uint input, int outputOffset)
+ {
+ unchecked
{
- unchecked
- {
- output[outputOffset] = (byte)input;
- output[outputOffset + 1] = (byte)(input >> 8);
- output[outputOffset + 2] = (byte)(input >> 16);
- output[outputOffset + 3] = (byte)(input >> 24);
- }
+ output[outputOffset] = (byte)input;
+ output[outputOffset + 1] = (byte)(input >> 8);
+ output[outputOffset + 2] = (byte)(input >> 16);
+ output[outputOffset + 3] = (byte)(input >> 24);
}
}
-}
+}
\ No newline at end of file
diff --git a/ExtensionMethods.cs b/ExtensionMethods.cs
index 2bd0784..a3376c1 100644
--- a/ExtensionMethods.cs
+++ b/ExtensionMethods.cs
@@ -1,5 +1,5 @@
-using CSChaCha20;
-using System;
+using System;
+using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
@@ -50,15 +50,29 @@ public static bool SequenceCompare(this byte[]? a, byte[]? b)
if (a.Length != b.Length) return false;
return a.SequenceEqual(b);
}
+
+ public static string TimeFormat(this TimeSpan timeSpan)
+ {
+ return string.Format("{0}{1}{2}{3}{4}",
+ timeSpan.Days > 0 ? $"{timeSpan.Days}天" : "",
+ timeSpan.Hours > 0 ? $"{timeSpan.Hours}小时" : "",
+ timeSpan.Minutes > 0 ? $"{timeSpan.Minutes}分钟" : "",
+ timeSpan.Seconds > 0 ? $"{timeSpan.Seconds}秒" : "",
+ timeSpan.Milliseconds > 0 ? $"{timeSpan.Milliseconds}毫秒" : "低于1毫秒").Trim();
+ }
+
public static async Task VerifyHash(this Stream stream, byte[] hash)
{
- Console.WriteLine("校验中...");
+ Console.WriteLine("校验中...");
+ Stopwatch stopwatch = new();
+ stopwatch.Restart();
byte[] streamHash = await stream.SHA256HashAsync();
+ stopwatch.Stop();
if (!streamHash.SequenceCompare(hash))
{
- throw new Exception($"校验失败!{Environment.NewLine}(A)SHA256:{streamHash.BytesToHexString()}{Environment.NewLine}(B)SHA256:{hash.BytesToHexString()}");
+ throw new Exception($"校验失败!耗时{stopwatch.Elapsed.TimeFormat()}{Environment.NewLine}(A)SHA256:{streamHash.BytesToHexString()}{Environment.NewLine}(B)SHA256:{hash.BytesToHexString()}");
}
- Console.WriteLine("校验完成。");
+ Console.WriteLine($"校验完成。耗时{stopwatch.Elapsed.TimeFormat()}");
}
public static byte[] RandomBytes(int length)
{
@@ -66,15 +80,11 @@ public static byte[] RandomBytes(int length)
}
public static byte[] StreamRead(this Stream stream, long a, long b)
{
- if ((b - a) > int.MaxValue || stream.Length < 4)
- {
- throw new OverflowException();
- }
+ if ((b - a) > int.MaxValue || stream.Length < 4) throw new OverflowException();
long save = stream.Position;
- int DataLength = (int)(b - a);
byte[] ReadData;
stream.Seek(a, SeekOrigin.Begin);
- ReadData = stream.StreamRead(DataLength);
+ ReadData = stream.StreamRead((int)(b - a));
stream.Seek(save, SeekOrigin.Begin);
return ReadData;
}
@@ -128,7 +138,7 @@ public static void CoverWrite(string s)
public static async Task Encrypto(this Stream data, Stream encrypted, byte[] key, byte[] iv)
{
int bytesRead;
- byte[] buffer = new byte[1024 * 1024 * 8];
+ byte[] buffer = new byte[1024 * 1024 * 4];
ChaCha20 Encrypto = new(key, iv, 0);
while ((bytesRead = await data.ReadAsync(buffer)) != 0)
{
diff --git a/Program.cs b/Program.cs
index 01ae5a9..7d42e65 100644
--- a/Program.cs
+++ b/Program.cs
@@ -1,4 +1,5 @@
using System;
+using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
@@ -69,6 +70,8 @@ internal static CryptoKey Deserialize(Stream stream)
public static async Task Encrypto(Stream data, Stream cryptodata, byte[] key)
{
Console.Write("加密中...");
+ Stopwatch stopwatch = new();
+ stopwatch.Restart();
long SaveDataPosition = data.Position;
cryptodata.SetLength(0);
@@ -82,12 +85,15 @@ public static async Task Encrypto(Stream data, Stream cryptodata, byte[] key)
cryptodata.Seek(0, SeekOrigin.Begin);
data.Seek(SaveDataPosition, SeekOrigin.Begin);
+ stopwatch.Stop();
Console.WriteLine();
- Console.WriteLine("加密完成。");
+ Console.WriteLine($"加密完成。耗时{ stopwatch.Elapsed.TimeFormat() }");
}
public static async Task Decrypto(Stream cryptodata, Stream data, byte[] key)
{
Console.Write("解密中...");
+ Stopwatch stopwatch = new();
+ stopwatch.Restart();
long SaveCryptoDataPosition = cryptodata.Position;
data.SetLength(0);
@@ -97,8 +103,9 @@ public static async Task Decrypto(Stream cryptodata, Stream data, byte[] key)
data.Seek(0, SeekOrigin.Begin);
cryptodata.Seek(SaveCryptoDataPosition, SeekOrigin.Begin);
+ stopwatch.Stop();
Console.WriteLine();
- Console.WriteLine("解密完成。");
+ Console.WriteLine($"解密完成。耗时{ stopwatch.Elapsed.TimeFormat() }");
}
}
public static void WriteCryptoKey(string filePath, CryptoKey key)
@@ -142,7 +149,7 @@ static async Task Main(string[] args)
try
{
FileInfo? file;
- if (!args.Any())
+ if (args.Length == 0)
{
FileInfo[] fileInfos = new DirectoryInfo(Environment.CurrentDirectory).GetFiles();
fileInfos = fileInfos.Where(i => i.FullName != Environment.ProcessPath).ToArray();
@@ -207,10 +214,13 @@ static async Task Main(string[] args)
byte[] EncryptFileHash;
using (FileStream EncryptFile = new(EncryptFilePath, FileMode.OpenOrCreate))
{
+ Stopwatch stopwatch = new();
+ stopwatch.Restart();
await Encrypto(InputFile, EncryptFile, Key);
Console.WriteLine("创建校验记录中...");
EncryptFileHash = await EncryptFile.SHA256HashAsync();
- Console.WriteLine("创建校验记录完成。");
+ stopwatch.Stop();
+ Console.WriteLine($"创建校验记录完成。耗时{stopwatch.Elapsed.TimeFormat()}");
}
cryptoKey = new()
{
@@ -221,6 +231,7 @@ static async Task Main(string[] args)
WriteCryptoKey($"{EncryptFilePath}.aodk", cryptoKey);
break;
}
+ Console.ReadKey();
return 0;
}
catch (Exception ex)