2018-07-13 17:25:39 +02:00
|
|
|
|
using System;
|
|
|
|
|
using System.IO;
|
|
|
|
|
using System.Numerics;
|
2018-06-20 19:42:56 +02:00
|
|
|
|
using System.Security.Cryptography;
|
2018-07-13 17:25:39 +02:00
|
|
|
|
using libhac.XTSSharp;
|
2018-06-20 19:42:56 +02:00
|
|
|
|
|
|
|
|
|
namespace libhac
|
|
|
|
|
{
|
|
|
|
|
public class Crypto
|
|
|
|
|
{
|
2018-08-12 22:45:10 +02:00
|
|
|
|
internal const int Aes128Size = 0x10;
|
|
|
|
|
internal const int Sha256DigestSize = 0x20;
|
|
|
|
|
|
2018-06-20 19:42:56 +02:00
|
|
|
|
public static void DecryptEcb(byte[] key, byte[] src, int srcIndex, byte[] dest, int destIndex, int length)
|
|
|
|
|
{
|
|
|
|
|
using (var aes = Aes.Create())
|
|
|
|
|
{
|
2018-07-13 17:25:39 +02:00
|
|
|
|
if (aes == null) throw new CryptographicException("Unable to create AES object");
|
2018-06-20 19:42:56 +02:00
|
|
|
|
aes.Key = key;
|
|
|
|
|
aes.Mode = CipherMode.ECB;
|
|
|
|
|
aes.Padding = PaddingMode.None;
|
|
|
|
|
var dec = aes.CreateDecryptor();
|
|
|
|
|
using (var ms = new MemoryStream(dest, destIndex, length))
|
|
|
|
|
using (var cs = new CryptoStream(ms, dec, CryptoStreamMode.Write))
|
|
|
|
|
{
|
|
|
|
|
cs.Write(src, srcIndex, length);
|
|
|
|
|
cs.FlushFinalBlock();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static void DecryptEcb(byte[] key, byte[] src, byte[] dest, int length) =>
|
|
|
|
|
DecryptEcb(key, src, 0, dest, 0, length);
|
|
|
|
|
|
2018-08-12 22:45:10 +02:00
|
|
|
|
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;
|
|
|
|
|
var dec = aes.CreateDecryptor();
|
|
|
|
|
using (var ms = new MemoryStream(dest, destIndex, length))
|
|
|
|
|
using (var cs = new CryptoStream(ms, dec, CryptoStreamMode.Write))
|
|
|
|
|
{
|
|
|
|
|
cs.Write(src, srcIndex, length);
|
|
|
|
|
cs.FlushFinalBlock();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static void DecryptCbc(byte[] key, byte[] iv, byte[] src, byte[] dest, int length) =>
|
|
|
|
|
DecryptCbc(key, iv, src, 0, dest, 0, length);
|
|
|
|
|
|
2018-07-13 17:25:39 +02:00
|
|
|
|
public static void GenerateKek(byte[] dst, byte[] src, byte[] masterKey, byte[] kekSeed, byte[] keySeed)
|
2018-06-20 19:42:56 +02:00
|
|
|
|
{
|
2018-08-12 22:45:10 +02:00
|
|
|
|
var kek = new byte[Aes128Size];
|
|
|
|
|
var srcKek = new byte[Aes128Size];
|
|
|
|
|
DecryptEcb(masterKey, kekSeed, kek, Aes128Size);
|
|
|
|
|
DecryptEcb(kek, src, srcKek, Aes128Size);
|
2018-06-20 19:42:56 +02:00
|
|
|
|
|
|
|
|
|
if (keySeed != null)
|
|
|
|
|
{
|
2018-08-12 22:45:10 +02:00
|
|
|
|
DecryptEcb(srcKek, keySeed, dst, Aes128Size);
|
2018-06-20 19:42:56 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
2018-07-13 17:25:39 +02:00
|
|
|
|
|
|
|
|
|
internal static BigInteger GetBigInteger(byte[] bytes)
|
|
|
|
|
{
|
|
|
|
|
byte[] signPadded = new byte[bytes.Length + 1];
|
|
|
|
|
Buffer.BlockCopy(bytes, 0, signPadded, 1, bytes.Length);
|
|
|
|
|
Array.Reverse(signPadded);
|
|
|
|
|
return new BigInteger(signPadded);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static RsaKey DecryptRsaKey(byte[] encryptedKey, byte[] kek)
|
|
|
|
|
{
|
|
|
|
|
var counter = new byte[0x10];
|
|
|
|
|
Array.Copy(encryptedKey, counter, 0x10);
|
|
|
|
|
var body = new byte[0x230];
|
|
|
|
|
Array.Copy(encryptedKey, 0x10, body, 0, 0x230);
|
|
|
|
|
var dec = new byte[0x230];
|
|
|
|
|
|
|
|
|
|
using (var streamDec = new RandomAccessSectorStream(new AesCtrStream(new MemoryStream(body), kek, counter)))
|
|
|
|
|
{
|
|
|
|
|
streamDec.Read(dec, 0, dec.Length);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var d = new byte[0x100];
|
|
|
|
|
var n = new byte[0x100];
|
|
|
|
|
var e = new byte[4];
|
|
|
|
|
Array.Copy(dec, 0, d, 0, 0x100);
|
|
|
|
|
Array.Copy(dec, 0x100, n, 0, 0x100);
|
|
|
|
|
Array.Copy(dec, 0x200, e, 0, 4);
|
|
|
|
|
|
|
|
|
|
var dInt = GetBigInteger(d);
|
|
|
|
|
var nInt = GetBigInteger(n);
|
|
|
|
|
var eInt = GetBigInteger(e);
|
|
|
|
|
|
|
|
|
|
var test = new BigInteger(12345678);
|
|
|
|
|
var testEnc = BigInteger.ModPow(test, dInt, nInt);
|
|
|
|
|
var testDec = BigInteger.ModPow(testEnc, eInt, nInt);
|
|
|
|
|
|
|
|
|
|
if (test != testDec)
|
|
|
|
|
{
|
|
|
|
|
throw new InvalidDataException("Could not verify RSA key pair");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return new RsaKey(n, e, d);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static byte[] DecryptTitleKey(byte[] titleKeyblock, RsaKey rsaKey)
|
|
|
|
|
{
|
|
|
|
|
if (rsaKey == null) return new byte[0x10];
|
|
|
|
|
|
|
|
|
|
var tikInt = GetBigInteger(titleKeyblock);
|
|
|
|
|
var decInt = BigInteger.ModPow(tikInt, rsaKey.DInt, rsaKey.NInt);
|
|
|
|
|
var decBytes = decInt.ToByteArray();
|
|
|
|
|
Array.Reverse(decBytes);
|
|
|
|
|
var decBlock = new byte[0x100];
|
|
|
|
|
Array.Copy(decBytes, 0, decBlock, decBlock.Length - decBytes.Length, decBytes.Length);
|
|
|
|
|
return UnwrapTitleKey(decBlock);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static byte[] UnwrapTitleKey(byte[] data)
|
|
|
|
|
{
|
|
|
|
|
var expectedLabelHash = Ticket.LabelHash;
|
|
|
|
|
var salt = new byte[0x20];
|
|
|
|
|
var db = new byte[0xdf];
|
|
|
|
|
Array.Copy(data, 1, salt, 0, salt.Length);
|
|
|
|
|
Array.Copy(data, 0x21, db, 0, db.Length);
|
|
|
|
|
|
|
|
|
|
CalculateMgf1AndXor(salt, db);
|
|
|
|
|
CalculateMgf1AndXor(db, salt);
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < 0x20; i++)
|
|
|
|
|
{
|
|
|
|
|
if (expectedLabelHash[i] != db[i])
|
|
|
|
|
{
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int keyOffset = 0x20;
|
|
|
|
|
while (keyOffset < 0xdf)
|
|
|
|
|
{
|
|
|
|
|
var value = db[keyOffset++];
|
|
|
|
|
if (value == 1)
|
|
|
|
|
{
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
if (value != 0)
|
|
|
|
|
{
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (keyOffset + 0x10 > db.Length) return null;
|
|
|
|
|
|
|
|
|
|
var key = new byte[0x10];
|
|
|
|
|
Array.Copy(db, keyOffset, key, 0, 0x10);
|
|
|
|
|
|
|
|
|
|
return key;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static void CalculateMgf1AndXor(byte[] masked, byte[] seed)
|
|
|
|
|
{
|
|
|
|
|
SHA256 hash = SHA256.Create();
|
|
|
|
|
var hashBuf = new byte[seed.Length + 4];
|
|
|
|
|
Array.Copy(seed, hashBuf, seed.Length);
|
|
|
|
|
|
|
|
|
|
int maskedSize = masked.Length;
|
|
|
|
|
int roundNum = 0;
|
|
|
|
|
int pOut = 0;
|
|
|
|
|
|
|
|
|
|
while (maskedSize != 0)
|
|
|
|
|
{
|
|
|
|
|
hashBuf[hashBuf.Length - 4] = (byte)(roundNum >> 24);
|
|
|
|
|
hashBuf[hashBuf.Length - 3] = (byte)(roundNum >> 16);
|
|
|
|
|
hashBuf[hashBuf.Length - 2] = (byte)(roundNum >> 8);
|
|
|
|
|
hashBuf[hashBuf.Length - 1] = (byte)roundNum;
|
|
|
|
|
roundNum++;
|
|
|
|
|
|
|
|
|
|
byte[] mask = hash.ComputeHash(hashBuf);
|
|
|
|
|
int curSize = Math.Min(maskedSize, 0x20);
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < curSize; i++, pOut++)
|
|
|
|
|
{
|
|
|
|
|
masked[pOut] ^= mask[i];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
maskedSize -= curSize;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public class RsaKey
|
|
|
|
|
{
|
|
|
|
|
public byte[] N { get; }
|
|
|
|
|
public byte[] E { get; }
|
|
|
|
|
public byte[] D { get; }
|
|
|
|
|
public BigInteger NInt { get; }
|
|
|
|
|
public BigInteger EInt { get; }
|
|
|
|
|
public BigInteger DInt { get; }
|
|
|
|
|
|
|
|
|
|
public RsaKey(byte[] n, byte[] e, byte[] d)
|
|
|
|
|
{
|
|
|
|
|
N = n;
|
|
|
|
|
E = e;
|
|
|
|
|
D = d;
|
|
|
|
|
NInt = Crypto.GetBigInteger(n);
|
|
|
|
|
EInt = Crypto.GetBigInteger(e);
|
|
|
|
|
DInt = Crypto.GetBigInteger(d);
|
|
|
|
|
}
|
2018-06-20 19:42:56 +02:00
|
|
|
|
}
|
|
|
|
|
}
|