Add XTS mode and remove duplicate code

This commit is contained in:
Alex Barney 2019-11-15 18:38:55 -06:00
parent df27d2a83b
commit 8b47be19c2
17 changed files with 8923 additions and 150 deletions

View file

@ -0,0 +1,68 @@
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace LibHac.Common
{
/// <summary>
/// Represents a buffer of 16 bytes.
/// Contains functions that assist with common operations on small buffers.
/// </summary>
[DebuggerDisplay("{ToString()}")]
[StructLayout(LayoutKind.Sequential, Size = 16)]
public struct Buffer16
{
[DebuggerBrowsable(DebuggerBrowsableState.Never)] private ulong _dummy0;
[DebuggerBrowsable(DebuggerBrowsableState.Never)] private ulong _dummy1;
public byte this[int i]
{
get => Bytes[i];
set => Bytes[i] = value;
}
public Span<byte> Bytes => SpanHelpers.AsByteSpan(ref this);
// Prevent a defensive copy by changing the read-only in reference to a reference with Unsafe.AsRef()
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Span<byte>(in Buffer16 value)
{
return SpanHelpers.AsByteSpan(ref Unsafe.AsRef(in value));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator ReadOnlySpan<byte>(in Buffer16 value)
{
return SpanHelpers.AsReadOnlyByteSpan(ref Unsafe.AsRef(in value));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ref T As<T>() where T : unmanaged
{
if (Unsafe.SizeOf<T>() > (uint)Unsafe.SizeOf<Buffer16>())
{
throw new ArgumentException();
}
return ref MemoryMarshal.GetReference(AsSpan<T>());
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Span<T> AsSpan<T>() where T : unmanaged
{
return SpanHelpers.AsSpan<Buffer16, T>(ref this);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly ReadOnlySpan<T> AsReadOnlySpan<T>() where T : unmanaged
{
return SpanHelpers.AsReadOnlySpan<Buffer16, T>(ref Unsafe.AsRef(in this));
}
public override string ToString()
{
return Bytes.ToHexString();
}
}
}

View file

@ -25,11 +25,51 @@ namespace LibHac.Common
return CreateSpan(ref reference, 1);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Span<TSpan> AsSpan<TStruct, TSpan>(ref TStruct reference)
where TStruct : unmanaged where TSpan : unmanaged
{
return CreateSpan(ref Unsafe.As<TStruct, TSpan>(ref reference),
Unsafe.SizeOf<TStruct>() / Unsafe.SizeOf<TSpan>());
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Span<byte> AsByteSpan<T>(ref T reference) where T : unmanaged
{
Span<T> span = AsSpan(ref reference);
return MemoryMarshal.Cast<T, byte>(span);
return CreateSpan(ref Unsafe.As<T, byte>(ref reference), Unsafe.SizeOf<T>());
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
#if NETCOREAPP
public static ReadOnlySpan<T> CreateReadOnlySpan<T>(ref T reference, int length)
{
return MemoryMarshal.CreateReadOnlySpan(ref reference, length);
}
#else
public static unsafe ReadOnlySpan<T> CreateReadOnlySpan<T>(ref T reference, int length)
{
return new ReadOnlySpan<T>(Unsafe.AsPointer(ref reference), length);
}
#endif
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ReadOnlySpan<T> AsReadOnlySpan<T>(ref T reference) where T : unmanaged
{
return CreateReadOnlySpan(ref reference, 1);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ReadOnlySpan<TSpan> AsReadOnlySpan<TStruct, TSpan>(ref TStruct reference)
where TStruct : unmanaged where TSpan : unmanaged
{
return CreateReadOnlySpan(ref Unsafe.As<TStruct, TSpan>(ref reference),
Unsafe.SizeOf<TStruct>() / Unsafe.SizeOf<TSpan>());
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ReadOnlySpan<byte> AsReadOnlyByteSpan<T>(ref T reference) where T : unmanaged
{
return CreateReadOnlySpan(ref Unsafe.As<T, byte>(ref reference), Unsafe.SizeOf<T>());
}
}
}

View file

@ -8,6 +8,9 @@ namespace LibHac.Crypto2
{
public static class AesCrypto
{
public const int KeySize128 = 0x10;
public const int BlockSize = 0x10;
public static bool IsAesNiSupported()
{
#if HAS_INTRINSICS
@ -77,5 +80,29 @@ namespace LibHac.Crypto2
#endif
return new AesCtrEncryptor(key, iv);
}
public static ICipher CreateXtsDecryptor(ReadOnlySpan<byte> key1, ReadOnlySpan<byte> key2,
ReadOnlySpan<byte> iv, bool preferDotNetCrypto = false)
{
#if HAS_INTRINSICS
if (IsAesNiSupported() && !preferDotNetCrypto)
{
return new AesXtsCipherHw(key1, key2, iv, true);
}
#endif
return new AesXtsCipher(key1, key2, iv, true);
}
public static ICipher CreateXtsEncryptor(ReadOnlySpan<byte> key1, ReadOnlySpan<byte> key2,
ReadOnlySpan<byte> iv, bool preferDotNetCrypto = false)
{
#if HAS_INTRINSICS
if (IsAesNiSupported() && !preferDotNetCrypto)
{
return new AesXtsCipherHw(key1, key2, iv, false);
}
#endif
return new AesXtsCipher(key1, key2, iv, false);
}
}
}

View file

@ -24,32 +24,24 @@ namespace LibHac.Crypto2
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
public void Transform(ReadOnlySpan<byte> input, Span<byte> output)
{
ReadOnlySpan<Vector128<byte>> keys = _aesCore.RoundKeys;
ReadOnlySpan<Vector128<byte>> inBlocks = MemoryMarshal.Cast<byte, Vector128<byte>>(input);
Span<Vector128<byte>> outBlocks = MemoryMarshal.Cast<byte, Vector128<byte>>(output);
int blockCount = Math.Min(input.Length, output.Length) >> 4;
Vector128<byte> b = _iv;
ref Vector128<byte> inBlock = ref Unsafe.As<byte, Vector128<byte>>(ref MemoryMarshal.GetReference(input));
ref Vector128<byte> outBlock = ref Unsafe.As<byte, Vector128<byte>>(ref MemoryMarshal.GetReference(output));
for (int i = 0; i < inBlocks.Length; i++)
Vector128<byte> iv = _iv;
for (int i = 0; i < blockCount; i++)
{
b = Sse2.Xor(b, inBlocks[i]);
iv = _aesCore.EncryptBlock(Sse2.Xor(iv, inBlock));
b = Sse2.Xor(b, keys[0]);
b = Aes.Encrypt(b, keys[1]);
b = Aes.Encrypt(b, keys[2]);
b = Aes.Encrypt(b, keys[3]);
b = Aes.Encrypt(b, keys[4]);
b = Aes.Encrypt(b, keys[5]);
b = Aes.Encrypt(b, keys[6]);
b = Aes.Encrypt(b, keys[7]);
b = Aes.Encrypt(b, keys[8]);
b = Aes.Encrypt(b, keys[9]);
b = Aes.EncryptLast(b, keys[10]);
outBlock = iv;
outBlocks[i] = b;
inBlock = ref Unsafe.Add(ref inBlock, 1);
outBlock = ref Unsafe.Add(ref outBlock, 1);
}
_iv = b;
_iv = iv;
}
}
@ -70,32 +62,22 @@ namespace LibHac.Crypto2
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
public void Transform(ReadOnlySpan<byte> input, Span<byte> output)
{
ReadOnlySpan<Vector128<byte>> keys = _aesCore.RoundKeys;
ReadOnlySpan<Vector128<byte>> inBlocks = MemoryMarshal.Cast<byte, Vector128<byte>>(input);
Span<Vector128<byte>> outBlocks = MemoryMarshal.Cast<byte, Vector128<byte>>(output);
int blockCount = Math.Min(input.Length, output.Length) >> 4;
ref Vector128<byte> inBlock = ref Unsafe.As<byte, Vector128<byte>>(ref MemoryMarshal.GetReference(input));
ref Vector128<byte> outBlock = ref Unsafe.As<byte, Vector128<byte>>(ref MemoryMarshal.GetReference(output));
Vector128<byte> iv = _iv;
for (int i = 0; i < inBlocks.Length; i++)
for (int i = 0; i < blockCount; i++)
{
Vector128<byte> b = inBlocks[i];
Vector128<byte> nextIv = b;
Vector128<byte> decBeforeIv = _aesCore.DecryptBlock(inBlock);
outBlock = Sse2.Xor(decBeforeIv, iv);
b = Sse2.Xor(b, keys[10]);
b = Aes.Decrypt(b, keys[9]);
b = Aes.Decrypt(b, keys[8]);
b = Aes.Decrypt(b, keys[7]);
b = Aes.Decrypt(b, keys[6]);
b = Aes.Decrypt(b, keys[5]);
b = Aes.Decrypt(b, keys[4]);
b = Aes.Decrypt(b, keys[3]);
b = Aes.Decrypt(b, keys[2]);
b = Aes.Decrypt(b, keys[1]);
b = Aes.DecryptLast(b, keys[0]);
iv = inBlock;
b = Sse2.Xor(b, iv);
iv = nextIv;
outBlocks[i] = b;
inBlock = ref Unsafe.Add(ref inBlock, 1);
outBlock = ref Unsafe.Add(ref outBlock, 1);
}
_iv = iv;

View file

@ -15,27 +15,62 @@ namespace LibHac.Crypto2
private Vector128<byte> _roundKeys;
public void Initialize(ReadOnlySpan<byte> key, bool isDecrypting)
public AesCoreNi(ReadOnlySpan<byte> key, bool isDecrypting)
{
KeyExpansion(key, RoundKeys, isDecrypting);
_roundKeys = default;
KeyExpansion(key, MemoryMarshal.CreateSpan(ref _roundKeys, RoundKeyCount), isDecrypting);
}
public Span<Vector128<byte>> RoundKeys =>
MemoryMarshal.CreateSpan(ref _roundKeys, RoundKeyCount);
public void Initialize(ReadOnlySpan<byte> key, bool isDecrypting)
{
KeyExpansion(key, MemoryMarshal.CreateSpan(ref _roundKeys, RoundKeyCount), isDecrypting);
}
public readonly ReadOnlySpan<Vector128<byte>> RoundKeys =>
MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef(in _roundKeys), RoundKeyCount);
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
public void Encrypt(ReadOnlySpan<byte> input, Span<byte> output)
public readonly void Encrypt(ReadOnlySpan<byte> input, Span<byte> output)
{
int blockCount = Math.Min(input.Length, output.Length) >> 4;
ref Vector128<byte> inBlock = ref Unsafe.As<byte, Vector128<byte>>(ref MemoryMarshal.GetReference(input));
ref Vector128<byte> outBlock = ref Unsafe.As<byte, Vector128<byte>>(ref MemoryMarshal.GetReference(output));
for (int i = 0; i < blockCount; i++)
{
outBlock = EncryptBlock(inBlock);
inBlock = ref Unsafe.Add(ref inBlock, 1);
outBlock = ref Unsafe.Add(ref outBlock, 1);
}
}
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
public readonly void Decrypt(ReadOnlySpan<byte> input, Span<byte> output)
{
int blockCount = Math.Min(input.Length, output.Length) >> 4;
ref Vector128<byte> inBlock = ref Unsafe.As<byte, Vector128<byte>>(ref MemoryMarshal.GetReference(input));
ref Vector128<byte> outBlock = ref Unsafe.As<byte, Vector128<byte>>(ref MemoryMarshal.GetReference(output));
for (int i = 0; i < blockCount; i++)
{
outBlock = DecryptBlock(inBlock);
inBlock = ref Unsafe.Add(ref inBlock, 1);
outBlock = ref Unsafe.Add(ref outBlock, 1);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly Vector128<byte> EncryptBlock(Vector128<byte> input)
{
ReadOnlySpan<Vector128<byte>> keys = RoundKeys;
ReadOnlySpan<Vector128<byte>> inBlocks = MemoryMarshal.Cast<byte, Vector128<byte>>(input);
Span<Vector128<byte>> outBlocks = MemoryMarshal.Cast<byte, Vector128<byte>>(output);
for (int i = 0; i < inBlocks.Length; i++)
{
Vector128<byte> b = inBlocks[i];
b = Sse2.Xor(b, keys[0]);
Vector128<byte> b = Sse2.Xor(input, keys[0]);
b = Aes.Encrypt(b, keys[1]);
b = Aes.Encrypt(b, keys[2]);
b = Aes.Encrypt(b, keys[3]);
@ -45,24 +80,15 @@ namespace LibHac.Crypto2
b = Aes.Encrypt(b, keys[7]);
b = Aes.Encrypt(b, keys[8]);
b = Aes.Encrypt(b, keys[9]);
b = Aes.EncryptLast(b, keys[10]);
outBlocks[i] = b;
}
return Aes.EncryptLast(b, keys[10]);
}
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
public void Decrypt(ReadOnlySpan<byte> input, Span<byte> output)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly Vector128<byte> DecryptBlock(Vector128<byte> input)
{
ReadOnlySpan<Vector128<byte>> keys = RoundKeys;
ReadOnlySpan<Vector128<byte>> inBlocks = MemoryMarshal.Cast<byte, Vector128<byte>>(input);
Span<Vector128<byte>> outBlocks = MemoryMarshal.Cast<byte, Vector128<byte>>(output);
for (int i = 0; i < inBlocks.Length; i++)
{
Vector128<byte> b = inBlocks[i];
b = Sse2.Xor(b, keys[10]);
Vector128<byte> b = Sse2.Xor(input, keys[10]);
b = Aes.Decrypt(b, keys[9]);
b = Aes.Decrypt(b, keys[8]);
b = Aes.Decrypt(b, keys[7]);
@ -72,10 +98,7 @@ namespace LibHac.Crypto2
b = Aes.Decrypt(b, keys[3]);
b = Aes.Decrypt(b, keys[2]);
b = Aes.Decrypt(b, keys[1]);
b = Aes.DecryptLast(b, keys[0]);
outBlocks[i] = b;
}
return Aes.DecryptLast(b, keys[0]);
}
[MethodImpl(MethodImplOptions.AggressiveOptimization)]

View file

@ -23,42 +23,35 @@ namespace LibHac.Crypto2
public void Transform(ReadOnlySpan<byte> input, Span<byte> output)
{
ReadOnlySpan<Vector128<byte>> keys = _aesCore.RoundKeys;
ReadOnlySpan<Vector128<byte>> inBlocks = MemoryMarshal.Cast<byte, Vector128<byte>>(input);
Span<Vector128<byte>> outBlocks = MemoryMarshal.Cast<byte, Vector128<byte>>(output);
int blockCount = Math.Min(input.Length, output.Length) >> 4;
ref Vector128<byte> inBlock = ref Unsafe.As<byte, Vector128<byte>>(ref MemoryMarshal.GetReference(input));
ref Vector128<byte> outBlock = ref Unsafe.As<byte, Vector128<byte>>(ref MemoryMarshal.GetReference(output));
Vector128<byte> byteSwapMask = Vector128.Create((ulong)0x706050403020100, 0x8090A0B0C0D0E0F).AsByte();
Vector128<ulong> inc = Vector128.Create((ulong)0, 1);
var iv = _iv;
Vector128<byte> iv = _iv;
Vector128<ulong> bSwappedIv = Ssse3.Shuffle(iv, byteSwapMask).AsUInt64();
for (int i = 0; i < inBlocks.Length; i++)
for (int i = 0; i < blockCount; i++)
{
Vector128<byte> b = Sse2.Xor(iv, keys[0]);
b = Aes.Encrypt(b, keys[1]);
b = Aes.Encrypt(b, keys[2]);
b = Aes.Encrypt(b, keys[3]);
b = Aes.Encrypt(b, keys[4]);
b = Aes.Encrypt(b, keys[5]);
b = Aes.Encrypt(b, keys[6]);
b = Aes.Encrypt(b, keys[7]);
b = Aes.Encrypt(b, keys[8]);
b = Aes.Encrypt(b, keys[9]);
b = Aes.EncryptLast(b, keys[10]);
outBlocks[i] = Sse2.Xor(inBlocks[i], b);
Vector128<byte> encIv = _aesCore.EncryptBlock(iv);
outBlock = Sse2.Xor(inBlock, encIv);
// Increase the counter
bSwappedIv = Sse2.Add(bSwappedIv, inc);
iv = Ssse3.Shuffle(bSwappedIv.AsByte(), byteSwapMask);
inBlock = ref Unsafe.Add(ref inBlock, 1);
outBlock = ref Unsafe.Add(ref outBlock, 1);
}
_iv = iv;
if ((input.Length & 0xF) != 0)
{
EncryptCtrPartialBlock(input.Slice(inBlocks.Length * 0x10), output.Slice(outBlocks.Length * 0x10));
EncryptCtrPartialBlock(input.Slice(blockCount * 0x10), output.Slice(blockCount * 0x10));
}
}

View file

@ -1,10 +1,12 @@
using System;
using System.Buffers;
using System.Security.Cryptography;
namespace LibHac.Crypto2
{
public class AesEcbEncryptor : ICipher
{
private const int BufferRentThreshold = 1024;
private ICryptoTransform _encryptor;
public AesEcbEncryptor(ReadOnlySpan<byte> key)
@ -20,17 +22,35 @@ namespace LibHac.Crypto2
}
public void Transform(ReadOnlySpan<byte> input, Span<byte> output)
{
if (input.Length < BufferRentThreshold)
{
var outputBuffer = new byte[input.Length];
input.CopyTo(outputBuffer);
_encryptor.TransformBlock(input.ToArray(), 0, input.Length, outputBuffer, 0);
_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
{
private const int BufferRentThreshold = 1024;
private ICryptoTransform _decryptor;
public AesEcbDecryptor(ReadOnlySpan<byte> key)
@ -46,12 +66,29 @@ namespace LibHac.Crypto2
}
public void Transform(ReadOnlySpan<byte> input, Span<byte> output)
{
if (input.Length < BufferRentThreshold)
{
var outputBuffer = new byte[input.Length];
input.CopyTo(outputBuffer);
_decryptor.TransformBlock(input.ToArray(), 0, input.Length, outputBuffer, 0);
_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); }
}
}
}
}

View file

@ -1,10 +1,5 @@

#if HAS_INTRINSICS
#if HAS_INTRINSICS
using System;
using System.Runtime.Intrinsics;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics.X86;
namespace LibHac.Crypto2
{
@ -20,28 +15,7 @@ namespace LibHac.Crypto2
public void Transform(ReadOnlySpan<byte> input, Span<byte> output)
{
ReadOnlySpan<Vector128<byte>> keys = _aesCore.RoundKeys;
ReadOnlySpan<Vector128<byte>> inBlocks = MemoryMarshal.Cast<byte, Vector128<byte>>(input);
Span<Vector128<byte>> outBlocks = MemoryMarshal.Cast<byte, Vector128<byte>>(output);
for (int i = 0; i < inBlocks.Length; i++)
{
Vector128<byte> b = inBlocks[i];
b = Sse2.Xor(b, keys[0]);
b = Aes.Encrypt(b, keys[1]);
b = Aes.Encrypt(b, keys[2]);
b = Aes.Encrypt(b, keys[3]);
b = Aes.Encrypt(b, keys[4]);
b = Aes.Encrypt(b, keys[5]);
b = Aes.Encrypt(b, keys[6]);
b = Aes.Encrypt(b, keys[7]);
b = Aes.Encrypt(b, keys[8]);
b = Aes.Encrypt(b, keys[9]);
b = Aes.EncryptLast(b, keys[10]);
outBlocks[i] = b;
}
_aesCore.Encrypt(input, output);
}
}

View file

@ -0,0 +1,208 @@
using System;
using System.Buffers;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using LibHac.Common;
namespace LibHac.Crypto2
{
public class AesXtsCipher : ICipher
{
private ICipher _dataCipher;
private ICipher _tweakCipher;
private Buffer16 _iv;
private bool _decrypting;
public AesXtsCipher(ReadOnlySpan<byte> key1, ReadOnlySpan<byte> key2, ReadOnlySpan<byte> iv, bool decrypting)
{
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);
}
}
public void Transform(ReadOnlySpan<byte> input, Span<byte> output)
{
if (_decrypting)
{
Decrypt(input, output);
}
else
{
Encrypt(input, output);
}
}
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];
}
}
}

View file

@ -0,0 +1,186 @@
#if HAS_INTRINSICS
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
using LibHac.Common;
namespace LibHac.Crypto2
{
public class AesXtsCipherHw : ICipher
{
private AesCoreNi _dataAesCore;
private AesCoreNi _tweakAesCore;
private Vector128<byte> _iv;
private bool _decrypting;
public AesXtsCipherHw(ReadOnlySpan<byte> key1, ReadOnlySpan<byte> key2, ReadOnlySpan<byte> iv, bool decrypting)
{
Debug.Assert(key1.Length == AesCrypto.KeySize128);
Debug.Assert(key2.Length == AesCrypto.KeySize128);
Debug.Assert(iv.Length == AesCrypto.KeySize128);
_dataAesCore = new AesCoreNi();
_dataAesCore.Initialize(key1, decrypting);
_tweakAesCore = new AesCoreNi();
_tweakAesCore.Initialize(key2, false);
_iv = Unsafe.ReadUnaligned<Vector128<byte>>(ref MemoryMarshal.GetReference(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;
Debug.Assert(blockCount > 0);
ref Vector128<byte> inBlock = ref Unsafe.As<byte, Vector128<byte>>(ref MemoryMarshal.GetReference(input));
ref Vector128<byte> outBlock = ref Unsafe.As<byte, Vector128<byte>>(ref MemoryMarshal.GetReference(output));
Vector128<byte> mask = Vector128.Create(0x87, 1).AsByte();
Vector128<byte> tweak = _tweakAesCore.EncryptBlock(_iv);
for (int i = 0; i < blockCount; i++)
{
Vector128<byte> tmp = Sse2.Xor(inBlock, tweak);
tmp = _dataAesCore.EncryptBlock(tmp);
outBlock = Sse2.Xor(tmp, tweak);
tweak = Gf128Mul(tweak, mask);
inBlock = ref Unsafe.Add(ref inBlock, 1);
outBlock = ref Unsafe.Add(ref outBlock, 1);
}
if (leftover != 0)
{
EncryptPartialFinalBlock(ref inBlock, ref outBlock, tweak, leftover);
}
}
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;
Debug.Assert(blockCount > 0);
if (leftover != 0) blockCount--;
ref Vector128<byte> inBlock = ref Unsafe.As<byte, Vector128<byte>>(ref MemoryMarshal.GetReference(input));
ref Vector128<byte> outBlock = ref Unsafe.As<byte, Vector128<byte>>(ref MemoryMarshal.GetReference(output));
Vector128<byte> mask = Vector128.Create(0x87, 1).AsByte();
Vector128<byte> tweak = _tweakAesCore.EncryptBlock(_iv);
for (int i = 0; i < blockCount; i++)
{
Vector128<byte> tmp = Sse2.Xor(inBlock, tweak);
tmp = _dataAesCore.DecryptBlock(tmp);
outBlock = Sse2.Xor(tmp, tweak);
tweak = Gf128Mul(tweak, mask);
inBlock = ref Unsafe.Add(ref inBlock, 1);
outBlock = ref Unsafe.Add(ref outBlock, 1);
}
if (leftover != 0)
{
DecryptPartialFinalBlock(ref inBlock, ref outBlock, tweak, mask, leftover);
}
}
// ReSharper disable once RedundantAssignment
private void DecryptPartialFinalBlock(ref Vector128<byte> input, ref Vector128<byte> output,
Vector128<byte> tweak, Vector128<byte> mask, int finalBlockLength)
{
Vector128<byte> finalTweak = Gf128Mul(tweak, mask);
Vector128<byte> tmp = Sse2.Xor(input, finalTweak);
tmp = _dataAesCore.DecryptBlock(tmp);
output = Sse2.Xor(tmp, finalTweak);
var x = new Buffer16();
ref Buffer16 outBuf = ref Unsafe.As<Vector128<byte>, Buffer16>(ref output);
ref Buffer16 nextInBuf = ref Unsafe.As<Vector128<byte>, Buffer16>(ref Unsafe.Add(ref input, 1));
ref Buffer16 nextOutBuf = ref Unsafe.As<Vector128<byte>, Buffer16>(ref Unsafe.Add(ref output, 1));
for (int i = 0; i < finalBlockLength; i++)
{
nextOutBuf[i] = outBuf[i];
x[i] = nextInBuf[i];
}
for (int i = finalBlockLength; i < 16; i++)
{
x[i] = outBuf[i];
}
tmp = Sse2.Xor(x.As<Vector128<byte>>(), tweak);
tmp = _dataAesCore.DecryptBlock(tmp);
output = Sse2.Xor(tmp, tweak);
}
private void EncryptPartialFinalBlock(ref Vector128<byte> input, ref Vector128<byte> output,
Vector128<byte> tweak, int finalBlockLength)
{
ref Vector128<byte> prevOutBlock = ref Unsafe.Subtract(ref output, 1);
var x = new Buffer16();
ref Buffer16 outBuf = ref Unsafe.As<Vector128<byte>, Buffer16>(ref output);
ref Buffer16 inBuf = ref Unsafe.As<Vector128<byte>, Buffer16>(ref input);
ref Buffer16 prevOutBuf = ref Unsafe.As<Vector128<byte>, Buffer16>(ref prevOutBlock);
for (int i = 0; i < finalBlockLength; i++)
{
outBuf[i] = prevOutBuf[i];
x[i] = inBuf[i];
}
for (int i = finalBlockLength; i < 16; i++)
{
x[i] = prevOutBuf[i];
}
Vector128<byte> tmp = Sse2.Xor(x.As<Vector128<byte>>(), tweak);
tmp = _dataAesCore.EncryptBlock(tmp);
prevOutBlock = Sse2.Xor(tmp, tweak);
}
public void Transform(ReadOnlySpan<byte> input, Span<byte> output)
{
if (_decrypting)
{
Decrypt(input, output);
}
else
{
Encrypt(input, output);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector128<byte> Gf128Mul(Vector128<byte> iv, Vector128<byte> mask)
{
Vector128<byte> tmp1 = Sse2.Add(iv.AsUInt64(), iv.AsUInt64()).AsByte();
Vector128<byte> tmp2 = Sse2.Shuffle(iv.AsInt32(), 0x13).AsByte();
tmp2 = Sse2.ShiftRightArithmetic(tmp2.AsInt32(), 31).AsByte();
tmp2 = Sse2.And(mask, tmp2);
return Sse2.Xor(tmp1, tmp2);
}
}
}
#endif

View file

@ -546,6 +546,7 @@ namespace LibHac.FsService
private void CloseReader(SaveDataInfoReader reader)
{
// ReSharper disable once RedundantAssignment
bool wasRemoved = OpenedReaders.Remove(reader);
Debug.Assert(wasRemoved);

View file

@ -145,13 +145,13 @@ namespace LibHac
return true;
}
public static void XorArrays(Span<byte> transformData, Span<byte> xorData)
public static void XorArrays(Span<byte> transformData, ReadOnlySpan<byte> xorData)
{
int sisdStart = 0;
if (Vector.IsHardwareAccelerated)
{
Span<Vector<byte>> dataVec = MemoryMarshal.Cast<byte, Vector<byte>>(transformData);
Span<Vector<byte>> xorVec = MemoryMarshal.Cast<byte, Vector<byte>>(xorData);
ReadOnlySpan<Vector<byte>> xorVec = MemoryMarshal.Cast<byte, Vector<byte>>(xorData);
sisdStart = dataVec.Length * Vector<byte>.Count;
for (int i = 0; i < dataVec.Length; i++)
@ -166,6 +166,33 @@ namespace LibHac
}
}
public static void XorArrays(Span<byte> output, ReadOnlySpan<byte> input1, ReadOnlySpan<byte> input2)
{
int length = Math.Min(input1.Length, input2.Length);
int sisdStart = 0;
if (Vector.IsHardwareAccelerated)
{
int lengthVec = length / Vector<byte>.Count;
Span<Vector<byte>> outputVec = MemoryMarshal.Cast<byte, Vector<byte>>(output);
ReadOnlySpan<Vector<byte>> input1Vec = MemoryMarshal.Cast<byte, Vector<byte>>(input1);
ReadOnlySpan<Vector<byte>> input2Vec = MemoryMarshal.Cast<byte, Vector<byte>>(input2);
sisdStart = lengthVec * Vector<byte>.Count;
for (int i = 0; i < lengthVec; i++)
{
outputVec[i] = input1Vec[i] ^ input2Vec[i];
}
}
for (int i = sisdStart; i < length; i++)
{
output[i] = (byte)(input1[i] ^ input2[i]);
}
}
public static void CopyStream(this Stream input, Stream output, long length, IProgressReport progress = null)
{
const int bufferSize = 0x8000;

View file

@ -116,7 +116,8 @@ namespace hactoolnet
logger.LogMessage($"{label}{averageRate}/s, fastest run: {fastestRate}/s, slowest run: {slowestRate}/s");
}
private static void RunCipherBenchmark(Func<ICipher> cipherNet, Func<ICipher> cipherLibHac, string label, IProgressReport logger)
private static void RunCipherBenchmark(Func<ICipher> cipherNet, Func<ICipher> cipherLibHac, bool benchBlocked,
string label, IProgressReport logger)
{
var srcData = new byte[Size];
@ -131,16 +132,25 @@ namespace hactoolnet
if (AesCrypto.IsAesNiSupported()) CipherBenchmark(srcData, dstDataLh, cipherLibHac, Iterations, "LibHac impl: ", logger);
CipherBenchmark(srcData, dstDataNet, cipherNet, Iterations, ".NET impl: ", logger);
if (AesCrypto.IsAesNiSupported()) CipherBenchmarkBlocked(srcData, dstDataBlockedLh, cipherLibHac, Iterations / 5, "LibHac impl (blocked): ", logger);
if (benchBlocked)
{
if (AesCrypto.IsAesNiSupported())
CipherBenchmarkBlocked(srcData, dstDataBlockedLh, cipherLibHac, Iterations / 5, "LibHac impl (blocked): ", logger);
CipherBenchmarkBlocked(srcData, dstDataBlockedNet, cipherNet, Iterations / 5, ".NET impl (blocked): ", logger);
}
if (AesCrypto.IsAesNiSupported())
{
logger.LogMessage($"{dstDataLh.SequenceEqual(dstDataNet)}");
if (benchBlocked)
{
logger.LogMessage($"{dstDataLh.SequenceEqual(dstDataBlockedLh)}");
logger.LogMessage($"{dstDataLh.SequenceEqual(dstDataBlockedNet)}");
}
}
}
public static void Process(Context ctx)
{
@ -197,12 +207,12 @@ namespace hactoolnet
Func<ICipher> encryptorNet = () => AesCrypto.CreateEcbEncryptor(new byte[0x10], true);
Func<ICipher> encryptorLh = () => AesCrypto.CreateEcbEncryptor(new byte[0x10]);
RunCipherBenchmark(encryptorNet, encryptorLh, "AES-ECB encrypt", ctx.Logger);
RunCipherBenchmark(encryptorNet, encryptorLh, true, "AES-ECB encrypt", ctx.Logger);
Func<ICipher> decryptorNet = () => AesCrypto.CreateEcbDecryptor(new byte[0x10], true);
Func<ICipher> decryptorLh = () => AesCrypto.CreateEcbDecryptor(new byte[0x10]);
RunCipherBenchmark(decryptorNet, decryptorLh, "AES-ECB decrypt", ctx.Logger);
RunCipherBenchmark(decryptorNet, decryptorLh, true, "AES-ECB decrypt", ctx.Logger);
break;
}
@ -211,12 +221,12 @@ namespace hactoolnet
Func<ICipher> encryptorNet = () => AesCrypto.CreateCbcEncryptor(new byte[0x10], new byte[0x10], true);
Func<ICipher> encryptorLh = () => AesCrypto.CreateCbcEncryptor(new byte[0x10], new byte[0x10]);
RunCipherBenchmark(encryptorNet, encryptorLh, "AES-CBC encrypt", ctx.Logger);
RunCipherBenchmark(encryptorNet, encryptorLh, true, "AES-CBC encrypt", ctx.Logger);
Func<ICipher> decryptorNet = () => AesCrypto.CreateCbcDecryptor(new byte[0x10], new byte[0x10], true);
Func<ICipher> decryptorLh = () => AesCrypto.CreateCbcDecryptor(new byte[0x10], new byte[0x10]);
RunCipherBenchmark(decryptorNet, decryptorLh, "AES-CBC decrypt", ctx.Logger);
RunCipherBenchmark(decryptorNet, decryptorLh, true, "AES-CBC decrypt", ctx.Logger);
break;
}
@ -226,7 +236,21 @@ namespace hactoolnet
Func<ICipher> encryptorNet = () => AesCrypto.CreateCtrEncryptor(new byte[0x10], new byte[0x10], true);
Func<ICipher> encryptorLh = () => AesCrypto.CreateCtrEncryptor(new byte[0x10], new byte[0x10]);
RunCipherBenchmark(encryptorNet, encryptorLh, "AES-CTR", ctx.Logger);
RunCipherBenchmark(encryptorNet, encryptorLh, true, "AES-CTR", ctx.Logger);
break;
}
case "aesxtsnew":
{
Func<ICipher> encryptorNet = () => AesCrypto.CreateXtsEncryptor(new byte[0x10], new byte[0x10], new byte[0x10], true);
Func<ICipher> encryptorLh = () => AesCrypto.CreateXtsEncryptor(new byte[0x10], new byte[0x10], new byte[0x10]);
RunCipherBenchmark(encryptorNet, encryptorLh, false, "AES-XTS encrypt", ctx.Logger);
Func<ICipher> decryptorNet = () => AesCrypto.CreateXtsDecryptor(new byte[0x10], new byte[0x10], new byte[0x10], true);
Func<ICipher> decryptorLh = () => AesCrypto.CreateXtsDecryptor(new byte[0x10], new byte[0x10], new byte[0x10]);
RunCipherBenchmark(decryptorNet, decryptorLh, false, "AES-XTS decrypt", ctx.Logger);
break;
}

View file

@ -0,0 +1,88 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using LibHac.Common;
using Xunit;
namespace LibHac.Tests
{
public class BufferStructTests
{
[Fact]
public static void BufferIndexer()
{
var buffer = new Buffer16();
buffer[0] = 5;
buffer[1] = 6;
Assert.Equal(5, buffer[0]);
Assert.Equal(6, buffer[1]);
}
[Fact]
public static void CastBufferToByteSpan()
{
var buffer = new Buffer16();
Span<byte> byteSpan = buffer.Bytes;
Assert.Equal(16, byteSpan.Length);
Assert.True(Unsafe.AreSame(ref Unsafe.As<Buffer16, byte>(ref buffer), ref byteSpan[0]));
}
[Fact]
public static void CastBufferToByteSpanImplicit()
{
var buffer = new Buffer16();
Span<byte> byteSpan = buffer;
Assert.Equal(16, byteSpan.Length);
Assert.True(Unsafe.AreSame(ref Unsafe.As<Buffer16, byte>(ref buffer), ref byteSpan[0]));
}
[Fact]
public static void CastBufferToReadOnlyByteSpanImplicit()
{
var buffer = new Buffer16();
ReadOnlySpan<byte> byteSpan = buffer;
Assert.Equal(16, byteSpan.Length);
Assert.True(Unsafe.AreSame(ref Unsafe.As<Buffer16, byte>(ref buffer), ref Unsafe.AsRef(byteSpan[0])));
}
[Fact]
public static void CastBufferToSpan()
{
var buffer = new Buffer16();
Span<ulong> ulongSpan = buffer.AsSpan<ulong>();
Assert.Equal(2, ulongSpan.Length);
Assert.True(Unsafe.AreSame(ref Unsafe.As<Buffer16, ulong>(ref buffer), ref ulongSpan[0]));
}
[Fact]
public static void CastBufferToStruct()
{
var buffer = new Buffer16();
ref ulong ulongSpan = ref buffer.As<ulong>();
Assert.True(Unsafe.AreSame(ref Unsafe.As<Buffer16, ulong>(ref buffer), ref ulongSpan));
}
[Fact]
public static void CastBufferToLargerStruct()
{
var buffer = new Buffer16();
Assert.Throws<ArgumentException>(() => buffer.As<Struct32Bytes>());
}
[StructLayout(LayoutKind.Sequential, Size = 32)]
private struct Struct32Bytes { }
}
}

View file

@ -0,0 +1,75 @@
using System;
using System.Collections.Generic;
using System.Linq;
using LibHac.Crypto2;
using Xunit;
namespace LibHac.Tests.CryptoTests
{
public class AesXtsTests
{
public static TheoryData<EncryptionTestVector> EncryptTestVectors =
RemovePartialByteTests(RspReader.ReadTestVectors(true, "XTSGenAES128.rsp"));
public static TheoryData<EncryptionTestVector> DecryptTestVectors =
RemovePartialByteTests(RspReader.ReadTestVectors(false, "XTSGenAES128.rsp"));
// The XTS implementation only supports multiples of whole bytes
private static TheoryData<EncryptionTestVector> RemovePartialByteTests(TheoryData<EncryptionTestVector> input)
{
IEnumerable<EncryptionTestVector> filteredTestVectors = input
.Select(x => x[0])
.Cast<EncryptionTestVector>()
.Where(x => x.DataUnitLength % 8 == 0);
var output = new TheoryData<EncryptionTestVector>();
foreach (EncryptionTestVector item in filteredTestVectors)
{
output.Add(item);
}
return output;
}
[Theory]
[MemberData(nameof(EncryptTestVectors))]
public static void Encrypt(EncryptionTestVector tv)
{
Span<byte> key1 = tv.Key.AsSpan(0, 0x10);
Span<byte> key2 = tv.Key.AsSpan(0x10, 0x10);
Common.CipherTestCore(tv.PlainText, tv.CipherText, AesCrypto.CreateXtsEncryptor(key1, key2, tv.Iv, true));
}
[Theory]
[MemberData(nameof(DecryptTestVectors))]
public static void Decrypt(EncryptionTestVector tv)
{
Span<byte> key1 = tv.Key.AsSpan(0, 0x10);
Span<byte> key2 = tv.Key.AsSpan(0x10, 0x10);
Common.CipherTestCore(tv.CipherText, tv.PlainText, AesCrypto.CreateXtsDecryptor(key1, key2, tv.Iv, true));
}
[AesIntrinsicsRequiredTheory]
[MemberData(nameof(EncryptTestVectors))]
public static void EncryptIntrinsics(EncryptionTestVector tv)
{
Span<byte> key1 = tv.Key.AsSpan(0, 0x10);
Span<byte> key2 = tv.Key.AsSpan(0x10, 0x10);
Common.CipherTestCore(tv.PlainText, tv.CipherText, AesCrypto.CreateXtsEncryptor(key1, key2, tv.Iv));
}
[AesIntrinsicsRequiredTheory]
[MemberData(nameof(DecryptTestVectors))]
public static void DecryptIntrinsics(EncryptionTestVector tv)
{
Span<byte> key1 = tv.Key.AsSpan(0, 0x10);
Span<byte> key2 = tv.Key.AsSpan(0x10, 0x10);
Common.CipherTestCore(tv.CipherText, tv.PlainText, AesCrypto.CreateXtsDecryptor(key1, key2, tv.Iv));
}
}
}

View file

@ -56,21 +56,27 @@ namespace LibHac.Tests.CryptoTests
canOutputVector = true;
switch (kvp[0])
switch (kvp[0].ToUpperInvariant())
{
case "COUNT":
testVector.Count = int.Parse(kvp[1]);
break;
case "DATAUNITLEN":
testVector.DataUnitLength = int.Parse(kvp[1]);
break;
case "KEY":
testVector.Key = kvp[1].ToBytes();
break;
case "IV":
case "I":
testVector.Iv = kvp[1].ToBytes();
break;
case "PLAINTEXT":
case "PT":
testVector.PlainText = kvp[1].ToBytes();
break;
case "CIPHERTEXT":
case "CT":
testVector.CipherText = kvp[1].ToBytes();
break;
}
@ -103,6 +109,7 @@ namespace LibHac.Tests.CryptoTests
{
public bool Encrypt { get; set; }
public int Count { get; set; }
public int DataUnitLength { get; set; }
public byte[] Key { get; set; }
public byte[] Iv { get; set; }
public byte[] PlainText { get; set; }

File diff suppressed because it is too large Load diff