mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2024-11-14 10:49:41 +01:00
Add XTS mode and remove duplicate code
This commit is contained in:
parent
df27d2a83b
commit
8b47be19c2
17 changed files with 8923 additions and 150 deletions
68
src/LibHac/Common/Buffer.cs
Normal file
68
src/LibHac/Common/Buffer.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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>());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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); }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
208
src/LibHac/Crypto2/AesXtsMode.cs
Normal file
208
src/LibHac/Crypto2/AesXtsMode.cs
Normal 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];
|
||||
}
|
||||
}
|
||||
}
|
186
src/LibHac/Crypto2/AesXtsModeHw.cs
Normal file
186
src/LibHac/Crypto2/AesXtsModeHw.cs
Normal 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
|
|
@ -546,6 +546,7 @@ namespace LibHac.FsService
|
|||
|
||||
private void CloseReader(SaveDataInfoReader reader)
|
||||
{
|
||||
// ReSharper disable once RedundantAssignment
|
||||
bool wasRemoved = OpenedReaders.Remove(reader);
|
||||
|
||||
Debug.Assert(wasRemoved);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
88
tests/LibHac.Tests/BufferStructTests.cs
Normal file
88
tests/LibHac.Tests/BufferStructTests.cs
Normal 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 { }
|
||||
}
|
||||
}
|
75
tests/LibHac.Tests/CryptoTests/AesXtsTests.cs
Normal file
75
tests/LibHac.Tests/CryptoTests/AesXtsTests.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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; }
|
||||
|
|
8013
tests/LibHac.Tests/CryptoTests/TestVectors/XTSGenAES128.rsp
Normal file
8013
tests/LibHac.Tests/CryptoTests/TestVectors/XTSGenAES128.rsp
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue