2022-08-27 23:01:28 +08:00
|
|
|
|
using System;
|
2022-10-02 18:31:08 +08:00
|
|
|
|
using System.IO;
|
2022-08-28 10:50:52 +08:00
|
|
|
|
using System.Runtime.CompilerServices;
|
2022-10-02 18:31:08 +08:00
|
|
|
|
using System.Security.Cryptography;
|
2026-04-04 00:32:07 +02:00
|
|
|
|
using System.Threading;
|
|
|
|
|
|
using System.Threading.Tasks;
|
2022-08-27 23:01:28 +08:00
|
|
|
|
|
2022-08-28 13:18:07 +08:00
|
|
|
|
namespace MinecraftClient.Crypto
|
2022-08-27 23:01:28 +08:00
|
|
|
|
{
|
2022-08-28 13:18:07 +08:00
|
|
|
|
public class AesCfb8Stream : Stream
|
2022-08-27 23:01:28 +08:00
|
|
|
|
{
|
2022-12-14 14:45:51 +08:00
|
|
|
|
public const int blockSize = 16;
|
2022-08-27 23:01:28 +08:00
|
|
|
|
|
2022-08-28 10:50:52 +08:00
|
|
|
|
private readonly Aes? Aes = null;
|
2022-08-28 13:18:07 +08:00
|
|
|
|
private readonly FastAes? FastAes = null;
|
2022-08-27 23:01:28 +08:00
|
|
|
|
|
|
|
|
|
|
private bool inStreamEnded = false;
|
|
|
|
|
|
|
2022-10-02 18:31:08 +08:00
|
|
|
|
private readonly byte[] ReadStreamIV = new byte[16];
|
|
|
|
|
|
private readonly byte[] WriteStreamIV = new byte[16];
|
2022-08-27 23:01:28 +08:00
|
|
|
|
|
2022-08-28 14:57:44 +08:00
|
|
|
|
public Stream BaseStream { get; set; }
|
|
|
|
|
|
|
2022-08-28 13:18:07 +08:00
|
|
|
|
public AesCfb8Stream(Stream stream, byte[] key)
|
2022-08-27 23:01:28 +08:00
|
|
|
|
{
|
|
|
|
|
|
BaseStream = stream;
|
|
|
|
|
|
|
2022-08-28 15:32:09 +08:00
|
|
|
|
if (FastAes.IsSupported())
|
2022-08-28 13:18:07 +08:00
|
|
|
|
FastAes = new FastAes(key);
|
2022-08-28 10:50:52 +08:00
|
|
|
|
else
|
2022-08-28 13:18:07 +08:00
|
|
|
|
{
|
|
|
|
|
|
Aes = Aes.Create();
|
|
|
|
|
|
Aes.BlockSize = 128;
|
|
|
|
|
|
Aes.KeySize = 128;
|
|
|
|
|
|
Aes.Key = key;
|
|
|
|
|
|
Aes.Mode = CipherMode.ECB;
|
|
|
|
|
|
Aes.Padding = PaddingMode.None;
|
|
|
|
|
|
}
|
2022-08-27 23:01:28 +08:00
|
|
|
|
|
|
|
|
|
|
Array.Copy(key, ReadStreamIV, 16);
|
|
|
|
|
|
Array.Copy(key, WriteStreamIV, 16);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public override bool CanRead
|
|
|
|
|
|
{
|
|
|
|
|
|
get { return true; }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public override bool CanSeek
|
|
|
|
|
|
{
|
|
|
|
|
|
get { return false; }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public override bool CanWrite
|
|
|
|
|
|
{
|
|
|
|
|
|
get { return true; }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public override void Flush()
|
|
|
|
|
|
{
|
|
|
|
|
|
BaseStream.Flush();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-04 00:32:07 +02:00
|
|
|
|
public override Task FlushAsync(CancellationToken cancellationToken)
|
|
|
|
|
|
{
|
|
|
|
|
|
return BaseStream.FlushAsync(cancellationToken);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-08-27 23:01:28 +08:00
|
|
|
|
public override long Length
|
|
|
|
|
|
{
|
|
|
|
|
|
get { throw new NotSupportedException(); }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public override long Position
|
|
|
|
|
|
{
|
|
|
|
|
|
get
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new NotSupportedException();
|
|
|
|
|
|
}
|
|
|
|
|
|
set
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new NotSupportedException();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public override int ReadByte()
|
|
|
|
|
|
{
|
2022-08-28 13:18:07 +08:00
|
|
|
|
if (inStreamEnded)
|
|
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
|
|
|
|
int inputBuf = BaseStream.ReadByte();
|
|
|
|
|
|
if (inputBuf == -1)
|
|
|
|
|
|
{
|
|
|
|
|
|
inStreamEnded = true;
|
|
|
|
|
|
return -1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Span<byte> blockOutput = stackalloc byte[blockSize];
|
refactor: convert == null / != null to is null / is not null in 31 files
Replace old-style null comparisons with modern C# pattern matching
syntax across Commands, Protocol, Mapping, ChatBots, Physics,
Inventory, Logger, CommandHandler, Scripting, Crypto, and other modules.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-24 00:42:59 +00:00
|
|
|
|
if (FastAes is not null)
|
2022-08-28 13:18:07 +08:00
|
|
|
|
FastAes.EncryptEcb(ReadStreamIV, blockOutput);
|
|
|
|
|
|
else
|
|
|
|
|
|
Aes!.EncryptEcb(ReadStreamIV, blockOutput, PaddingMode.None);
|
|
|
|
|
|
|
|
|
|
|
|
// Shift left
|
|
|
|
|
|
Array.Copy(ReadStreamIV, 1, ReadStreamIV, 0, blockSize - 1);
|
|
|
|
|
|
ReadStreamIV[blockSize - 1] = (byte)inputBuf;
|
|
|
|
|
|
|
|
|
|
|
|
return (byte)(blockOutput[0] ^ inputBuf);
|
2022-08-27 23:01:28 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-04 00:32:07 +02:00
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
|
|
|
|
private void EncryptBlock(ReadOnlySpan<byte> blockInput, Span<byte> blockOutput)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (FastAes is not null)
|
|
|
|
|
|
FastAes.EncryptEcb(blockInput, blockOutput);
|
|
|
|
|
|
else
|
|
|
|
|
|
Aes!.EncryptEcb(blockInput, blockOutput, PaddingMode.None);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-08-28 10:50:52 +08:00
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
2022-08-27 23:01:28 +08:00
|
|
|
|
public override int Read(byte[] buffer, int outOffset, int required)
|
|
|
|
|
|
{
|
2022-08-28 13:18:07 +08:00
|
|
|
|
if (inStreamEnded)
|
2022-08-27 23:01:28 +08:00
|
|
|
|
return 0;
|
|
|
|
|
|
|
2022-12-14 14:45:51 +08:00
|
|
|
|
Span<byte> blockOutput = stackalloc byte[blockSize];
|
2022-08-28 13:18:07 +08:00
|
|
|
|
|
2022-08-27 23:01:28 +08:00
|
|
|
|
byte[] inputBuf = new byte[blockSize + required];
|
|
|
|
|
|
Array.Copy(ReadStreamIV, inputBuf, blockSize);
|
|
|
|
|
|
|
|
|
|
|
|
for (int readed = 0, curRead; readed < required; readed += curRead)
|
|
|
|
|
|
{
|
|
|
|
|
|
curRead = BaseStream.Read(inputBuf, blockSize + readed, required - readed);
|
|
|
|
|
|
if (curRead == 0)
|
|
|
|
|
|
{
|
2022-08-28 13:18:07 +08:00
|
|
|
|
inStreamEnded = true;
|
2022-08-27 23:01:28 +08:00
|
|
|
|
return readed;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-08-28 13:18:07 +08:00
|
|
|
|
int processEnd = readed + curRead;
|
2026-04-04 00:32:07 +02:00
|
|
|
|
for (int idx = readed; idx < processEnd; idx++)
|
2022-08-27 23:01:28 +08:00
|
|
|
|
{
|
2026-04-04 00:32:07 +02:00
|
|
|
|
ReadOnlySpan<byte> blockInput = new(inputBuf, idx, blockSize);
|
|
|
|
|
|
EncryptBlock(blockInput, blockOutput);
|
|
|
|
|
|
buffer[outOffset + idx] = (byte)(blockOutput[0] ^ inputBuf[idx + blockSize]);
|
2022-08-28 13:18:07 +08:00
|
|
|
|
}
|
2022-08-27 23:01:28 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Array.Copy(inputBuf, required, ReadStreamIV, 0, blockSize);
|
|
|
|
|
|
|
|
|
|
|
|
return required;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-08-28 13:18:07 +08:00
|
|
|
|
public override long Seek(long offset, SeekOrigin origin)
|
2022-08-27 23:01:28 +08:00
|
|
|
|
{
|
|
|
|
|
|
throw new NotSupportedException();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public override void SetLength(long value)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new NotSupportedException();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public override void WriteByte(byte b)
|
|
|
|
|
|
{
|
2022-08-28 13:18:07 +08:00
|
|
|
|
Span<byte> blockOutput = stackalloc byte[blockSize];
|
|
|
|
|
|
|
2026-04-04 00:32:07 +02:00
|
|
|
|
EncryptBlock(WriteStreamIV, blockOutput);
|
2022-08-28 13:18:07 +08:00
|
|
|
|
|
|
|
|
|
|
byte outputBuf = (byte)(blockOutput[0] ^ b);
|
|
|
|
|
|
|
|
|
|
|
|
BaseStream.WriteByte(outputBuf);
|
|
|
|
|
|
|
|
|
|
|
|
// Shift left
|
|
|
|
|
|
Array.Copy(WriteStreamIV, 1, WriteStreamIV, 0, blockSize - 1);
|
|
|
|
|
|
WriteStreamIV[blockSize - 1] = outputBuf;
|
2022-08-27 23:01:28 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-08-28 13:18:07 +08:00
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
2022-08-27 23:01:28 +08:00
|
|
|
|
public override void Write(byte[] input, int offset, int required)
|
|
|
|
|
|
{
|
|
|
|
|
|
byte[] outputBuf = new byte[blockSize + required];
|
|
|
|
|
|
Array.Copy(WriteStreamIV, outputBuf, blockSize);
|
|
|
|
|
|
|
|
|
|
|
|
Span<byte> blockOutput = stackalloc byte[blockSize];
|
|
|
|
|
|
for (int wirtten = 0; wirtten < required; ++wirtten)
|
|
|
|
|
|
{
|
|
|
|
|
|
ReadOnlySpan<byte> blockInput = new(outputBuf, wirtten, blockSize);
|
2026-04-04 00:32:07 +02:00
|
|
|
|
EncryptBlock(blockInput, blockOutput);
|
2022-08-27 23:01:28 +08:00
|
|
|
|
outputBuf[blockSize + wirtten] = (byte)(blockOutput[0] ^ input[offset + wirtten]);
|
|
|
|
|
|
}
|
2026-04-04 00:32:07 +02:00
|
|
|
|
BaseStream.Write(outputBuf, blockSize, required);
|
2022-08-27 23:01:28 +08:00
|
|
|
|
|
|
|
|
|
|
Array.Copy(outputBuf, required, WriteStreamIV, 0, blockSize);
|
|
|
|
|
|
}
|
2026-04-04 00:32:07 +02:00
|
|
|
|
|
|
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
|
|
|
|
|
public override async ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (inStreamEnded || buffer.Length == 0)
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
|
|
byte[] inputBuf = new byte[blockSize + buffer.Length];
|
|
|
|
|
|
Array.Copy(ReadStreamIV, inputBuf, blockSize);
|
|
|
|
|
|
|
|
|
|
|
|
for (int readed = 0; readed < buffer.Length;)
|
|
|
|
|
|
{
|
|
|
|
|
|
int curRead = await BaseStream.ReadAsync(inputBuf.AsMemory(blockSize + readed, buffer.Length - readed), cancellationToken);
|
|
|
|
|
|
if (curRead == 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
inStreamEnded = true;
|
|
|
|
|
|
Array.Copy(inputBuf, readed, ReadStreamIV, 0, blockSize);
|
|
|
|
|
|
return readed;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int processEnd = readed + curRead;
|
|
|
|
|
|
DecryptToOutputBuffer(inputBuf, buffer, readed, processEnd);
|
|
|
|
|
|
readed = processEnd;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Array.Copy(inputBuf, buffer.Length, ReadStreamIV, 0, blockSize);
|
|
|
|
|
|
return buffer.Length;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
|
|
|
|
|
public override async ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (buffer.Length == 0)
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
byte[] outputBuf = new byte[blockSize + buffer.Length];
|
|
|
|
|
|
Array.Copy(WriteStreamIV, outputBuf, blockSize);
|
|
|
|
|
|
EncryptToOutputBuffer(buffer, outputBuf);
|
|
|
|
|
|
|
|
|
|
|
|
await BaseStream.WriteAsync(outputBuf.AsMemory(blockSize, buffer.Length), cancellationToken);
|
|
|
|
|
|
Array.Copy(outputBuf, buffer.Length, WriteStreamIV, 0, blockSize);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
|
|
|
|
|
{
|
|
|
|
|
|
return ReadAsync(buffer.AsMemory(offset, count), cancellationToken).AsTask();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
|
|
|
|
|
{
|
|
|
|
|
|
return WriteAsync(buffer.AsMemory(offset, count), cancellationToken).AsTask();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
|
|
|
|
|
private void DecryptToOutputBuffer(byte[] inputBuf, Memory<byte> output, int start, int end)
|
|
|
|
|
|
{
|
|
|
|
|
|
Span<byte> blockOutput = stackalloc byte[blockSize];
|
|
|
|
|
|
for (int idx = start; idx < end; idx++)
|
|
|
|
|
|
{
|
|
|
|
|
|
ReadOnlySpan<byte> blockInput = new(inputBuf, idx, blockSize);
|
|
|
|
|
|
EncryptBlock(blockInput, blockOutput);
|
|
|
|
|
|
output.Span[idx] = (byte)(blockOutput[0] ^ inputBuf[idx + blockSize]);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
|
|
|
|
|
private void EncryptToOutputBuffer(ReadOnlyMemory<byte> input, byte[] outputBuf)
|
|
|
|
|
|
{
|
|
|
|
|
|
Span<byte> blockOutput = stackalloc byte[blockSize];
|
|
|
|
|
|
for (int written = 0; written < input.Length; ++written)
|
|
|
|
|
|
{
|
|
|
|
|
|
ReadOnlySpan<byte> blockInput = new(outputBuf, written, blockSize);
|
|
|
|
|
|
EncryptBlock(blockInput, blockOutput);
|
|
|
|
|
|
outputBuf[blockSize + written] = (byte)(blockOutput[0] ^ input.Span[written]);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2022-08-27 23:01:28 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|