Refactor non-NI AES code

This commit is contained in:
Alex Barney 2019-11-18 18:08:25 -07:00
parent 2752a7c3db
commit 4b2c4d9553
11 changed files with 445 additions and 346 deletions

View file

@ -0,0 +1,41 @@
using System;
using System.Buffers;
using System.Runtime.CompilerServices;
namespace LibHac.Common
{
public readonly ref struct RentedArray<T>
{
// It's faster to create new smaller arrays than rent them
private const int RentThresholdBytes = 512;
private static int RentThresholdElements => RentThresholdBytes / Unsafe.SizeOf<T>();
private readonly Span<T> _span;
public T[] Array { get; }
public Span<T> Span => _span;
public RentedArray(int minimumSize)
{
if (minimumSize >= RentThresholdElements)
{
Array = ArrayPool<T>.Shared.Rent(minimumSize);
}
else
{
Array = new T[minimumSize];
}
_span = Array.AsSpan(0, minimumSize);
}
public void Dispose()
{
// Only return if array was rented
if (_span.Length >= RentThresholdElements)
{
ArrayPool<T>.Shared.Return(Array);
}
}
}
}

View file

@ -86,7 +86,7 @@ namespace LibHac.Crypto2
return new AesCtrCipherNi(key, iv);
}
#endif
return new AesCtrEncryptor(key, iv);
return new AesCtrCipher(key, iv);
}
public static ICipher CreateXtsDecryptor(ReadOnlySpan<byte> key1, ReadOnlySpan<byte> key2,
@ -98,7 +98,7 @@ namespace LibHac.Crypto2
return new AesXtsDecryptorNi(key1, key2, iv);
}
#endif
return new AesXtsCipher(key1, key2, iv, true);
return new AesXtsDecryptor(key1, key2, iv);
}
public static ICipher CreateXtsEncryptor(ReadOnlySpan<byte> key1, ReadOnlySpan<byte> key2,
@ -110,7 +110,7 @@ namespace LibHac.Crypto2
return new AesXtsEncryptorNi(key1, key2, iv);
}
#endif
return new AesXtsCipher(key1, key2, iv, false);
return new AesXtsEncryptor(key1, key2, iv);
}
public static void EncryptEcb128(ReadOnlySpan<byte> input, Span<byte> output, ReadOnlySpan<byte> key,

View file

@ -1,59 +1,37 @@
using System;
using System.Security.Cryptography;
using LibHac.Crypto2.Detail;
namespace LibHac.Crypto2
{
public class AesCbcEncryptor : ICipher
{
private ICryptoTransform _encryptor;
private AesCbcMode _baseCipher;
public AesCbcEncryptor(ReadOnlySpan<byte> key, ReadOnlySpan<byte> iv)
{
Aes aes = Aes.Create();
if (aes == null) throw new CryptographicException("Unable to create AES object");
aes.Key = key.ToArray();
aes.IV = iv.ToArray();
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.None;
_encryptor = aes.CreateEncryptor();
_baseCipher = new AesCbcMode();
_baseCipher.Initialize(key, iv, false);
}
public void Transform(ReadOnlySpan<byte> input, Span<byte> output)
{
var outputBuffer = new byte[input.Length];
_encryptor.TransformBlock(input.ToArray(), 0, input.Length, outputBuffer, 0);
outputBuffer.CopyTo(output);
_baseCipher.Encrypt(input, output);
}
}
public class AesCbcDecryptor : ICipher
{
private ICryptoTransform _decryptor;
private AesCbcMode _baseCipher;
public AesCbcDecryptor(ReadOnlySpan<byte> key, ReadOnlySpan<byte> iv)
{
Aes aes = Aes.Create();
if (aes == null) throw new CryptographicException("Unable to create AES object");
aes.Key = key.ToArray();
aes.IV = iv.ToArray();
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.None;
_decryptor = aes.CreateDecryptor();
_baseCipher = new AesCbcMode();
_baseCipher.Initialize(key, iv, true);
}
public void Transform(ReadOnlySpan<byte> input, Span<byte> output)
{
var outputBuffer = new byte[input.Length];
_decryptor.TransformBlock(input.ToArray(), 0, input.Length, outputBuffer, 0);
outputBuffer.CopyTo(output);
_baseCipher.Decrypt(input, output);
}
}
}

View file

@ -1,69 +1,21 @@
using System;
using System.Buffers;
using System.Buffers.Binary;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using LibHac.Crypto2.Detail;
namespace LibHac.Crypto2
{
public class AesCtrEncryptor : ICipher
public class AesCtrCipher : ICipher
{
private const int BlockSize = 128;
private const int BlockSizeBytes = BlockSize / 8;
private AesCtrMode _baseCipher;
private readonly ICryptoTransform _encryptor;
private readonly byte[] _counter = new byte[0x10];
public AesCtrEncryptor(ReadOnlySpan<byte> key, ReadOnlySpan<byte> iv)
public AesCtrCipher(ReadOnlySpan<byte> key, ReadOnlySpan<byte> iv)
{
Aes aes = Aes.Create();
if (aes == null) throw new CryptographicException("Unable to create AES object");
aes.Mode = CipherMode.ECB;
aes.Padding = PaddingMode.None;
_encryptor = aes.CreateEncryptor(key.ToArray(), new byte[0x10]);
iv.CopyTo(_counter);
_baseCipher = new AesCtrMode();
_baseCipher.Initialize(key, iv);
}
public void Transform(ReadOnlySpan<byte> input, Span<byte> output)
{
int blockCount = Util.DivideByRoundUp(input.Length, BlockSizeBytes);
int length = blockCount * BlockSizeBytes;
byte[] counterXor = ArrayPool<byte>.Shared.Rent(length);
try
{
FillDecryptedCounter(_counter, counterXor.AsSpan(0, length));
_encryptor.TransformBlock(counterXor, 0, length, counterXor, 0);
input.CopyTo(output);
Util.XorArrays(output, counterXor);
}
finally
{
ArrayPool<byte>.Shared.Return(counterXor);
}
}
private static void FillDecryptedCounter(Span<byte> counter, Span<byte> buffer)
{
Span<ulong> bufL = MemoryMarshal.Cast<byte, ulong>(buffer);
Span<ulong> counterL = MemoryMarshal.Cast<byte, ulong>(counter);
ulong hi = counterL[0];
ulong lo = BinaryPrimitives.ReverseEndianness(counterL[1]);
for (int i = 0; i < bufL.Length; i += 2)
{
bufL[i] = hi;
bufL[i + 1] = BinaryPrimitives.ReverseEndianness(lo);
lo++;
}
counterL[1] = BinaryPrimitives.ReverseEndianness(lo);
_baseCipher.Transform(input, output);
}
}
}

View file

@ -1,94 +1,37 @@
using System;
using System.Buffers;
using System.Security.Cryptography;
using LibHac.Crypto2.Detail;
namespace LibHac.Crypto2
{
public class AesEcbEncryptor : ICipher
{
private const int BufferRentThreshold = 1024;
private ICryptoTransform _encryptor;
private AesEcbMode _baseCipher;
public AesEcbEncryptor(ReadOnlySpan<byte> key)
{
Aes aes = Aes.Create();
if (aes == null) throw new CryptographicException("Unable to create AES object");
aes.Key = key.ToArray();
aes.Mode = CipherMode.ECB;
aes.Padding = PaddingMode.None;
_encryptor = aes.CreateEncryptor();
_baseCipher = new AesEcbMode();
_baseCipher.Initialize(key, false);
}
public void Transform(ReadOnlySpan<byte> input, Span<byte> output)
{
if (input.Length < BufferRentThreshold)
{
var outputBuffer = new byte[input.Length];
input.CopyTo(outputBuffer);
_encryptor.TransformBlock(outputBuffer, 0, input.Length, outputBuffer, 0);
outputBuffer.CopyTo(output);
}
else
{
byte[] outputBuffer = ArrayPool<byte>.Shared.Rent(input.Length);
try
{
input.CopyTo(outputBuffer);
_encryptor.TransformBlock(outputBuffer, 0, input.Length, outputBuffer, 0);
outputBuffer.CopyTo(output);
}
finally { ArrayPool<byte>.Shared.Return(outputBuffer); }
}
_baseCipher.Encrypt(input, output);
}
}
public class AesEcbDecryptor : ICipher
{
private const int BufferRentThreshold = 1024;
private ICryptoTransform _decryptor;
private AesEcbMode _baseCipher;
public AesEcbDecryptor(ReadOnlySpan<byte> key)
{
Aes aes = Aes.Create();
if (aes == null) throw new CryptographicException("Unable to create AES object");
aes.Key = key.ToArray();
aes.Mode = CipherMode.ECB;
aes.Padding = PaddingMode.None;
_decryptor = aes.CreateDecryptor();
_baseCipher = new AesEcbMode();
_baseCipher.Initialize(key, true);
}
public void Transform(ReadOnlySpan<byte> input, Span<byte> output)
{
if (input.Length < BufferRentThreshold)
{
var outputBuffer = new byte[input.Length];
input.CopyTo(outputBuffer);
_decryptor.TransformBlock(outputBuffer, 0, input.Length, outputBuffer, 0);
outputBuffer.CopyTo(output);
}
else
{
byte[] outputBuffer = ArrayPool<byte>.Shared.Rent(input.Length);
try
{
input.CopyTo(outputBuffer);
_decryptor.TransformBlock(outputBuffer, 0, input.Length, outputBuffer, 0);
outputBuffer.CopyTo(output);
}
finally { ArrayPool<byte>.Shared.Return(outputBuffer); }
}
_baseCipher.Decrypt(input, output);
}
}
}

View file

@ -1,208 +1,37 @@
using System;
using System.Buffers;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using LibHac.Common;
using LibHac.Crypto2.Detail;
namespace LibHac.Crypto2
{
public class AesXtsCipher : ICipher
public class AesXtsEncryptor : ICipher
{
private ICipher _dataCipher;
private ICipher _tweakCipher;
private Buffer16 _iv;
private bool _decrypting;
private AesXtsMode _baseCipher;
public AesXtsCipher(ReadOnlySpan<byte> key1, ReadOnlySpan<byte> key2, ReadOnlySpan<byte> iv, bool decrypting)
public AesXtsEncryptor(ReadOnlySpan<byte> key1, ReadOnlySpan<byte> key2, ReadOnlySpan<byte> iv)
{
Debug.Assert(key1.Length == AesCrypto.KeySize128);
Debug.Assert(key2.Length == AesCrypto.KeySize128);
Debug.Assert(iv.Length == AesCrypto.KeySize128);
if (decrypting)
{
_dataCipher = new AesEcbDecryptor(key1);
}
else
{
_dataCipher = new AesEcbEncryptor(key1);
}
_tweakCipher = new AesEcbEncryptor(key2);
_iv = new Buffer16();
iv.CopyTo(_iv);
_decrypting = decrypting;
}
public void Encrypt(ReadOnlySpan<byte> input, Span<byte> output)
{
int length = Math.Min(input.Length, output.Length);
int blockCount = length >> 4;
int leftover = length & 0xF;
// Data units must be at least 1 block long.
if (length < AesCrypto.BlockSize)
throw new ArgumentException();
var tweak = new Buffer16();
_tweakCipher.Transform(_iv, tweak);
byte[] tweakBufferRented = ArrayPool<byte>.Shared.Rent(blockCount * AesCrypto.BlockSize);
try
{
Span<byte> tweakBuffer = tweakBufferRented.AsSpan(0, blockCount * AesCrypto.BlockSize);
tweak = FillTweakBuffer(tweak, MemoryMarshal.Cast<byte, Buffer16>(tweakBuffer));
Util.XorArrays(output, input, tweakBuffer);
_dataCipher.Transform(output.Slice(0, blockCount * AesCrypto.BlockSize), output);
Util.XorArrays(output, output, tweakBuffer);
}
finally { ArrayPool<byte>.Shared.Return(tweakBufferRented); }
if (leftover != 0)
{
ref Buffer16 inBlock =
ref Unsafe.Add(ref Unsafe.As<byte, Buffer16>(ref MemoryMarshal.GetReference(input)), blockCount);
ref Buffer16 outBlock =
ref Unsafe.Add(ref Unsafe.As<byte, Buffer16>(ref MemoryMarshal.GetReference(output)), blockCount);
ref Buffer16 prevOutBlock = ref Unsafe.Subtract(ref outBlock, 1);
var tmp = new Buffer16();
for (int i = 0; i < leftover; i++)
{
outBlock[i] = prevOutBlock[i];
tmp[i] = inBlock[i];
}
for (int i = leftover; i < AesCrypto.BlockSize; i++)
{
tmp[i] = prevOutBlock[i];
}
XorBuffer(ref tmp, ref tmp, ref tweak);
_dataCipher.Transform(tmp, tmp);
XorBuffer(ref prevOutBlock, ref tmp, ref tweak);
}
}
public void Decrypt(ReadOnlySpan<byte> input, Span<byte> output)
{
int length = Math.Min(input.Length, output.Length);
int blockCount = length >> 4;
int leftover = length & 0xF;
// Data units must be at least 1 block long.
if (length < AesCrypto.BlockSize)
throw new ArgumentException();
if (leftover != 0) blockCount--;
var tweak = new Buffer16();
_tweakCipher.Transform(_iv, tweak);
if (blockCount > 0)
{
byte[] tweakBufferRented = ArrayPool<byte>.Shared.Rent(blockCount * AesCrypto.BlockSize);
try
{
Span<byte> tweakBuffer = tweakBufferRented.AsSpan(0, blockCount * AesCrypto.BlockSize);
tweak = FillTweakBuffer(tweak, MemoryMarshal.Cast<byte, Buffer16>(tweakBuffer));
Util.XorArrays(output, input, tweakBuffer);
_dataCipher.Transform(output.Slice(0, blockCount * AesCrypto.BlockSize), output);
Util.XorArrays(output, output, tweakBuffer);
}
finally { ArrayPool<byte>.Shared.Return(tweakBufferRented); }
}
if (leftover != 0)
{
Buffer16 finalTweak = tweak;
Gf128Mul(ref finalTweak);
ref Buffer16 inBlock =
ref Unsafe.Add(ref Unsafe.As<byte, Buffer16>(ref MemoryMarshal.GetReference(input)), blockCount);
ref Buffer16 outBlock =
ref Unsafe.Add(ref Unsafe.As<byte, Buffer16>(ref MemoryMarshal.GetReference(output)), blockCount);
var tmp = new Buffer16();
XorBuffer(ref tmp, ref inBlock, ref finalTweak);
_dataCipher.Transform(tmp, tmp);
XorBuffer(ref outBlock, ref tmp, ref finalTweak);
ref Buffer16 finalOutBlock = ref Unsafe.Add(ref outBlock, 1);
ref Buffer16 finalInBlock = ref Unsafe.Add(ref inBlock, 1);
for (int i = 0; i < leftover; i++)
{
finalOutBlock[i] = outBlock[i];
tmp[i] = finalInBlock[i];
}
for (int i = leftover; i < AesCrypto.BlockSize; i++)
{
tmp[i] = outBlock[i];
}
XorBuffer(ref tmp, ref tmp, ref tweak);
_dataCipher.Transform(tmp, tmp);
XorBuffer(ref outBlock, ref tmp, ref tweak);
}
_baseCipher = new AesXtsMode();
_baseCipher.Initialize(key1, key2, iv, false);
}
public void Transform(ReadOnlySpan<byte> input, Span<byte> output)
{
if (_decrypting)
_baseCipher.Encrypt(input, output);
}
}
public class AesXtsDecryptor : ICipher
{
Decrypt(input, output);
}
else
private AesXtsMode _baseCipher;
public AesXtsDecryptor(ReadOnlySpan<byte> key1, ReadOnlySpan<byte> key2, ReadOnlySpan<byte> iv)
{
Encrypt(input, output);
}
_baseCipher = new AesXtsMode();
_baseCipher.Initialize(key1, key2, iv, true);
}
private static Buffer16 FillTweakBuffer(Buffer16 initialTweak, Span<Buffer16> tweakBuffer)
public void Transform(ReadOnlySpan<byte> input, Span<byte> output)
{
for (int i = 0; i < tweakBuffer.Length; i++)
{
tweakBuffer[i] = initialTweak;
Gf128Mul(ref initialTweak);
}
return initialTweak;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void Gf128Mul(ref Buffer16 buffer)
{
Span<ulong> b = buffer.AsSpan<ulong>();
ulong tt = (ulong)((long)b[1] >> 63) & 0x87;
b[1] = (b[1] << 1) | (b[0] >> 63);
b[0] = (b[0] << 1) ^ tt;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void XorBuffer(ref Buffer16 output, ref Buffer16 input1, ref Buffer16 input2)
{
Span<ulong> outputS = output.AsSpan<ulong>();
Span<ulong> input1S = input1.AsSpan<ulong>();
Span<ulong> input2S = input2.AsSpan<ulong>();
outputS[0] = input1S[0] ^ input2S[0];
outputS[1] = input1S[1] ^ input2S[1];
_baseCipher.Decrypt(input, output);
}
}
}

View file

@ -0,0 +1,26 @@
using System;
using System.Security.Cryptography;
namespace LibHac.Crypto2.Detail
{
public struct AesCbcMode
{
private AesCore _aesCore;
public void Initialize(ReadOnlySpan<byte> key, ReadOnlySpan<byte> iv, bool isDecrypting)
{
_aesCore = new AesCore();
_aesCore.Initialize(key, iv, CipherMode.CBC, isDecrypting);
}
public void Encrypt(ReadOnlySpan<byte> input, Span<byte> output)
{
_aesCore.Encrypt(input, output);
}
public void Decrypt(ReadOnlySpan<byte> input, Span<byte> output)
{
_aesCore.Decrypt(input, output);
}
}
}

View file

@ -0,0 +1,74 @@
using System;
using System.Diagnostics;
using System.Security.Cryptography;
using LibHac.Common;
namespace LibHac.Crypto2.Detail
{
public struct AesCore
{
private ICryptoTransform _transform;
private bool _isDecrypting;
public void Initialize(ReadOnlySpan<byte> key, ReadOnlySpan<byte> iv, CipherMode mode, bool isDecrypting)
{
Debug.Assert(key.Length == AesCrypto.KeySize128);
Debug.Assert(iv.IsEmpty || iv.Length == AesCrypto.BlockSize);
Aes aes = Aes.Create();
if (aes == null) throw new CryptographicException("Unable to create AES object");
aes.Key = key.ToArray();
aes.Mode = mode;
aes.Padding = PaddingMode.None;
if (!iv.IsEmpty)
{
aes.IV = iv.ToArray();
}
_transform = isDecrypting ? aes.CreateDecryptor() : aes.CreateEncryptor();
_isDecrypting = isDecrypting;
}
public void Encrypt(ReadOnlySpan<byte> input, Span<byte> output)
{
Debug.Assert(!_isDecrypting);
Transform(input, output);
}
public void Decrypt(ReadOnlySpan<byte> input, Span<byte> output)
{
Debug.Assert(_isDecrypting);
Transform(input, output);
}
public void Encrypt(byte[] input, byte[] output, int length)
{
Debug.Assert(!_isDecrypting);
Transform(input, output, length);
}
public void Decrypt(byte[] input, byte[] output, int length)
{
Debug.Assert(_isDecrypting);
Transform(input, output, length);
}
private void Transform(ReadOnlySpan<byte> input, Span<byte> output)
{
using var rented = new RentedArray<byte>(input.Length);
input.CopyTo(rented.Array);
Transform(rented.Array, rented.Array, input.Length);
rented.Array.CopyTo(output);
}
private void Transform(byte[] input, byte[] output, int length)
{
_transform.TransformBlock(input, 0, length, output, 0);
}
}
}

View file

@ -0,0 +1,55 @@
using System;
using System.Buffers.Binary;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using LibHac.Common;
namespace LibHac.Crypto2.Detail
{
public struct AesCtrMode
{
private AesCore _aesCore;
private byte[] _counter;
public void Initialize(ReadOnlySpan<byte> key, ReadOnlySpan<byte> iv)
{
Debug.Assert(iv.Length == AesCrypto.BlockSize);
_aesCore = new AesCore();
_aesCore.Initialize(key, ReadOnlySpan<byte>.Empty, CipherMode.ECB, false);
_counter = iv.ToArray();
}
public void Transform(ReadOnlySpan<byte> input, Span<byte> output)
{
int blockCount = Util.DivideByRoundUp(input.Length, AesCrypto.BlockSize);
int length = blockCount * AesCrypto.BlockSize;
using var counterBuffer = new RentedArray<byte>(length);
FillDecryptedCounter(_counter, counterBuffer.Span);
_aesCore.Encrypt(counterBuffer.Array, counterBuffer.Array, length);
Util.XorArrays(output, input, counterBuffer.Span);
}
private static void FillDecryptedCounter(Span<byte> counter, Span<byte> buffer)
{
Span<ulong> bufL = MemoryMarshal.Cast<byte, ulong>(buffer);
Span<ulong> counterL = MemoryMarshal.Cast<byte, ulong>(counter);
ulong hi = counterL[0];
ulong lo = BinaryPrimitives.ReverseEndianness(counterL[1]);
for (int i = 0; i < bufL.Length; i += 2)
{
bufL[i] = hi;
bufL[i + 1] = BinaryPrimitives.ReverseEndianness(lo);
lo++;
}
counterL[1] = BinaryPrimitives.ReverseEndianness(lo);
}
}
}

View file

@ -0,0 +1,26 @@
using System;
using System.Security.Cryptography;
namespace LibHac.Crypto2.Detail
{
public struct AesEcbMode
{
private AesCore _aesCore;
public void Initialize(ReadOnlySpan<byte> key, bool isDecrypting)
{
_aesCore = new AesCore();
_aesCore.Initialize(key, ReadOnlySpan<byte>.Empty, CipherMode.ECB, isDecrypting);
}
public void Encrypt(ReadOnlySpan<byte> input, Span<byte> output)
{
_aesCore.Encrypt(input, output);
}
public void Decrypt(ReadOnlySpan<byte> input, Span<byte> output)
{
_aesCore.Decrypt(input, output);
}
}
}

View file

@ -0,0 +1,175 @@
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using LibHac.Common;
namespace LibHac.Crypto2.Detail
{
public struct AesXtsMode
{
private AesCore _dataAesCore;
private AesCore _tweakAesCore;
private byte[] _iv;
public void Initialize(ReadOnlySpan<byte> key1, ReadOnlySpan<byte> key2, ReadOnlySpan<byte> iv, bool isDecrypting)
{
Debug.Assert(iv.Length == AesCrypto.BlockSize);
_dataAesCore = new AesCore();
_tweakAesCore = new AesCore();
_dataAesCore.Initialize(key1, ReadOnlySpan<byte>.Empty, CipherMode.ECB, isDecrypting);
_tweakAesCore.Initialize(key2, ReadOnlySpan<byte>.Empty, CipherMode.ECB, false);
_iv = iv.ToArray();
}
public void Encrypt(ReadOnlySpan<byte> input, Span<byte> output)
{
int length = Math.Min(input.Length, output.Length);
int blockCount = length >> 4;
int leftover = length & 0xF;
// Data units must be at least 1 block long.
if (length < AesCrypto.BlockSize)
throw new ArgumentException();
var tweak = new Buffer16();
_tweakAesCore.Encrypt(_iv, tweak);
using var tweakBuffer = new RentedArray<byte>(blockCount * AesCrypto.BlockSize);
tweak = FillTweakBuffer(tweak, MemoryMarshal.Cast<byte, Buffer16>(tweakBuffer.Span));
Util.XorArrays(output, input, tweakBuffer.Span);
_dataAesCore.Encrypt(output.Slice(0, blockCount * AesCrypto.BlockSize), output);
Util.XorArrays(output, output, tweakBuffer.Array);
if (leftover != 0)
{
ref Buffer16 inBlock =
ref Unsafe.Add(ref Unsafe.As<byte, Buffer16>(ref MemoryMarshal.GetReference(input)), blockCount);
ref Buffer16 outBlock =
ref Unsafe.Add(ref Unsafe.As<byte, Buffer16>(ref MemoryMarshal.GetReference(output)), blockCount);
ref Buffer16 prevOutBlock = ref Unsafe.Subtract(ref outBlock, 1);
var tmp = new Buffer16();
for (int i = 0; i < leftover; i++)
{
outBlock[i] = prevOutBlock[i];
tmp[i] = inBlock[i];
}
for (int i = leftover; i < AesCrypto.BlockSize; i++)
{
tmp[i] = prevOutBlock[i];
}
XorBuffer(ref tmp, ref tmp, ref tweak);
_dataAesCore.Encrypt(tmp, tmp);
XorBuffer(ref prevOutBlock, ref tmp, ref tweak);
}
}
public void Decrypt(ReadOnlySpan<byte> input, Span<byte> output)
{
int length = Math.Min(input.Length, output.Length);
int blockCount = length >> 4;
int leftover = length & 0xF;
// Data units must be at least 1 block long.
if (length < AesCrypto.BlockSize)
throw new ArgumentException();
if (leftover != 0) blockCount--;
var tweak = new Buffer16();
_tweakAesCore.Encrypt(_iv, tweak);
if (blockCount > 0)
{
using var tweakBuffer = new RentedArray<byte>(blockCount * AesCrypto.BlockSize);
tweak = FillTweakBuffer(tweak, MemoryMarshal.Cast<byte, Buffer16>(tweakBuffer.Span));
Util.XorArrays(output, input, tweakBuffer.Span);
_dataAesCore.Decrypt(output.Slice(0, blockCount * AesCrypto.BlockSize), output);
Util.XorArrays(output, output, tweakBuffer.Span);
}
if (leftover != 0)
{
Buffer16 finalTweak = tweak;
Gf128Mul(ref finalTweak);
ref Buffer16 inBlock =
ref Unsafe.Add(ref Unsafe.As<byte, Buffer16>(ref MemoryMarshal.GetReference(input)), blockCount);
ref Buffer16 outBlock =
ref Unsafe.Add(ref Unsafe.As<byte, Buffer16>(ref MemoryMarshal.GetReference(output)), blockCount);
var tmp = new Buffer16();
XorBuffer(ref tmp, ref inBlock, ref finalTweak);
_dataAesCore.Decrypt(tmp, tmp);
XorBuffer(ref outBlock, ref tmp, ref finalTweak);
ref Buffer16 finalOutBlock = ref Unsafe.Add(ref outBlock, 1);
ref Buffer16 finalInBlock = ref Unsafe.Add(ref inBlock, 1);
for (int i = 0; i < leftover; i++)
{
finalOutBlock[i] = outBlock[i];
tmp[i] = finalInBlock[i];
}
for (int i = leftover; i < AesCrypto.BlockSize; i++)
{
tmp[i] = outBlock[i];
}
XorBuffer(ref tmp, ref tmp, ref tweak);
_dataAesCore.Decrypt(tmp, tmp);
XorBuffer(ref outBlock, ref tmp, ref tweak);
}
}
private static Buffer16 FillTweakBuffer(Buffer16 initialTweak, Span<Buffer16> tweakBuffer)
{
for (int i = 0; i < tweakBuffer.Length; i++)
{
tweakBuffer[i] = initialTweak;
Gf128Mul(ref initialTweak);
}
return initialTweak;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void Gf128Mul(ref Buffer16 buffer)
{
Span<ulong> b = buffer.AsSpan<ulong>();
ulong tt = (ulong)((long)b[1] >> 63) & 0x87;
b[1] = (b[1] << 1) | (b[0] >> 63);
b[0] = (b[0] << 1) ^ tt;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void XorBuffer(ref Buffer16 output, ref Buffer16 input1, ref Buffer16 input2)
{
Span<ulong> outputS = output.AsSpan<ulong>();
Span<ulong> input1S = input1.AsSpan<ulong>();
Span<ulong> input2S = input2.AsSpan<ulong>();
outputS[0] = input1S[0] ^ input2S[0];
outputS[1] = input1S[1] ^ input2S[1];
}
}
}