Avoid allocations when doing encryption with AES-NI

This commit is contained in:
Alex Barney 2019-11-17 10:59:46 -07:00
parent df646fb503
commit 2752a7c3db
16 changed files with 302 additions and 111 deletions

View file

@ -1,6 +1,9 @@
using System;
using System.Runtime.CompilerServices;
#if NETCOREAPP
using System.Runtime.InteropServices;
#endif
namespace LibHac.Common
{

View file

@ -1,7 +1,9 @@
using System;
// ReSharper disable AssignmentIsFullyDiscarded
using System;
#if HAS_INTRINSICS
using System.Runtime.Intrinsics.X86;
using LibHac.Crypto2.Detail;
#endif
namespace LibHac.Crypto2
@ -25,7 +27,7 @@ namespace LibHac.Crypto2
#if HAS_INTRINSICS
if (IsAesNiSupported() && !preferDotNetCrypto)
{
return new AesEcbDecryptorHw(key);
return new AesEcbDecryptorNi(key);
}
#endif
return new AesEcbDecryptor(key);
@ -36,7 +38,7 @@ namespace LibHac.Crypto2
#if HAS_INTRINSICS
if (IsAesNiSupported() && !preferDotNetCrypto)
{
return new AesEcbEncryptorHw(key);
return new AesEcbEncryptorNi(key);
}
#endif
return new AesEcbEncryptor(key);
@ -47,7 +49,7 @@ namespace LibHac.Crypto2
#if HAS_INTRINSICS
if (IsAesNiSupported() && !preferDotNetCrypto)
{
return new AesCbcDecryptorHw(key, iv);
return new AesCbcDecryptorNi(key, iv);
}
#endif
return new AesCbcDecryptor(key, iv);
@ -58,7 +60,7 @@ namespace LibHac.Crypto2
#if HAS_INTRINSICS
if (IsAesNiSupported() && !preferDotNetCrypto)
{
return new AesCbcEncryptorHw(key, iv);
return new AesCbcEncryptorNi(key, iv);
}
#endif
return new AesCbcEncryptor(key, iv);
@ -66,6 +68,12 @@ namespace LibHac.Crypto2
public static ICipher CreateCtrDecryptor(ReadOnlySpan<byte> key, ReadOnlySpan<byte> iv, bool preferDotNetCrypto = false)
{
#if HAS_INTRINSICS
if (IsAesNiSupported() && !preferDotNetCrypto)
{
return new AesCtrCipherNi(key, iv);
}
#endif
// Encryption and decryption in counter mode is the same operation
return CreateCtrEncryptor(key, iv, preferDotNetCrypto);
}
@ -75,7 +83,7 @@ namespace LibHac.Crypto2
#if HAS_INTRINSICS
if (IsAesNiSupported() && !preferDotNetCrypto)
{
return new AesCtrEncryptorHw(key, iv);
return new AesCtrCipherNi(key, iv);
}
#endif
return new AesCtrEncryptor(key, iv);
@ -87,7 +95,7 @@ namespace LibHac.Crypto2
#if HAS_INTRINSICS
if (IsAesNiSupported() && !preferDotNetCrypto)
{
return new AesXtsCipherHw(key1, key2, iv, true);
return new AesXtsDecryptorNi(key1, key2, iv);
}
#endif
return new AesXtsCipher(key1, key2, iv, true);
@ -99,7 +107,7 @@ namespace LibHac.Crypto2
#if HAS_INTRINSICS
if (IsAesNiSupported() && !preferDotNetCrypto)
{
return new AesXtsCipherHw(key1, key2, iv, false);
return new AesXtsEncryptorNi(key1, key2, iv);
}
#endif
return new AesXtsCipher(key1, key2, iv, false);
@ -108,6 +116,17 @@ namespace LibHac.Crypto2
public static void EncryptEcb128(ReadOnlySpan<byte> input, Span<byte> output, ReadOnlySpan<byte> key,
bool preferDotNetCrypto = false)
{
#if HAS_INTRINSICS
if (IsAesNiSupported() && !preferDotNetCrypto)
{
AesEcbModeNi cipherNi;
unsafe { _ = &cipherNi; } // workaround for CS0165
cipherNi.Initialize(key, false);
cipherNi.Encrypt(input, output);
return;
}
#endif
ICipher cipher = CreateEcbEncryptor(key, preferDotNetCrypto);
cipher.Transform(input, output);
@ -116,6 +135,17 @@ namespace LibHac.Crypto2
public static void DecryptEcb128(ReadOnlySpan<byte> input, Span<byte> output, ReadOnlySpan<byte> key,
bool preferDotNetCrypto = false)
{
#if HAS_INTRINSICS
if (IsAesNiSupported() && !preferDotNetCrypto)
{
AesEcbModeNi cipherNi;
unsafe { _ = &cipherNi; } // workaround for CS0165
cipherNi.Initialize(key, true);
cipherNi.Decrypt(input, output);
return;
}
#endif
ICipher cipher = CreateEcbDecryptor(key, preferDotNetCrypto);
cipher.Transform(input, output);
@ -124,6 +154,17 @@ namespace LibHac.Crypto2
public static void EncryptCbc128(ReadOnlySpan<byte> input, Span<byte> output, ReadOnlySpan<byte> key,
ReadOnlySpan<byte> iv, bool preferDotNetCrypto = false)
{
#if HAS_INTRINSICS
if (IsAesNiSupported() && !preferDotNetCrypto)
{
AesCbcModeNi cipherNi;
unsafe { _ = &cipherNi; } // workaround for CS0165
cipherNi.Initialize(key, iv, false);
cipherNi.Encrypt(input, output);
return;
}
#endif
ICipher cipher = CreateCbcEncryptor(key, iv, preferDotNetCrypto);
cipher.Transform(input, output);
@ -132,6 +173,17 @@ namespace LibHac.Crypto2
public static void DecryptCbc128(ReadOnlySpan<byte> input, Span<byte> output, ReadOnlySpan<byte> key,
ReadOnlySpan<byte> iv, bool preferDotNetCrypto = false)
{
#if HAS_INTRINSICS
if (IsAesNiSupported() && !preferDotNetCrypto)
{
AesCbcModeNi cipherNi;
unsafe { _ = &cipherNi; } // workaround for CS0165
cipherNi.Initialize(key, iv, true);
cipherNi.Decrypt(input, output);
return;
}
#endif
ICipher cipher = CreateCbcDecryptor(key, iv, preferDotNetCrypto);
cipher.Transform(input, output);
@ -140,6 +192,17 @@ namespace LibHac.Crypto2
public static void EncryptCtr128(ReadOnlySpan<byte> input, Span<byte> output, ReadOnlySpan<byte> key,
ReadOnlySpan<byte> iv, bool preferDotNetCrypto = false)
{
#if HAS_INTRINSICS
if (IsAesNiSupported() && !preferDotNetCrypto)
{
AesCtrModeNi cipherNi;
unsafe { _ = &cipherNi; } // workaround for CS0165
cipherNi.Initialize(key, iv);
cipherNi.Transform(input, output);
return;
}
#endif
ICipher cipher = CreateCtrEncryptor(key, iv, preferDotNetCrypto);
cipher.Transform(input, output);
@ -148,6 +211,17 @@ namespace LibHac.Crypto2
public static void DecryptCtr128(ReadOnlySpan<byte> input, Span<byte> output, ReadOnlySpan<byte> key,
ReadOnlySpan<byte> iv, bool preferDotNetCrypto = false)
{
#if HAS_INTRINSICS
if (IsAesNiSupported() && !preferDotNetCrypto)
{
AesCtrModeNi cipherNi;
unsafe { _ = &cipherNi; } // workaround for CS0165
cipherNi.Initialize(key, iv);
cipherNi.Transform(input, output);
return;
}
#endif
ICipher cipher = CreateCtrDecryptor(key, iv, preferDotNetCrypto);
cipher.Transform(input, output);
@ -156,6 +230,17 @@ namespace LibHac.Crypto2
public static void EncryptXts128(ReadOnlySpan<byte> input, Span<byte> output, ReadOnlySpan<byte> key1,
ReadOnlySpan<byte> key2, ReadOnlySpan<byte> iv, bool preferDotNetCrypto = false)
{
#if HAS_INTRINSICS
if (IsAesNiSupported() && !preferDotNetCrypto)
{
AesXtsModeNi cipherNi;
unsafe { _ = &cipherNi; } // workaround for CS0165
cipherNi.Initialize(key1, key2, iv, false);
cipherNi.Encrypt(input, output);
return;
}
#endif
ICipher cipher = CreateXtsEncryptor(key1, key2, iv, preferDotNetCrypto);
cipher.Transform(input, output);
@ -164,6 +249,17 @@ namespace LibHac.Crypto2
public static void DecryptXts128(ReadOnlySpan<byte> input, Span<byte> output, ReadOnlySpan<byte> key1,
ReadOnlySpan<byte> key2, ReadOnlySpan<byte> iv, bool preferDotNetCrypto = false)
{
#if HAS_INTRINSICS
if (IsAesNiSupported() && !preferDotNetCrypto)
{
AesXtsModeNi cipherNi;
unsafe { _ = &cipherNi; } // workaround for CS0165
cipherNi.Initialize(key1, key2, iv, true);
cipherNi.Decrypt(input, output);
return;
}
#endif
ICipher cipher = CreateXtsDecryptor(key1, key2, iv, preferDotNetCrypto);
cipher.Transform(input, output);

View file

@ -0,0 +1,39 @@
#if HAS_INTRINSICS
using System;
using LibHac.Crypto2.Detail;
namespace LibHac.Crypto2
{
public struct AesCbcEncryptorNi : ICipher
{
private AesCbcModeNi _baseCipher;
public AesCbcEncryptorNi(ReadOnlySpan<byte> key, ReadOnlySpan<byte> iv)
{
_baseCipher = new AesCbcModeNi();
_baseCipher.Initialize(key, iv, false);
}
public void Transform(ReadOnlySpan<byte> input, Span<byte> output)
{
_baseCipher.Encrypt(input, output);
}
}
public struct AesCbcDecryptorNi : ICipher
{
private AesCbcModeNi _baseCipher;
public AesCbcDecryptorNi(ReadOnlySpan<byte> key, ReadOnlySpan<byte> iv)
{
_baseCipher = new AesCbcModeNi();
_baseCipher.Initialize(key, iv, true);
}
public void Transform(ReadOnlySpan<byte> input, Span<byte> output)
{
_baseCipher.Decrypt(input, output);
}
}
}
#endif

View file

@ -0,0 +1,23 @@
#if HAS_INTRINSICS
using System;
using LibHac.Crypto2.Detail;
namespace LibHac.Crypto2
{
public class AesCtrCipherNi : ICipher
{
private AesCtrModeNi _baseCipher;
public AesCtrCipherNi(ReadOnlySpan<byte> key, ReadOnlySpan<byte> iv)
{
_baseCipher = new AesCtrModeNi();
_baseCipher.Initialize(key, iv);
}
public void Transform(ReadOnlySpan<byte> input, Span<byte> output)
{
_baseCipher.Transform(input, output);
}
}
}
#endif

View file

@ -0,0 +1,39 @@
#if HAS_INTRINSICS
using System;
using LibHac.Crypto2.Detail;
namespace LibHac.Crypto2
{
public class AesEcbEncryptorNi : ICipher
{
private AesEcbModeNi _baseCipher;
public AesEcbEncryptorNi(ReadOnlySpan<byte> key)
{
_baseCipher = new AesEcbModeNi();
_baseCipher.Initialize(key, false);
}
public void Transform(ReadOnlySpan<byte> input, Span<byte> output)
{
_baseCipher.Encrypt(input, output);
}
}
public class AesEcbDecryptorNi : ICipher
{
private AesEcbModeNi _baseCipher;
public AesEcbDecryptorNi(ReadOnlySpan<byte> key)
{
_baseCipher = new AesEcbModeNi();
_baseCipher.Initialize(key, true);
}
public void Transform(ReadOnlySpan<byte> input, Span<byte> output)
{
_baseCipher.Decrypt(input, output);
}
}
}
#endif

View file

@ -1,38 +0,0 @@
#if HAS_INTRINSICS
using System;
namespace LibHac.Crypto2
{
public class AesEcbEncryptorHw : ICipher
{
private AesCoreNi _aesCore;
public AesEcbEncryptorHw(ReadOnlySpan<byte> key)
{
_aesCore = new AesCoreNi();
_aesCore.Initialize(key, false);
}
public void Transform(ReadOnlySpan<byte> input, Span<byte> output)
{
_aesCore.Encrypt(input, output);
}
}
public class AesEcbDecryptorHw : ICipher
{
private AesCoreNi _aesCore;
public AesEcbDecryptorHw(ReadOnlySpan<byte> key)
{
_aesCore = new AesCoreNi();
_aesCore.Initialize(key, true);
}
public void Transform(ReadOnlySpan<byte> input, Span<byte> output)
{
_aesCore.Decrypt(input, output);
}
}
}
#endif

View file

@ -0,0 +1,39 @@
#if HAS_INTRINSICS
using System;
using LibHac.Crypto2.Detail;
namespace LibHac.Crypto2
{
public class AesXtsEncryptorNi : ICipher
{
private AesXtsModeNi _baseCipher;
public AesXtsEncryptorNi(ReadOnlySpan<byte> key1, ReadOnlySpan<byte> key2, ReadOnlySpan<byte> iv)
{
_baseCipher = new AesXtsModeNi();
_baseCipher.Initialize(key1, key2, iv, false);
}
public void Transform(ReadOnlySpan<byte> input, Span<byte> output)
{
_baseCipher.Encrypt(input, output);
}
}
public class AesXtsDecryptorNi : ICipher
{
private AesXtsModeNi _baseCipher;
public AesXtsDecryptorNi(ReadOnlySpan<byte> key1, ReadOnlySpan<byte> key2, ReadOnlySpan<byte> iv)
{
_baseCipher = new AesXtsModeNi();
_baseCipher.Initialize(key1, key2, iv, true);
}
public void Transform(ReadOnlySpan<byte> input, Span<byte> output)
{
_baseCipher.Decrypt(input, output);
}
}
}
#endif

View file

@ -1,28 +1,28 @@
#if HAS_INTRINSICS
using System;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
namespace LibHac.Crypto2
namespace LibHac.Crypto2.Detail
{
public struct AesCbcEncryptorHw : ICipher
public struct AesCbcModeNi
{
#pragma warning disable 649
private AesCoreNi _aesCore;
#pragma warning restore 649
private Vector128<byte> _iv;
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
public AesCbcEncryptorHw(ReadOnlySpan<byte> key, ReadOnlySpan<byte> iv)
public void Initialize(ReadOnlySpan<byte> key, ReadOnlySpan<byte> iv, bool isDecrypting)
{
_aesCore = new AesCoreNi();
_aesCore.Initialize(key, false);
_aesCore.Initialize(key, isDecrypting);
_iv = Unsafe.ReadUnaligned<Vector128<byte>>(ref MemoryMarshal.GetReference(iv));
}
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
public void Transform(ReadOnlySpan<byte> input, Span<byte> output)
public void Encrypt(ReadOnlySpan<byte> input, Span<byte> output)
{
int blockCount = Math.Min(input.Length, output.Length) >> 4;
@ -43,24 +43,8 @@ namespace LibHac.Crypto2
_iv = iv;
}
}
public struct AesCbcDecryptorHw : ICipher
{
private AesCoreNi _aesCore;
private Vector128<byte> _iv;
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
public AesCbcDecryptorHw(ReadOnlySpan<byte> key, ReadOnlySpan<byte> iv)
{
_aesCore = new AesCoreNi();
_aesCore.Initialize(key, true);
_iv = Unsafe.ReadUnaligned<Vector128<byte>>(ref MemoryMarshal.GetReference(iv));
}
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
public void Transform(ReadOnlySpan<byte> input, Span<byte> output)
public void Decrypt(ReadOnlySpan<byte> input, Span<byte> output)
{
int blockCount = Math.Min(input.Length, output.Length) >> 4;

View file

@ -1,11 +1,11 @@
#if NETCOREAPP
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
using System;
using System.Runtime.CompilerServices;
namespace LibHac.Crypto2
namespace LibHac.Crypto2.Detail
{
[StructLayout(LayoutKind.Sequential, Size = RoundKeyCount * RoundKeySize)]
public struct AesCoreNi
@ -15,13 +15,6 @@ namespace LibHac.Crypto2
private Vector128<byte> _roundKeys;
public AesCoreNi(ReadOnlySpan<byte> key, bool isDecrypting)
{
_roundKeys = default;
KeyExpansion(key, MemoryMarshal.CreateSpan(ref _roundKeys, RoundKeyCount), isDecrypting);
}
public void Initialize(ReadOnlySpan<byte> key, bool isDecrypting)
{
KeyExpansion(key, MemoryMarshal.CreateSpan(ref _roundKeys, RoundKeyCount), isDecrypting);
@ -30,7 +23,6 @@ namespace LibHac.Crypto2
public readonly ReadOnlySpan<Vector128<byte>> RoundKeys =>
MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef(in _roundKeys), RoundKeyCount);
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
public readonly void Encrypt(ReadOnlySpan<byte> input, Span<byte> output)
{

View file

@ -5,17 +5,18 @@ using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
namespace LibHac.Crypto2
namespace LibHac.Crypto2.Detail
{
public class AesCtrEncryptorHw : ICipher
public struct AesCtrModeNi
{
#pragma warning disable 649
private AesCoreNi _aesCore;
#pragma warning restore 649
private Vector128<byte> _iv;
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
public AesCtrEncryptorHw(ReadOnlySpan<byte> key, ReadOnlySpan<byte> iv)
public void Initialize(ReadOnlySpan<byte> key, ReadOnlySpan<byte> iv)
{
_aesCore = new AesCoreNi();
_aesCore.Initialize(key, false);
_iv = Unsafe.ReadUnaligned<Vector128<byte>>(ref MemoryMarshal.GetReference(iv));

View file

@ -0,0 +1,28 @@
#if HAS_INTRINSICS
using System;
namespace LibHac.Crypto2.Detail
{
public struct AesEcbModeNi
{
#pragma warning disable 649
private AesCoreNi _aesCore;
#pragma warning restore 649
public void Initialize(ReadOnlySpan<byte> key, bool isDecrypting)
{
_aesCore.Initialize(key, 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);
}
}
}
#endif

View file

@ -7,30 +7,27 @@ using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
using LibHac.Common;
namespace LibHac.Crypto2
namespace LibHac.Crypto2.Detail
{
public class AesXtsCipherHw : ICipher
public struct AesXtsModeNi
{
#pragma warning disable 649
private AesCoreNi _dataAesCore;
private AesCoreNi _tweakAesCore;
private Vector128<byte> _iv;
private bool _decrypting;
#pragma warning restore 649
public AesXtsCipherHw(ReadOnlySpan<byte> key1, ReadOnlySpan<byte> key2, ReadOnlySpan<byte> iv, bool decrypting)
private Vector128<byte> _iv;
public void Initialize(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)
@ -158,18 +155,6 @@ namespace LibHac.Crypto2
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)
{