mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2024-11-14 10:49:41 +01:00
Tweaks and cleanup for PR #164
- Fully span-ify CalculateCmac and update callers. - Modify CalculateCmac signature to match Nintendo's. - Avoid doing stackalloc based on an input length parameter. - Use ReadOnlySpan instead of Span where appropriate. - Standardize XML comments to use triple slashes. - Make use of the entire rented buffer when copying. Unrelated to the PR - Slip in a missed enum to string conversion in hactoolnet for CoreRT reflection-free mode.
This commit is contained in:
parent
265147b678
commit
49711b12db
9 changed files with 88 additions and 81 deletions
|
@ -1,6 +1,6 @@
|
|||
// ReSharper disable AssignmentIsFullyDiscarded
|
||||
using System;
|
||||
|
||||
using LibHac.Diag;
|
||||
#if HAS_INTRINSICS
|
||||
using LibHac.Crypto.Detail;
|
||||
|
||||
|
@ -266,83 +266,72 @@ namespace LibHac.Crypto
|
|||
cipher.Transform(input, output);
|
||||
}
|
||||
|
||||
/**
|
||||
* <param name="k">A byte span containing the 128-bit key used in the AES-CBC-128 steps</param>
|
||||
* <param name="m">A byte span containing the message to be authenticated</param>
|
||||
* <param name="mIndex">The offset within the byte span at which the message will be read from</param>
|
||||
* <param name="t">A byte span to output the message authentication code into</param>
|
||||
* <param name="tIndex">The offset within the byte span at which the authentication code will be written to</param>
|
||||
* <param name="len">The length of the message</param>
|
||||
* <remarks>https://tools.ietf.org/html/rfc4493</remarks>
|
||||
*/
|
||||
public static void CalculateCmac(Span<byte> k, Span<byte> m, int mIndex, Span<byte> t, int tIndex, int len)
|
||||
/// <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, k, zero);
|
||||
EncryptCbc128(zero, l, key, zero);
|
||||
|
||||
// Step 2, K1 is derived through the following operation:
|
||||
Span<byte> k1 = LeftShiftBytes(l);
|
||||
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 = LeftShiftBytes(k1);
|
||||
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.
|
||||
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
|
||||
Span<byte> message = stackalloc byte[len];
|
||||
m.Slice(mIndex, len).CopyTo(message);
|
||||
paddedMessage = len < 0x800 ? stackalloc byte[len] : new byte[len];
|
||||
data.CopyTo(paddedMessage);
|
||||
|
||||
for (int j = 0; j < k1.Length; j++)
|
||||
message[message.Length - 16 + j] ^= k1[j];
|
||||
|
||||
Span<byte> encResult = stackalloc byte[message.Length];
|
||||
EncryptCbc128(message, encResult, k, zero); // The result of the previous process will be the input of the last encryption.
|
||||
encResult.Slice(message.Length - 0x10).CopyTo(t.Slice(tIndex));
|
||||
paddedMessage[paddedMessage.Length - 16 + j] ^= k1[j];
|
||||
}
|
||||
else // Otherwise, the last block shall be padded with 10^i and XOR'ed with K2.
|
||||
{
|
||||
Span<byte> message = stackalloc byte[len + (16 - len % 16)];
|
||||
message[len] = 0x80;
|
||||
m.Slice(mIndex, len).CopyTo(message);
|
||||
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++)
|
||||
message[message.Length - 16 + j] ^= k2[j];
|
||||
|
||||
Span<byte> encResult = stackalloc byte[message.Length];
|
||||
EncryptCbc128(message, encResult, k, zero); // The result of the previous process will be the input of the last encryption.
|
||||
encResult.Slice(message.Length - 0x10).CopyTo(t.Slice(tIndex));
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* <param name="k">A byte span containing the 128-bit key used in the AES-CBC-128 steps</param>
|
||||
* <param name="m">A byte span containing the message to be authenticated</param>
|
||||
* <param name="t">A byte span to output the message authentication code into</param>
|
||||
* <remarks>https://tools.ietf.org/html/rfc4493</remarks>
|
||||
*/
|
||||
public static void CalculateCmac(Span<byte> k, Span<byte> m, Span<byte> t) =>
|
||||
CalculateCmac(k, m, 0, t, 0, m.Length);
|
||||
|
||||
private static byte[] LeftShiftBytes(Span<byte> bytes)
|
||||
private static void LeftShiftBytes(ReadOnlySpan<byte> input, Span<byte> output)
|
||||
{
|
||||
var shifted = new byte[bytes.Length];
|
||||
Assert.AssertTrue(output.Length >= input.Length);
|
||||
|
||||
byte carry = 0;
|
||||
|
||||
for (var i = bytes.Length - 1; i >= 0; i--)
|
||||
for (int i = input.Length - 1; i >= 0; i--)
|
||||
{
|
||||
var b = (ushort)(bytes[i] << 1);
|
||||
shifted[i] = (byte)((b & 0xff) + carry);
|
||||
ushort b = (ushort)(input[i] << 1);
|
||||
output[i] = (byte)((b & 0xff) + carry);
|
||||
carry = (byte)((b & 0xff00) >> 8);
|
||||
}
|
||||
|
||||
return shifted;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,11 +50,9 @@ namespace LibHac.Crypto
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <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>
|
||||
*/
|
||||
/// <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);
|
||||
|
@ -79,23 +77,19 @@ namespace LibHac.Crypto
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* <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>
|
||||
*/
|
||||
/// <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>
|
||||
*/
|
||||
/// <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;
|
||||
|
|
|
@ -91,11 +91,11 @@ namespace LibHac.FsSystem.Save
|
|||
|
||||
private Validity ValidateSignature(Keyset keyset)
|
||||
{
|
||||
var calculatedCmac = new byte[0x10];
|
||||
Span<byte> calculatedCmac = stackalloc byte[0x10];
|
||||
|
||||
Aes.CalculateCmac(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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -269,7 +269,7 @@ namespace LibHac.FsSystem.Save
|
|||
headerStream.Position = 0x100;
|
||||
headerStream.Read(cmacData, 0, 0x200);
|
||||
|
||||
Aes.CalculateCmac(keyset.SaveMacKey, cmacData, 0, cmac, 0, 0x200);
|
||||
Aes.CalculateCmac(cmac, cmacData, keyset.SaveMacKey);
|
||||
|
||||
headerStream.Position = 0;
|
||||
headerStream.Write(cmac, 0, 0x10);
|
||||
|
|
|
@ -83,10 +83,12 @@ namespace LibHac.FsSystem
|
|||
long pos = 0;
|
||||
|
||||
using var buffer = new RentedArray<byte>(bufferSize);
|
||||
int rentedBufferSize = buffer.Array.Length;
|
||||
|
||||
while (remaining > 0)
|
||||
{
|
||||
int toCopy = (int)Math.Min(bufferSize, remaining);
|
||||
Span<byte> buf = buffer.Span.Slice(0, toCopy);
|
||||
int toCopy = (int)Math.Min(rentedBufferSize, remaining);
|
||||
Span<byte> buf = buffer.Array.AsSpan(0, toCopy);
|
||||
input.Read(pos, buf);
|
||||
output.Write(pos, buf);
|
||||
|
||||
|
@ -132,12 +134,14 @@ namespace LibHac.FsSystem
|
|||
long pos = offset;
|
||||
|
||||
using var buffer = new RentedArray<byte>(bufferSize);
|
||||
buffer.Span.Slice(0, (int)Math.Min(remaining, bufferSize)).Fill(value);
|
||||
int rentedBufferSize = buffer.Array.Length;
|
||||
|
||||
buffer.Array.AsSpan(0, (int)Math.Min(remaining, rentedBufferSize)).Fill(value);
|
||||
|
||||
while (remaining > 0)
|
||||
{
|
||||
int toFill = (int)Math.Min(bufferSize, remaining);
|
||||
Span<byte> buf = buffer.Span.Slice(0, toFill);
|
||||
int toFill = (int)Math.Min(rentedBufferSize, remaining);
|
||||
Span<byte> buf = buffer.Array.AsSpan(0, toFill);
|
||||
|
||||
input.Write(pos, buf);
|
||||
|
||||
|
@ -188,14 +192,16 @@ namespace LibHac.FsSystem
|
|||
{
|
||||
long remaining = length;
|
||||
long inOffset = 0;
|
||||
|
||||
using var buffer = new RentedArray<byte>(bufferSize);
|
||||
int rentedBufferSize = buffer.Array.Length;
|
||||
|
||||
progress?.SetTotal(length);
|
||||
|
||||
while (remaining > 0)
|
||||
{
|
||||
int toWrite = (int)Math.Min(bufferSize, remaining);
|
||||
input.Read(inOffset, buffer.Span.Slice(0, toWrite));
|
||||
int toWrite = (int)Math.Min(rentedBufferSize, remaining);
|
||||
input.Read(inOffset, buffer.Array.AsSpan(0, toWrite));
|
||||
|
||||
output.Write(buffer.Array, 0, toWrite);
|
||||
remaining -= toWrite;
|
||||
|
|
|
@ -350,7 +350,7 @@ namespace LibHac
|
|||
}
|
||||
|
||||
Array.Copy(EncryptedKeyblobs[i], expectedCmac, 0x10);
|
||||
Aes.CalculateCmac(KeyblobMacKeys[i], EncryptedKeyblobs[i], 0x10, cmac, 0, 0xa0);
|
||||
Aes.CalculateCmac(cmac, EncryptedKeyblobs[i].AsSpan(0x10, 0xA0), KeyblobMacKeys[i]);
|
||||
|
||||
if (!Utilities.ArraysEqual(cmac, expectedCmac))
|
||||
{
|
||||
|
|
|
@ -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()
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)})");
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using LibHac.Crypto;
|
||||
using System;
|
||||
using LibHac.Crypto;
|
||||
using Xunit;
|
||||
|
||||
namespace LibHac.Tests
|
||||
|
@ -58,7 +59,7 @@ namespace LibHac.Tests
|
|||
{
|
||||
var actual = new byte[0x10];
|
||||
|
||||
Aes.CalculateCmac(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);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue