diff --git a/src/LibHac/Crypto/Aes.cs b/src/LibHac/Crypto/Aes.cs
index f24cb4ef..17652cf4 100644
--- a/src/LibHac/Crypto/Aes.cs
+++ b/src/LibHac/Crypto/Aes.cs
@@ -1,6 +1,6 @@
// ReSharper disable AssignmentIsFullyDiscarded
using System;
-
+using LibHac.Diag;
#if HAS_INTRINSICS
using LibHac.Crypto.Detail;
@@ -265,5 +265,73 @@ namespace LibHac.Crypto
cipher.Transform(input, output);
}
+
+ ///
+ /// Computes the CMAC of the provided data using AES-128.
+ ///
+ /// The buffer where the generated MAC will be placed. Must be at least 16 bytes long.
+ /// The message on which the MAC will be calculated.
+ /// The 128-bit AES key used to calculate the MAC.
+ /// https://tools.ietf.org/html/rfc4493
+ public static void CalculateCmac(Span mac, ReadOnlySpan data, ReadOnlySpan key)
+ {
+ ReadOnlySpan zero = stackalloc byte[16];
+ int len = data.Length;
+
+ // Step 1, AES-128 with key K is applied to an all-zero input block.
+ Span l = stackalloc byte[16];
+ EncryptCbc128(zero, l, key, zero);
+
+ // Step 2, K1 is derived through the following operation:
+ Span k1 = stackalloc byte[16];
+ LeftShiftBytes(l, k1);
+ if ((l[0] & 0x80) == 0x80) // If the most significant bit of L is equal to 0, K1 is the left-shift of L by 1 bit.
+ k1[15] ^= 0x87; // Otherwise, K1 is the XOR of const_Rb and the left-shift of L by 1 bit.
+
+ // Step 3, K2 is derived through the following operation:
+ Span k2 = stackalloc byte[16];
+ LeftShiftBytes(k1, k2);
+ if ((k1[0] & 0x80) == 0x80) // If the most significant bit of K1 is equal to 0, K2 is the left-shift of K1 by 1 bit.
+ k2[15] ^= 0x87; // Otherwise, K2 is the XOR of const_Rb and the left-shift of K1 by 1 bit.
+
+ // ReSharper disable once RedundantAssignment
+ Span paddedMessage = l;
+
+ if (len != 0 && len % 16 == 0) // If the size of the input message block is equal to a positive multiple of the block size (namely, 128 bits),
+ { // the last block shall be XOR'ed with K1 before processing
+ paddedMessage = len < 0x800 ? stackalloc byte[len] : new byte[len];
+ data.CopyTo(paddedMessage);
+
+ for (int j = 0; j < k1.Length; j++)
+ paddedMessage[paddedMessage.Length - 16 + j] ^= k1[j];
+ }
+ else // Otherwise, the last block shall be padded with 10^i and XOR'ed with K2.
+ {
+ int paddedLength = len + (16 - len % 16);
+ paddedMessage = paddedLength < 0x800 ? stackalloc byte[paddedLength] : new byte[paddedLength];
+ paddedMessage[len] = 0x80;
+ data.CopyTo(paddedMessage);
+
+ for (int j = 0; j < k2.Length; j++)
+ paddedMessage[paddedMessage.Length - 16 + j] ^= k2[j];
+ }
+
+ EncryptCbc128(paddedMessage, paddedMessage, key, zero); // The result of the previous process will be the input of the last encryption.
+ paddedMessage.Slice(paddedMessage.Length - 0x10).CopyTo(mac);
+ }
+
+ private static void LeftShiftBytes(ReadOnlySpan input, Span output)
+ {
+ Assert.AssertTrue(output.Length >= input.Length);
+
+ byte carry = 0;
+
+ for (int i = input.Length - 1; i >= 0; i--)
+ {
+ ushort b = (ushort)(input[i] << 1);
+ output[i] = (byte)((b & 0xff) + carry);
+ carry = (byte)((b & 0xff00) >> 8);
+ }
+ }
}
}
diff --git a/src/LibHac/Crypto/Rsa.cs b/src/LibHac/Crypto/Rsa.cs
index 7370998f..2ecbf826 100644
--- a/src/LibHac/Crypto/Rsa.cs
+++ b/src/LibHac/Crypto/Rsa.cs
@@ -1,4 +1,5 @@
using System;
+using System.Numerics;
using System.Security.Cryptography;
namespace LibHac.Crypto
@@ -6,7 +7,15 @@ namespace LibHac.Crypto
public static class Rsa
{
public static bool VerifyRsa2048PssSha256(ReadOnlySpan signature, ReadOnlySpan modulus,
- ReadOnlySpan exponent, ReadOnlySpan message)
+ ReadOnlySpan exponent, ReadOnlySpan message) =>
+ VerifyRsa2048Sha256(signature, modulus, exponent, message, RSASignaturePadding.Pss);
+
+ public static bool VerifyRsa2048Pkcs1Sha256(ReadOnlySpan signature, ReadOnlySpan modulus,
+ ReadOnlySpan exponent, ReadOnlySpan message) =>
+ VerifyRsa2048Sha256(signature, modulus, exponent, message, RSASignaturePadding.Pkcs1);
+
+ private static bool VerifyRsa2048Sha256(ReadOnlySpan signature, ReadOnlySpan modulus,
+ ReadOnlySpan exponent, ReadOnlySpan message, RSASignaturePadding padding)
{
try
{
@@ -14,7 +23,7 @@ namespace LibHac.Crypto
using (var rsa = RSA.Create(param))
{
- return rsa.VerifyData(message, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pss);
+ return rsa.VerifyData(message, signature, HashAlgorithmName.SHA256, padding);
}
}
catch (CryptographicException)
@@ -40,5 +49,129 @@ namespace LibHac.Crypto
return false;
}
}
+
+ /// The RSA Modulus (n)
+ /// The RSA Public Exponent (e)
+ /// The RSA Private Exponent (d)
+ public static RSAParameters RecoverParameters(BigInteger n, BigInteger e, BigInteger d)
+ {
+ (BigInteger p, BigInteger q) = DeriveRsaPrimeNumberPair(n, e, d);
+
+ BigInteger dp = d % (p - BigInteger.One);
+ BigInteger dq = d % (q - BigInteger.One);
+ BigInteger inverseQ = Utilities.ModInverse(q, p);
+
+ int modLen = n.ToByteArray().Length;
+ int halfModLen = (modLen + 1) / 2;
+
+ return new RSAParameters
+ {
+ Modulus = n.GetBytes(modLen),
+ Exponent = e.GetBytes(-1),
+ D = d.GetBytes(modLen),
+ P = p.GetBytes(halfModLen),
+ Q = q.GetBytes(halfModLen),
+ DP = dp.GetBytes(halfModLen),
+ DQ = dq.GetBytes(halfModLen),
+ InverseQ = inverseQ.GetBytes(halfModLen)
+ };
+ }
+
+ /// The RSA Modulus (n)
+ /// The RSA Public Exponent (e)
+ /// The RSA Private Exponent (d)
+ public static RSAParameters RecoverParameters(ReadOnlySpan n, ReadOnlySpan e, ReadOnlySpan d) =>
+ RecoverParameters(n.GetBigInteger(), e.GetBigInteger(), d.GetBigInteger());
+
+ ///
+ /// Derive RSA Prime Number Pair (p, q) from RSA Modulus (n), RSA Public Exponent (e) and RSA Private Exponent (d)
+ ///
+ /// The RSA Modulus (n)
+ /// The RSA Public Exponent (e)
+ /// The RSA Private Exponent (d)
+ /// RSA Prime Number Pair
+ private static (BigInteger p, BigInteger q) DeriveRsaPrimeNumberPair(BigInteger n, BigInteger e, BigInteger d)
+ {
+ BigInteger k = d * e - BigInteger.One;
+
+ if (!k.IsEven)
+ {
+ throw new InvalidOperationException("d*e - 1 is odd");
+ }
+
+ BigInteger two = BigInteger.One + BigInteger.One;
+ BigInteger t = BigInteger.One;
+
+ BigInteger r = k / two;
+
+ while (r.IsEven)
+ {
+ t++;
+ r /= two;
+ }
+
+ byte[] rndBuf = n.ToByteArray();
+
+ if (rndBuf[^1] == 0)
+ {
+ rndBuf = new byte[rndBuf.Length - 1];
+ }
+
+ BigInteger nMinusOne = n - BigInteger.One;
+
+ bool cracked = false;
+ BigInteger y = BigInteger.Zero;
+
+ using (var rng = RandomNumberGenerator.Create())
+ {
+ for (int i = 0; i < 100 && !cracked; i++)
+ {
+ BigInteger g;
+
+ do
+ {
+ rng.GetBytes(rndBuf);
+ g = Utilities.GetBigInteger(rndBuf);
+ }
+ while (g >= n);
+
+ y = BigInteger.ModPow(g, r, n);
+
+ if (y.IsOne || y == nMinusOne)
+ {
+ i--;
+ continue;
+ }
+
+ for (BigInteger j = BigInteger.One; j < t; j++)
+ {
+ BigInteger x = BigInteger.ModPow(y, two, n);
+
+ if (x.IsOne)
+ {
+ cracked = true;
+ break;
+ }
+
+ if (x == nMinusOne)
+ {
+ break;
+ }
+
+ y = x;
+ }
+ }
+ }
+
+ if (!cracked)
+ {
+ throw new InvalidOperationException("Prime factors not found");
+ }
+
+ BigInteger p = BigInteger.GreatestCommonDivisor(y - BigInteger.One, n);
+ BigInteger q = n / p;
+
+ return (p, q);
+ }
}
}
diff --git a/src/LibHac/CryptoOld.cs b/src/LibHac/CryptoOld.cs
index 956441c4..82ca8f90 100644
--- a/src/LibHac/CryptoOld.cs
+++ b/src/LibHac/CryptoOld.cs
@@ -1,103 +1,33 @@
using System;
using System.IO;
-using System.Numerics;
using System.Security.Cryptography;
+using LibHac.Crypto;
using LibHac.FsSystem;
+using Aes = LibHac.Crypto.Aes;
+
namespace LibHac
{
public static class CryptoOld
{
- internal const int Aes128Size = 0x10;
-
- public static void DecryptEcb(byte[] key, byte[] src, int srcIndex, byte[] dest, int destIndex, int length)
- {
- using (var aes = Aes.Create())
- {
- if (aes == null) throw new CryptographicException("Unable to create AES object");
- aes.Key = key;
- aes.Mode = CipherMode.ECB;
- aes.Padding = PaddingMode.None;
- Array.Copy(aes.CreateDecryptor().TransformFinalBlock(src, srcIndex, length), 0, dest, destIndex, length);
- }
- }
-
- public static void DecryptEcb(byte[] key, byte[] src, byte[] dest, int length) =>
- DecryptEcb(key, src, 0, dest, 0, length);
-
- public static void EncryptEcb(byte[] key, byte[] src, int srcIndex, byte[] dest, int destIndex, int length)
- {
- using (var aes = Aes.Create())
- {
- if (aes == null) throw new CryptographicException("Unable to create AES object");
- aes.Key = key;
- aes.Mode = CipherMode.ECB;
- aes.Padding = PaddingMode.None;
- Array.Copy(aes.CreateEncryptor().TransformFinalBlock(src, srcIndex, length), 0, dest, destIndex, length);
- }
- }
-
- public static void EncryptEcb(byte[] key, byte[] src, byte[] dest, int length) =>
- EncryptEcb(key, src, 0, dest, 0, length);
-
- public static void DecryptCbc(byte[] key, byte[] iv, byte[] src, int srcIndex, byte[] dest, int destIndex, int length)
- {
- using (var aes = Aes.Create())
- {
- if (aes == null) throw new CryptographicException("Unable to create AES object");
- aes.Key = key;
- aes.IV = iv;
- aes.Mode = CipherMode.CBC;
- aes.Padding = PaddingMode.None;
- Array.Copy(aes.CreateDecryptor().TransformFinalBlock(src, srcIndex, length), 0, dest, destIndex, length);
- }
- }
-
- public static void DecryptCbc(byte[] key, byte[] iv, byte[] src, byte[] dest, int length) =>
- DecryptCbc(key, iv, src, 0, dest, 0, length);
-
- public static void EncryptCbc(byte[] key, byte[] iv, byte[] src, int srcIndex, byte[] dest, int destIndex, int length)
- {
- using (var aes = Aes.Create())
- {
- if (aes == null) throw new CryptographicException("Unable to create AES object");
- aes.Key = key;
- aes.IV = iv;
- aes.Mode = CipherMode.CBC;
- aes.Padding = PaddingMode.None;
- Array.Copy(aes.CreateEncryptor().TransformFinalBlock(src, srcIndex, length), 0, dest, destIndex, length);
- }
- }
-
- public static void EncryptCbc(byte[] key, byte[] iv, byte[] src, byte[] dest, int length) =>
- EncryptCbc(key, iv, src, 0, dest, 0, length);
-
public static void GenerateKek(byte[] key, byte[] src, byte[] dest, byte[] kekSeed, byte[] keySeed)
{
- var kek = new byte[Aes128Size];
- var srcKek = new byte[Aes128Size];
+ var kek = new byte[Aes.KeySize128];
+ var srcKek = new byte[Aes.KeySize128];
- DecryptEcb(key, kekSeed, kek, Aes128Size);
- DecryptEcb(kek, src, srcKek, Aes128Size);
+ Aes.DecryptEcb128(kekSeed, kek, key);
+ Aes.DecryptEcb128(src, srcKek, kek);
if (keySeed != null)
{
- DecryptEcb(srcKek, keySeed, dest, Aes128Size);
+ Aes.DecryptEcb128(keySeed, dest, srcKek);
}
else
{
- Array.Copy(srcKek, dest, Aes128Size);
+ Array.Copy(srcKek, dest, Aes.KeySize128);
}
}
- internal static BigInteger GetBigInteger(ReadOnlySpan bytes)
- {
- var signPadded = new byte[bytes.Length + 1];
- bytes.CopyTo(signPadded.AsSpan(1));
- Array.Reverse(signPadded);
- return new BigInteger(signPadded);
- }
-
public static RSAParameters DecryptRsaKey(byte[] encryptedKey, byte[] kek)
{
var counter = new byte[0x10];
@@ -114,11 +44,7 @@ namespace LibHac
Array.Copy(key, 0x100, n, 0, 0x100);
Array.Copy(key, 0x200, e, 0, 4);
- BigInteger dInt = GetBigInteger(d);
- BigInteger nInt = GetBigInteger(n);
- BigInteger eInt = GetBigInteger(e);
-
- RSAParameters rsaParams = RecoverRsaParameters(nInt, eInt, dInt);
+ RSAParameters rsaParams = Rsa.RecoverParameters(n, e, d);
TestRsaKey(rsaParams);
return rsaParams;
}
@@ -138,43 +64,15 @@ namespace LibHac
}
}
- public static Validity Rsa2048Pkcs1Verify(byte[] data, byte[] signature, byte[] modulus)
- {
- using (var rsa = RSA.Create())
- {
- try
- {
- rsa.ImportParameters(new RSAParameters { Exponent = new byte[] { 1, 0, 1 }, Modulus = modulus });
+ public static Validity Rsa2048Pkcs1Verify(byte[] data, byte[] signature, byte[] modulus) =>
+ Rsa.VerifyRsa2048Pkcs1Sha256(signature, modulus, new byte[] { 1, 0, 1 }, data)
+ ? Validity.Valid
+ : Validity.Invalid;
- return rsa.VerifyData(data, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1)
- ? Validity.Valid
- : Validity.Invalid;
- }
- catch (CryptographicException)
- {
- return Validity.Invalid;
- }
- }
- }
-
- public static Validity Rsa2048PssVerify(byte[] data, byte[] signature, byte[] modulus)
- {
- using (var rsa = RSA.Create())
- {
- try
- {
- rsa.ImportParameters(new RSAParameters { Exponent = new byte[] { 1, 0, 1 }, Modulus = modulus });
-
- return rsa.VerifyData(data, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pss)
- ? Validity.Valid
- : Validity.Invalid;
- }
- catch (CryptographicException)
- {
- return Validity.Invalid;
- }
- }
- }
+ public static Validity Rsa2048PssVerify(byte[] data, byte[] signature, byte[] modulus) =>
+ Rsa.VerifyRsa2048PssSha256(signature, modulus, new byte[] { 1, 0, 1 }, data)
+ ? Validity.Valid
+ : Validity.Invalid;
public static byte[] DecryptRsaOaep(byte[] data, RSAParameters rsaParams)
{
@@ -201,222 +99,5 @@ namespace LibHac
}
}
}
-
- public static RSAParameters RecoverRsaParameters(ReadOnlySpan modulus, ReadOnlySpan exponent)
- {
- BigInteger dInt = GetBigInteger(exponent);
- BigInteger nInt = GetBigInteger(modulus);
- BigInteger eInt = GetBigInteger(new byte[] { 1, 0, 1 });
-
- return RecoverRsaParameters(nInt, eInt, dInt);
- }
-
- private static RSAParameters RecoverRsaParameters(BigInteger n, BigInteger e, BigInteger d)
- {
- using (var rng = RandomNumberGenerator.Create())
- {
- BigInteger k = d * e - 1;
-
- if (!k.IsEven)
- {
- throw new InvalidOperationException("d*e - 1 is odd");
- }
-
- BigInteger two = 2;
- BigInteger t = BigInteger.One;
-
- BigInteger r = k / two;
-
- while (r.IsEven)
- {
- t++;
- r /= two;
- }
-
- byte[] rndBuf = n.ToByteArray();
-
- if (rndBuf[rndBuf.Length - 1] == 0)
- {
- rndBuf = new byte[rndBuf.Length - 1];
- }
-
- BigInteger nMinusOne = n - BigInteger.One;
-
- bool cracked = false;
- BigInteger y = BigInteger.Zero;
-
- for (int i = 0; i < 100 && !cracked; i++)
- {
- BigInteger g;
-
- do
- {
- rng.GetBytes(rndBuf);
- g = GetBigInteger(rndBuf);
- }
- while (g >= n);
-
- y = BigInteger.ModPow(g, r, n);
-
- if (y.IsOne || y == nMinusOne)
- {
- i--;
- continue;
- }
-
- for (BigInteger j = BigInteger.One; j < t; j++)
- {
- BigInteger x = BigInteger.ModPow(y, two, n);
-
- if (x.IsOne)
- {
- cracked = true;
- break;
- }
-
- if (x == nMinusOne)
- {
- break;
- }
-
- y = x;
- }
- }
-
- if (!cracked)
- {
- throw new InvalidOperationException("Prime factors not found");
- }
-
- BigInteger p = BigInteger.GreatestCommonDivisor(y - BigInteger.One, n);
- BigInteger q = n / p;
- BigInteger dp = d % (p - BigInteger.One);
- BigInteger dq = d % (q - BigInteger.One);
- BigInteger inverseQ = ModInverse(q, p);
-
- int modLen = rndBuf.Length;
- int halfModLen = (modLen + 1) / 2;
-
- return new RSAParameters
- {
- Modulus = GetBytes(n, modLen),
- Exponent = GetBytes(e, -1),
- D = GetBytes(d, modLen),
- P = GetBytes(p, halfModLen),
- Q = GetBytes(q, halfModLen),
- DP = GetBytes(dp, halfModLen),
- DQ = GetBytes(dq, halfModLen),
- InverseQ = GetBytes(inverseQ, halfModLen)
- };
- }
- }
-
- private static byte[] GetBytes(BigInteger value, int size)
- {
- byte[] bytes = value.ToByteArray();
-
- if (size == -1)
- {
- size = bytes.Length;
- }
-
- if (bytes.Length > size + 1)
- {
- throw new InvalidOperationException($"Cannot squeeze value {value} to {size} bytes from {bytes.Length}.");
- }
-
- if (bytes.Length == size + 1 && bytes[bytes.Length - 1] != 0)
- {
- throw new InvalidOperationException($"Cannot squeeze value {value} to {size} bytes from {bytes.Length}.");
- }
-
- Array.Resize(ref bytes, size);
- Array.Reverse(bytes);
- return bytes;
- }
-
- private static BigInteger ModInverse(BigInteger e, BigInteger n)
- {
- BigInteger r = n;
- BigInteger newR = e;
- BigInteger t = 0;
- BigInteger newT = 1;
-
- while (newR != 0)
- {
- BigInteger quotient = r / newR;
- BigInteger temp;
-
- temp = t;
- t = newT;
- newT = temp - quotient * newT;
-
- temp = r;
- r = newR;
- newR = temp - quotient * newR;
- }
-
- if (t < 0)
- {
- t = t + n;
- }
-
- return t;
- }
-
- public static void CalculateAesCmac(byte[] key, byte[] src, int srcIndex, byte[] dest, int destIndex, int length)
- {
- var l = new byte[16];
- EncryptCbc(key, l, l, l, 0x10);
- byte[] paddedMessage;
- int paddedLength = length;
-
- byte[] firstSubkey = Rol(l);
- if ((l[0] & 0x80) == 0x80)
- firstSubkey[15] ^= 0x87;
-
- byte[] secondSubkey = Rol(firstSubkey);
- if ((firstSubkey[0] & 0x80) == 0x80)
- secondSubkey[15] ^= 0x87;
-
- if (length != 0 && length % 16 == 0)
- {
- paddedMessage = new byte[paddedLength];
- Array.Copy(src, srcIndex, paddedMessage, 0, length);
-
- for (int j = 0; j < firstSubkey.Length; j++)
- paddedMessage[length - 16 + j] ^= firstSubkey[j];
- }
- else
- {
- paddedLength += 16 - length % 16;
- paddedMessage = new byte[paddedLength];
- paddedMessage[length] = 0x80;
- Array.Copy(src, srcIndex, paddedMessage, 0, length);
-
- for (int j = 0; j < secondSubkey.Length; j++)
- paddedMessage[paddedLength - 16 + j] ^= secondSubkey[j];
- }
-
- var encResult = new byte[paddedMessage.Length];
- EncryptCbc(key, new byte[16], paddedMessage, encResult, paddedLength);
-
- Array.Copy(encResult, paddedLength - 0x10, dest, destIndex, 0x10);
- }
-
- private static byte[] Rol(byte[] b)
- {
- var r = new byte[b.Length];
- byte carry = 0;
-
- for (int i = b.Length - 1; i >= 0; i--)
- {
- ushort u = (ushort)(b[i] << 1);
- r[i] = (byte)((u & 0xff) + carry);
- carry = (byte)((u & 0xff00) >> 8);
- }
-
- return r;
- }
}
-}
+}
\ No newline at end of file
diff --git a/src/LibHac/FsSystem/AesXtsFileHeader.cs b/src/LibHac/FsSystem/AesXtsFileHeader.cs
index 0288b631..eabf2bcc 100644
--- a/src/LibHac/FsSystem/AesXtsFileHeader.cs
+++ b/src/LibHac/FsSystem/AesXtsFileHeader.cs
@@ -5,6 +5,8 @@ using System.Text;
using LibHac.Fs;
using LibHac.Fs.Fsa;
+using Aes = LibHac.Crypto.Aes;
+
namespace LibHac.FsSystem
{
public class AesXtsFileHeader
@@ -79,14 +81,14 @@ namespace LibHac.FsSystem
private void DecryptKeys()
{
- CryptoOld.DecryptEcb(Kek1, EncryptedKey1, DecryptedKey1, 0x10);
- CryptoOld.DecryptEcb(Kek2, EncryptedKey2, DecryptedKey2, 0x10);
+ Aes.DecryptEcb128(EncryptedKey1, DecryptedKey1, Kek1);
+ Aes.DecryptEcb128(EncryptedKey2, DecryptedKey2, Kek2);
}
private void EncryptKeys()
{
- CryptoOld.EncryptEcb(Kek1, DecryptedKey1, EncryptedKey1, 0x10);
- CryptoOld.EncryptEcb(Kek2, DecryptedKey2, EncryptedKey2, 0x10);
+ Aes.EncryptEcb128(DecryptedKey1, EncryptedKey1, Kek1);
+ Aes.EncryptEcb128(DecryptedKey2, EncryptedKey2, Kek2);
}
private void GenerateKek(byte[] kekSeed, string path)
diff --git a/src/LibHac/FsSystem/NcaUtils/Nca.cs b/src/LibHac/FsSystem/NcaUtils/Nca.cs
index 7e55a6f4..a1931d5d 100644
--- a/src/LibHac/FsSystem/NcaUtils/Nca.cs
+++ b/src/LibHac/FsSystem/NcaUtils/Nca.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using LibHac.Common;
+using LibHac.Crypto;
using LibHac.Diag;
using LibHac.Fs;
using LibHac.Fs.Fsa;
@@ -50,9 +51,9 @@ namespace LibHac.FsSystem.NcaUtils
}
byte[] encryptedKey = Header.GetEncryptedKey(index).ToArray();
- var decryptedKey = new byte[CryptoOld.Aes128Size];
+ var decryptedKey = new byte[Aes.KeySize128];
- CryptoOld.DecryptEcb(keyAreaKey, encryptedKey, decryptedKey, CryptoOld.Aes128Size);
+ Aes.DecryptEcb128(encryptedKey, decryptedKey, keyAreaKey);
return decryptedKey;
}
@@ -76,9 +77,9 @@ namespace LibHac.FsSystem.NcaUtils
}
byte[] encryptedKey = accessKey.Value.ToArray();
- var decryptedKey = new byte[CryptoOld.Aes128Size];
+ var decryptedKey = new byte[Aes.KeySize128];
- CryptoOld.DecryptEcb(titleKek, encryptedKey, decryptedKey, CryptoOld.Aes128Size);
+ Aes.DecryptEcb128(encryptedKey, decryptedKey, titleKek);
return decryptedKey;
}
diff --git a/src/LibHac/FsSystem/NcaUtils/NcaHeader.cs b/src/LibHac/FsSystem/NcaUtils/NcaHeader.cs
index b0558233..f52d3d22 100644
--- a/src/LibHac/FsSystem/NcaUtils/NcaHeader.cs
+++ b/src/LibHac/FsSystem/NcaUtils/NcaHeader.cs
@@ -160,8 +160,8 @@ namespace LibHac.FsSystem.NcaUtils
throw new ArgumentOutOfRangeException($"Key index must be between 0 and 3. Actual: {index}");
}
- int offset = NcaHeaderStruct.KeyAreaOffset + CryptoOld.Aes128Size * index;
- return _header.Span.Slice(offset, CryptoOld.Aes128Size);
+ int offset = NcaHeaderStruct.KeyAreaOffset + Aes.KeySize128 * index;
+ return _header.Span.Slice(offset, Aes.KeySize128);
}
public NcaFsHeader GetFsHeader(int index)
diff --git a/src/LibHac/FsSystem/Save/Header.cs b/src/LibHac/FsSystem/Save/Header.cs
index db656cb9..6c8b4250 100644
--- a/src/LibHac/FsSystem/Save/Header.cs
+++ b/src/LibHac/FsSystem/Save/Header.cs
@@ -91,11 +91,11 @@ namespace LibHac.FsSystem.Save
private Validity ValidateSignature(Keyset keyset)
{
- var calculatedCmac = new byte[0x10];
+ Span calculatedCmac = stackalloc byte[0x10];
- CryptoOld.CalculateAesCmac(keyset.SaveMacKey, Data, 0x100, calculatedCmac, 0, 0x200);
+ Aes.CalculateCmac(calculatedCmac, Data.AsSpan(0x100, 0x200), keyset.SaveMacKey);
- return Utilities.ArraysEqual(calculatedCmac, Cmac) ? Validity.Valid : Validity.Invalid;
+ return CryptoUtil.IsSameBytes(calculatedCmac, Cmac, Aes.BlockSize) ? Validity.Valid : Validity.Invalid;
}
}
diff --git a/src/LibHac/FsSystem/Save/SaveDataFileSystem.cs b/src/LibHac/FsSystem/Save/SaveDataFileSystem.cs
index 6ec53e1e..f6d4a37a 100644
--- a/src/LibHac/FsSystem/Save/SaveDataFileSystem.cs
+++ b/src/LibHac/FsSystem/Save/SaveDataFileSystem.cs
@@ -269,7 +269,7 @@ namespace LibHac.FsSystem.Save
headerStream.Position = 0x100;
headerStream.Read(cmacData, 0, 0x200);
- CryptoOld.CalculateAesCmac(keyset.SaveMacKey, cmacData, 0, cmac, 0, 0x200);
+ Aes.CalculateCmac(cmac, cmacData, keyset.SaveMacKey);
headerStream.Position = 0;
headerStream.Write(cmac, 0, 0x10);
diff --git a/src/LibHac/FsSystem/StorageExtensions.cs b/src/LibHac/FsSystem/StorageExtensions.cs
index b433aa94..611cc7b8 100644
--- a/src/LibHac/FsSystem/StorageExtensions.cs
+++ b/src/LibHac/FsSystem/StorageExtensions.cs
@@ -1,7 +1,7 @@
using System;
-using System.Buffers;
using System.IO;
using System.Runtime.InteropServices;
+using LibHac.Common;
using LibHac.Fs;
using LibHac.Fs.Fsa;
@@ -71,10 +71,8 @@ namespace LibHac.FsSystem
public static IFile AsFile(this IStorage storage, OpenMode mode) => new StorageFile(storage, mode);
- public static void CopyTo(this IStorage input, IStorage output, IProgressReport progress = null)
+ public static void CopyTo(this IStorage input, IStorage output, IProgressReport progress = null, int bufferSize = 81920)
{
- const int bufferSize = 81920;
-
input.GetSize(out long inputSize).ThrowIfFailure();
output.GetSize(out long outputSize).ThrowIfFailure();
@@ -84,25 +82,20 @@ namespace LibHac.FsSystem
long pos = 0;
- byte[] buffer = ArrayPool.Shared.Rent(bufferSize);
- try
- {
- while (remaining > 0)
- {
- int toCopy = (int)Math.Min(bufferSize, remaining);
- Span buf = buffer.AsSpan(0, toCopy);
- input.Read(pos, buf);
- output.Write(pos, buf);
+ using var buffer = new RentedArray(bufferSize);
+ int rentedBufferSize = buffer.Array.Length;
- remaining -= toCopy;
- pos += toCopy;
-
- progress?.ReportAdd(toCopy);
- }
- }
- finally
+ while (remaining > 0)
{
- ArrayPool.Shared.Return(buffer);
+ int toCopy = (int)Math.Min(rentedBufferSize, remaining);
+ Span buf = buffer.Array.AsSpan(0, toCopy);
+ input.Read(pos, buf);
+ output.Write(pos, buf);
+
+ remaining -= toCopy;
+ pos += toCopy;
+
+ progress?.ReportAdd(toCopy);
}
progress?.SetTotal(0);
@@ -140,27 +133,22 @@ namespace LibHac.FsSystem
long pos = offset;
- byte[] buffer = ArrayPool.Shared.Rent(bufferSize);
- try
+ using var buffer = new RentedArray(bufferSize);
+ int rentedBufferSize = buffer.Array.Length;
+
+ buffer.Array.AsSpan(0, (int)Math.Min(remaining, rentedBufferSize)).Fill(value);
+
+ while (remaining > 0)
{
- buffer.AsSpan(0, (int)Math.Min(remaining, bufferSize)).Fill(value);
+ int toFill = (int)Math.Min(rentedBufferSize, remaining);
+ Span buf = buffer.Array.AsSpan(0, toFill);
- while (remaining > 0)
- {
- int toFill = (int)Math.Min(bufferSize, remaining);
- Span buf = buffer.AsSpan(0, toFill);
+ input.Write(pos, buf);
- input.Write(pos, buf);
+ remaining -= toFill;
+ pos += toFill;
- remaining -= toFill;
- pos += toFill;
-
- progress?.ReportAdd(toFill);
- }
- }
- finally
- {
- ArrayPool.Shared.Return(buffer);
+ progress?.ReportAdd(toFill);
}
progress?.SetTotal(0);
@@ -200,30 +188,32 @@ namespace LibHac.FsSystem
return arr;
}
- public static void CopyToStream(this IStorage input, Stream output, long length, IProgressReport progress = null)
+ public static void CopyToStream(this IStorage input, Stream output, long length, IProgressReport progress = null, int bufferSize = 0x8000)
{
- const int bufferSize = 0x8000;
long remaining = length;
long inOffset = 0;
- var buffer = new byte[bufferSize];
+
+ using var buffer = new RentedArray(bufferSize);
+ int rentedBufferSize = buffer.Array.Length;
+
progress?.SetTotal(length);
while (remaining > 0)
{
- int toWrite = (int)Math.Min(buffer.Length, remaining);
- input.Read(inOffset, buffer.AsSpan(0, toWrite));
+ int toWrite = (int)Math.Min(rentedBufferSize, remaining);
+ input.Read(inOffset, buffer.Array.AsSpan(0, toWrite));
- output.Write(buffer, 0, toWrite);
+ output.Write(buffer.Array, 0, toWrite);
remaining -= toWrite;
inOffset += toWrite;
progress?.ReportAdd(toWrite);
}
}
- public static void CopyToStream(this IStorage input, Stream output)
+ public static void CopyToStream(this IStorage input, Stream output, int bufferSize = 0x8000)
{
input.GetSize(out long inputSize).ThrowIfFailure();
- CopyToStream(input, output, inputSize);
+ CopyToStream(input, output, inputSize, bufferSize: bufferSize);
}
public static IStorage AsStorage(this Stream stream)
diff --git a/src/LibHac/Keyset.cs b/src/LibHac/Keyset.cs
index 4bbdc83b..20006594 100644
--- a/src/LibHac/Keyset.cs
+++ b/src/LibHac/Keyset.cs
@@ -4,6 +4,7 @@ using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
+using LibHac.Crypto;
using LibHac.Fs;
using LibHac.FsSrv;
using LibHac.Spl;
@@ -81,7 +82,7 @@ namespace LibHac
if (_nca0RsaKeyAreaKey.HasValue)
return _nca0RsaKeyAreaKey.Value;
- _nca0RsaKeyAreaKey = CryptoOld.RecoverRsaParameters(BetaNca0Modulus, BetaNca0Exponent);
+ _nca0RsaKeyAreaKey = Rsa.RecoverParameters(BetaNca0Modulus, BetaNca0Exponent, new byte[] { 1, 0, 1 });
return _nca0RsaKeyAreaKey.Value;
}
}
@@ -349,7 +350,7 @@ namespace LibHac
}
Array.Copy(EncryptedKeyblobs[i], expectedCmac, 0x10);
- CryptoOld.CalculateAesCmac(KeyblobMacKeys[i], EncryptedKeyblobs[i], 0x10, cmac, 0, 0xa0);
+ Aes.CalculateCmac(cmac, EncryptedKeyblobs[i].AsSpan(0x10, 0xA0), KeyblobMacKeys[i]);
if (!Utilities.ArraysEqual(cmac, expectedCmac))
{
diff --git a/src/LibHac/Utilities.cs b/src/LibHac/Utilities.cs
index c34207a4..304c22b6 100644
--- a/src/LibHac/Utilities.cs
+++ b/src/LibHac/Utilities.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.IO;
using System.Numerics;
using System.Runtime.CompilerServices;
@@ -500,6 +500,67 @@ namespace LibHac
return value > 0 && ResetLeastSignificantOneBit(value) == 0;
}
+ public static BigInteger GetBigInteger(this ReadOnlySpan bytes)
+ {
+ var signPadded = new byte[bytes.Length + 1];
+ bytes.CopyTo(signPadded.AsSpan(1));
+ Array.Reverse(signPadded);
+ return new BigInteger(signPadded);
+ }
+
+ public static byte[] GetBytes(this BigInteger value, int size)
+ {
+ byte[] bytes = value.ToByteArray();
+
+ if (size == -1)
+ {
+ size = bytes.Length;
+ }
+
+ if (bytes.Length > size + 1)
+ {
+ throw new InvalidOperationException($"Cannot squeeze value {value} to {size} bytes from {bytes.Length}.");
+ }
+
+ if (bytes.Length == size + 1 && bytes[bytes.Length - 1] != 0)
+ {
+ throw new InvalidOperationException($"Cannot squeeze value {value} to {size} bytes from {bytes.Length}.");
+ }
+
+ Array.Resize(ref bytes, size);
+ Array.Reverse(bytes);
+ return bytes;
+ }
+
+ public static BigInteger ModInverse(BigInteger e, BigInteger n)
+ {
+ BigInteger r = n;
+ BigInteger newR = e;
+ BigInteger t = 0;
+ BigInteger newT = 1;
+
+ while (newR != 0)
+ {
+ BigInteger quotient = r / newR;
+ BigInteger temp;
+
+ temp = t;
+ t = newT;
+ newT = temp - quotient * newT;
+
+ temp = r;
+ r = newR;
+ newR = temp - quotient * newR;
+ }
+
+ if (t < 0)
+ {
+ t = t + n;
+ }
+
+ return t;
+ }
+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int ResetLeastSignificantOneBit(int value)
{
diff --git a/src/LibHac/XciHeader.cs b/src/LibHac/XciHeader.cs
index 4bfa3bb0..7fa5fb4c 100644
--- a/src/LibHac/XciHeader.cs
+++ b/src/LibHac/XciHeader.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.IO;
using System.Text;
using LibHac.Crypto;
@@ -96,7 +96,7 @@ namespace LibHac
Flags = (GameCardAttribute)reader.ReadByte();
PackageId = reader.ReadUInt64();
ValidDataEndPage = reader.ReadInt64();
- AesCbcIv = reader.ReadBytes(CryptoOld.Aes128Size);
+ AesCbcIv = reader.ReadBytes(Aes.KeySize128);
Array.Reverse(AesCbcIv);
RootPartitionOffset = reader.ReadInt64();
RootPartitionHeaderSize = reader.ReadInt64();
@@ -111,7 +111,7 @@ namespace LibHac
{
byte[] encHeader = reader.ReadBytes(EncryptedHeaderSize);
var decHeader = new byte[EncryptedHeaderSize];
- CryptoOld.DecryptCbc(keyset.XciHeaderKey, AesCbcIv, encHeader, decHeader, EncryptedHeaderSize);
+ Aes.DecryptCbc128(encHeader, decHeader, keyset.XciHeaderKey, AesCbcIv);
using (var decreader = new BinaryReader(new MemoryStream(decHeader)))
{
diff --git a/src/hactoolnet/EnumStrings.cs b/src/hactoolnet/EnumStrings.cs
index bb21681f..77c4d681 100644
--- a/src/hactoolnet/EnumStrings.cs
+++ b/src/hactoolnet/EnumStrings.cs
@@ -1,6 +1,8 @@
using LibHac;
+using LibHac.Fs;
using LibHac.FsSystem.NcaUtils;
using LibHac.Ncm;
+using ContentType = LibHac.Ncm.ContentType;
namespace hactoolnet
{
@@ -96,5 +98,20 @@ namespace hactoolnet
_ => value.ToString()
};
}
+
+ public static string Print(this SaveDataType value)
+ {
+ return value switch
+ {
+ SaveDataType.System => nameof(SaveDataType.System),
+ SaveDataType.Account => nameof(SaveDataType.Account),
+ SaveDataType.Bcat => nameof(SaveDataType.Bcat),
+ SaveDataType.Device => nameof(SaveDataType.Device),
+ SaveDataType.Temporary => nameof(SaveDataType.Temporary),
+ SaveDataType.Cache => nameof(SaveDataType.Cache),
+ SaveDataType.SystemBcat => nameof(SaveDataType.SystemBcat),
+ _ => value.ToString()
+ };
+ }
}
}
diff --git a/src/hactoolnet/ProcessSave.cs b/src/hactoolnet/ProcessSave.cs
index 8d6c928e..0ea5e29c 100644
--- a/src/hactoolnet/ProcessSave.cs
+++ b/src/hactoolnet/ProcessSave.cs
@@ -330,7 +330,7 @@ namespace hactoolnet
PrintItem(sb, colLen, "Title ID:", $"{save.Header.ExtraData.TitleId:x16}");
PrintItem(sb, colLen, "User ID:", save.Header.ExtraData.UserId);
PrintItem(sb, colLen, "Save ID:", $"{save.Header.ExtraData.SaveId:x16}");
- PrintItem(sb, colLen, "Save Type:", $"{save.Header.ExtraData.Type}");
+ PrintItem(sb, colLen, "Save Type:", $"{save.Header.ExtraData.Type.Print()}");
PrintItem(sb, colLen, "Owner ID:", $"{save.Header.ExtraData.SaveOwnerId:x16}");
PrintItem(sb, colLen, "Timestamp:", $"{DateTimeOffset.FromUnixTimeSeconds(save.Header.ExtraData.Timestamp):yyyy-MM-dd HH:mm:ss} UTC");
PrintItem(sb, colLen, "Save Data Size:", $"0x{save.Header.ExtraData.DataSize:x16} ({Utilities.GetBytesReadable(save.Header.ExtraData.DataSize)})");
diff --git a/tests/LibHac.Tests/AesCmac.cs b/tests/LibHac.Tests/AesCmac.cs
index 9caf2315..52e2dd2d 100644
--- a/tests/LibHac.Tests/AesCmac.cs
+++ b/tests/LibHac.Tests/AesCmac.cs
@@ -1,4 +1,6 @@
-using Xunit;
+using System;
+using LibHac.Crypto;
+using Xunit;
namespace LibHac.Tests
{
@@ -57,7 +59,7 @@ namespace LibHac.Tests
{
var actual = new byte[0x10];
- CryptoOld.CalculateAesCmac(data.Key, data.Message, data.Start, actual, 0, data.Length);
+ Aes.CalculateCmac(actual, data.Message.AsSpan(data.Start, data.Length), data.Key);
Assert.Equal(data.Expected, actual);
}