Merge pull request #164 from Xpl0itR/misc

Misc changes to StorageExtensions and CryptoOld

- Changes to StorageExtensions
  - Use LibHac's RentedArray<T> struct
  - Expose bufferSize as a variable in CopyTo and CopyToStream
- Changes to CryptoOld
  - Remove AES functions and replace references to said functions with functions from the Crypto.Aes class
  - Edit and move the AES-CMAC function into the Crypto.Aes class
  - Move BigInteger functions to the Utilities class
  - Reduce duplication between the RSA functions
  - Edit and move the Rsa parameter recovery function into the Crypto.Rsa class
This commit is contained in:
Alex Barney 2020-09-08 01:33:16 -07:00 committed by GitHub
commit 5f755bc76f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 366 additions and 410 deletions

View file

@ -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);
}
/// <summary>
/// Computes the CMAC of the provided data using AES-128.
/// </summary>
/// <param name="mac">The buffer where the generated MAC will be placed. Must be at least 16 bytes long.</param>
/// <param name="data">The message on which the MAC will be calculated.</param>
/// <param name="key">The 128-bit AES key used to calculate the MAC.</param>
/// <remarks>https://tools.ietf.org/html/rfc4493</remarks>
public static void CalculateCmac(Span<byte> mac, ReadOnlySpan<byte> data, ReadOnlySpan<byte> key)
{
ReadOnlySpan<byte> zero = stackalloc byte[16];
int len = data.Length;
// Step 1, AES-128 with key K is applied to an all-zero input block.
Span<byte> l = stackalloc byte[16];
EncryptCbc128(zero, l, key, zero);
// Step 2, K1 is derived through the following operation:
Span<byte> 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<byte> 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<byte> 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<byte> input, Span<byte> 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);
}
}
}
}

View file

@ -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<byte> signature, ReadOnlySpan<byte> modulus,
ReadOnlySpan<byte> exponent, ReadOnlySpan<byte> message)
ReadOnlySpan<byte> exponent, ReadOnlySpan<byte> message) =>
VerifyRsa2048Sha256(signature, modulus, exponent, message, RSASignaturePadding.Pss);
public static bool VerifyRsa2048Pkcs1Sha256(ReadOnlySpan<byte> signature, ReadOnlySpan<byte> modulus,
ReadOnlySpan<byte> exponent, ReadOnlySpan<byte> message) =>
VerifyRsa2048Sha256(signature, modulus, exponent, message, RSASignaturePadding.Pkcs1);
private static bool VerifyRsa2048Sha256(ReadOnlySpan<byte> signature, ReadOnlySpan<byte> modulus,
ReadOnlySpan<byte> exponent, ReadOnlySpan<byte> 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;
}
}
/// <param name="n">The RSA Modulus (n)</param>
/// <param name="e">The RSA Public Exponent (e)</param>
/// <param name="d">The RSA Private Exponent (d)</param>
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)
};
}
/// <param name="n">The RSA Modulus (n)</param>
/// <param name="e">The RSA Public Exponent (e)</param>
/// <param name="d">The RSA Private Exponent (d)</param>
public static RSAParameters RecoverParameters(ReadOnlySpan<byte> n, ReadOnlySpan<byte> e, ReadOnlySpan<byte> d) =>
RecoverParameters(n.GetBigInteger(), e.GetBigInteger(), d.GetBigInteger());
/// <summary>
/// Derive RSA Prime Number Pair (p, q) from RSA Modulus (n), RSA Public Exponent (e) and RSA Private Exponent (d)
/// </summary>
/// <param name="n">The RSA Modulus (n)</param>
/// <param name="e">The RSA Public Exponent (e)</param>
/// <param name="d">The RSA Private Exponent (d)</param>
/// <returns>RSA Prime Number Pair</returns>
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);
}
}
}

View file

@ -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<byte> 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<byte> modulus, ReadOnlySpan<byte> 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;
}
}
}
}

View file

@ -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)

View file

@ -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;
}

View file

@ -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)

View file

@ -91,11 +91,11 @@ namespace LibHac.FsSystem.Save
private Validity ValidateSignature(Keyset keyset)
{
var calculatedCmac = new byte[0x10];
Span<byte> 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;
}
}

View file

@ -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);

View file

@ -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<byte>.Shared.Rent(bufferSize);
try
{
while (remaining > 0)
{
int toCopy = (int)Math.Min(bufferSize, remaining);
Span<byte> buf = buffer.AsSpan(0, toCopy);
input.Read(pos, buf);
output.Write(pos, buf);
using var buffer = new RentedArray<byte>(bufferSize);
int rentedBufferSize = buffer.Array.Length;
remaining -= toCopy;
pos += toCopy;
progress?.ReportAdd(toCopy);
}
}
finally
while (remaining > 0)
{
ArrayPool<byte>.Shared.Return(buffer);
int toCopy = (int)Math.Min(rentedBufferSize, remaining);
Span<byte> 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<byte>.Shared.Rent(bufferSize);
try
using var buffer = new RentedArray<byte>(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<byte> buf = buffer.Array.AsSpan(0, toFill);
while (remaining > 0)
{
int toFill = (int)Math.Min(bufferSize, remaining);
Span<byte> 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<byte>.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<byte>(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)

View file

@ -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))
{

View file

@ -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<byte> 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)
{

View file

@ -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)))
{

View file

@ -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()
};
}
}
}

View file

@ -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)})");

View file

@ -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);
}