mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2024-11-14 10:49:41 +01:00
Refactor non-NI AES code
This commit is contained in:
parent
2752a7c3db
commit
4b2c4d9553
11 changed files with 445 additions and 346 deletions
41
src/LibHac/Common/RentedArray.cs
Normal file
41
src/LibHac/Common/RentedArray.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -86,7 +86,7 @@ namespace LibHac.Crypto2
|
||||||
return new AesCtrCipherNi(key, iv);
|
return new AesCtrCipherNi(key, iv);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
return new AesCtrEncryptor(key, iv);
|
return new AesCtrCipher(key, iv);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ICipher CreateXtsDecryptor(ReadOnlySpan<byte> key1, ReadOnlySpan<byte> key2,
|
public static ICipher CreateXtsDecryptor(ReadOnlySpan<byte> key1, ReadOnlySpan<byte> key2,
|
||||||
|
@ -98,7 +98,7 @@ namespace LibHac.Crypto2
|
||||||
return new AesXtsDecryptorNi(key1, key2, iv);
|
return new AesXtsDecryptorNi(key1, key2, iv);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
return new AesXtsCipher(key1, key2, iv, true);
|
return new AesXtsDecryptor(key1, key2, iv);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ICipher CreateXtsEncryptor(ReadOnlySpan<byte> key1, ReadOnlySpan<byte> key2,
|
public static ICipher CreateXtsEncryptor(ReadOnlySpan<byte> key1, ReadOnlySpan<byte> key2,
|
||||||
|
@ -110,7 +110,7 @@ namespace LibHac.Crypto2
|
||||||
return new AesXtsEncryptorNi(key1, key2, iv);
|
return new AesXtsEncryptorNi(key1, key2, iv);
|
||||||
}
|
}
|
||||||
#endif
|
#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,
|
public static void EncryptEcb128(ReadOnlySpan<byte> input, Span<byte> output, ReadOnlySpan<byte> key,
|
||||||
|
|
|
@ -1,59 +1,37 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Security.Cryptography;
|
using LibHac.Crypto2.Detail;
|
||||||
|
|
||||||
namespace LibHac.Crypto2
|
namespace LibHac.Crypto2
|
||||||
{
|
{
|
||||||
public class AesCbcEncryptor : ICipher
|
public class AesCbcEncryptor : ICipher
|
||||||
{
|
{
|
||||||
private ICryptoTransform _encryptor;
|
private AesCbcMode _baseCipher;
|
||||||
|
|
||||||
public AesCbcEncryptor(ReadOnlySpan<byte> key, ReadOnlySpan<byte> iv)
|
public AesCbcEncryptor(ReadOnlySpan<byte> key, ReadOnlySpan<byte> iv)
|
||||||
{
|
{
|
||||||
Aes aes = Aes.Create();
|
_baseCipher = new AesCbcMode();
|
||||||
|
_baseCipher.Initialize(key, iv, false);
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Transform(ReadOnlySpan<byte> input, Span<byte> output)
|
public void Transform(ReadOnlySpan<byte> input, Span<byte> output)
|
||||||
{
|
{
|
||||||
var outputBuffer = new byte[input.Length];
|
_baseCipher.Encrypt(input, output);
|
||||||
|
|
||||||
_encryptor.TransformBlock(input.ToArray(), 0, input.Length, outputBuffer, 0);
|
|
||||||
|
|
||||||
outputBuffer.CopyTo(output);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class AesCbcDecryptor : ICipher
|
public class AesCbcDecryptor : ICipher
|
||||||
{
|
{
|
||||||
private ICryptoTransform _decryptor;
|
private AesCbcMode _baseCipher;
|
||||||
|
|
||||||
public AesCbcDecryptor(ReadOnlySpan<byte> key, ReadOnlySpan<byte> iv)
|
public AesCbcDecryptor(ReadOnlySpan<byte> key, ReadOnlySpan<byte> iv)
|
||||||
{
|
{
|
||||||
Aes aes = Aes.Create();
|
_baseCipher = new AesCbcMode();
|
||||||
|
_baseCipher.Initialize(key, iv, true);
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Transform(ReadOnlySpan<byte> input, Span<byte> output)
|
public void Transform(ReadOnlySpan<byte> input, Span<byte> output)
|
||||||
{
|
{
|
||||||
var outputBuffer = new byte[input.Length];
|
_baseCipher.Decrypt(input, output);
|
||||||
|
|
||||||
_decryptor.TransformBlock(input.ToArray(), 0, input.Length, outputBuffer, 0);
|
|
||||||
|
|
||||||
outputBuffer.CopyTo(output);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,69 +1,21 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Buffers;
|
using LibHac.Crypto2.Detail;
|
||||||
using System.Buffers.Binary;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Security.Cryptography;
|
|
||||||
|
|
||||||
namespace LibHac.Crypto2
|
namespace LibHac.Crypto2
|
||||||
{
|
{
|
||||||
public class AesCtrEncryptor : ICipher
|
public class AesCtrCipher : ICipher
|
||||||
{
|
{
|
||||||
private const int BlockSize = 128;
|
private AesCtrMode _baseCipher;
|
||||||
private const int BlockSizeBytes = BlockSize / 8;
|
|
||||||
|
|
||||||
private readonly ICryptoTransform _encryptor;
|
public AesCtrCipher(ReadOnlySpan<byte> key, ReadOnlySpan<byte> iv)
|
||||||
private readonly byte[] _counter = new byte[0x10];
|
|
||||||
|
|
||||||
public AesCtrEncryptor(ReadOnlySpan<byte> key, ReadOnlySpan<byte> iv)
|
|
||||||
{
|
{
|
||||||
Aes aes = Aes.Create();
|
_baseCipher = new AesCtrMode();
|
||||||
if (aes == null) throw new CryptographicException("Unable to create AES object");
|
_baseCipher.Initialize(key, iv);
|
||||||
|
|
||||||
aes.Mode = CipherMode.ECB;
|
|
||||||
aes.Padding = PaddingMode.None;
|
|
||||||
|
|
||||||
_encryptor = aes.CreateEncryptor(key.ToArray(), new byte[0x10]);
|
|
||||||
|
|
||||||
iv.CopyTo(_counter);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Transform(ReadOnlySpan<byte> input, Span<byte> output)
|
public void Transform(ReadOnlySpan<byte> input, Span<byte> output)
|
||||||
{
|
{
|
||||||
int blockCount = Util.DivideByRoundUp(input.Length, BlockSizeBytes);
|
_baseCipher.Transform(input, output);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,94 +1,37 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Buffers;
|
using LibHac.Crypto2.Detail;
|
||||||
using System.Security.Cryptography;
|
|
||||||
|
|
||||||
namespace LibHac.Crypto2
|
namespace LibHac.Crypto2
|
||||||
{
|
{
|
||||||
public class AesEcbEncryptor : ICipher
|
public class AesEcbEncryptor : ICipher
|
||||||
{
|
{
|
||||||
private const int BufferRentThreshold = 1024;
|
private AesEcbMode _baseCipher;
|
||||||
private ICryptoTransform _encryptor;
|
|
||||||
|
|
||||||
public AesEcbEncryptor(ReadOnlySpan<byte> key)
|
public AesEcbEncryptor(ReadOnlySpan<byte> key)
|
||||||
{
|
{
|
||||||
Aes aes = Aes.Create();
|
_baseCipher = new AesEcbMode();
|
||||||
|
_baseCipher.Initialize(key, false);
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Transform(ReadOnlySpan<byte> input, Span<byte> output)
|
public void Transform(ReadOnlySpan<byte> input, Span<byte> output)
|
||||||
{
|
{
|
||||||
if (input.Length < BufferRentThreshold)
|
_baseCipher.Encrypt(input, output);
|
||||||
{
|
|
||||||
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); }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class AesEcbDecryptor : ICipher
|
public class AesEcbDecryptor : ICipher
|
||||||
{
|
{
|
||||||
private const int BufferRentThreshold = 1024;
|
private AesEcbMode _baseCipher;
|
||||||
private ICryptoTransform _decryptor;
|
|
||||||
|
|
||||||
public AesEcbDecryptor(ReadOnlySpan<byte> key)
|
public AesEcbDecryptor(ReadOnlySpan<byte> key)
|
||||||
{
|
{
|
||||||
Aes aes = Aes.Create();
|
_baseCipher = new AesEcbMode();
|
||||||
|
_baseCipher.Initialize(key, true);
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Transform(ReadOnlySpan<byte> input, Span<byte> output)
|
public void Transform(ReadOnlySpan<byte> input, Span<byte> output)
|
||||||
{
|
{
|
||||||
if (input.Length < BufferRentThreshold)
|
_baseCipher.Decrypt(input, output);
|
||||||
{
|
|
||||||
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); }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,208 +1,37 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Buffers;
|
using LibHac.Crypto2.Detail;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using LibHac.Common;
|
|
||||||
|
|
||||||
namespace LibHac.Crypto2
|
namespace LibHac.Crypto2
|
||||||
{
|
{
|
||||||
public class AesXtsCipher : ICipher
|
public class AesXtsEncryptor : ICipher
|
||||||
{
|
{
|
||||||
private ICipher _dataCipher;
|
private AesXtsMode _baseCipher;
|
||||||
private ICipher _tweakCipher;
|
|
||||||
private Buffer16 _iv;
|
|
||||||
private bool _decrypting;
|
|
||||||
|
|
||||||
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);
|
_baseCipher = new AesXtsMode();
|
||||||
Debug.Assert(key2.Length == AesCrypto.KeySize128);
|
_baseCipher.Initialize(key1, key2, iv, false);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Transform(ReadOnlySpan<byte> input, Span<byte> output)
|
public void Transform(ReadOnlySpan<byte> input, Span<byte> output)
|
||||||
{
|
{
|
||||||
if (_decrypting)
|
_baseCipher.Encrypt(input, output);
|
||||||
{
|
}
|
||||||
Decrypt(input, output);
|
}
|
||||||
}
|
|
||||||
else
|
public class AesXtsDecryptor : ICipher
|
||||||
{
|
{
|
||||||
Encrypt(input, output);
|
private AesXtsMode _baseCipher;
|
||||||
}
|
|
||||||
|
public AesXtsDecryptor(ReadOnlySpan<byte> key1, ReadOnlySpan<byte> key2, ReadOnlySpan<byte> iv)
|
||||||
|
{
|
||||||
|
_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++)
|
_baseCipher.Decrypt(input, output);
|
||||||
{
|
|
||||||
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];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
26
src/LibHac/Crypto2/Detail/AesCbcMode.cs
Normal file
26
src/LibHac/Crypto2/Detail/AesCbcMode.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
74
src/LibHac/Crypto2/Detail/AesCore.cs
Normal file
74
src/LibHac/Crypto2/Detail/AesCore.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
55
src/LibHac/Crypto2/Detail/AesCtrMode.cs
Normal file
55
src/LibHac/Crypto2/Detail/AesCtrMode.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
26
src/LibHac/Crypto2/Detail/AesEcbMode.cs
Normal file
26
src/LibHac/Crypto2/Detail/AesEcbMode.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
175
src/LibHac/Crypto2/Detail/AesXtsMode.cs
Normal file
175
src/LibHac/Crypto2/Detail/AesXtsMode.cs
Normal 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];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue