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)