Use .NET 4.6+ built-in RSA-OAEP decryption

This commit is contained in:
Alex Barney 2018-08-25 11:38:43 -05:00
parent d701fdd28b
commit d7929fc458
6 changed files with 179 additions and 106 deletions

View file

@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFrameworks>netcoreapp2.1;net45</TargetFrameworks> <TargetFrameworks>netcoreapp2.1;net46</TargetFrameworks>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View file

@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFrameworks>netcoreapp2.1;net45</TargetFrameworks> <TargetFrameworks>netcoreapp2.1;net46</TargetFrameworks>
<LangVersion>7.3</LangVersion> <LangVersion>7.3</LangVersion>
</PropertyGroup> </PropertyGroup>

View file

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>netcoreapp2.1;net45</TargetFrameworks> <TargetFrameworks>netcoreapp2.1;net46</TargetFrameworks>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View file

@ -6,7 +6,7 @@ using libhac.XTSSharp;
namespace libhac namespace libhac
{ {
public class Crypto public static class Crypto
{ {
internal const int Aes128Size = 0x10; internal const int Aes128Size = 0x10;
internal const int Sha256DigestSize = 0x20; internal const int Sha256DigestSize = 0x20;
@ -67,15 +67,15 @@ namespace libhac
} }
} }
internal static BigInteger GetBigInteger(byte[] bytes) private static BigInteger GetBigInteger(byte[] bytes)
{ {
byte[] signPadded = new byte[bytes.Length + 1]; var signPadded = new byte[bytes.Length + 1];
Buffer.BlockCopy(bytes, 0, signPadded, 1, bytes.Length); Buffer.BlockCopy(bytes, 0, signPadded, 1, bytes.Length);
Array.Reverse(signPadded); Array.Reverse(signPadded);
return new BigInteger(signPadded); return new BigInteger(signPadded);
} }
public static RsaKey DecryptRsaKey(byte[] encryptedKey, byte[] kek) public static RSAParameters DecryptRsaKey(byte[] encryptedKey, byte[] kek)
{ {
var counter = new byte[0x10]; var counter = new byte[0x10];
Array.Copy(encryptedKey, counter, 0x10); Array.Copy(encryptedKey, counter, 0x10);
@ -99,120 +99,188 @@ namespace libhac
var nInt = GetBigInteger(n); var nInt = GetBigInteger(n);
var eInt = GetBigInteger(e); var eInt = GetBigInteger(e);
var test = new BigInteger(12345678); RSAParameters rsaParams = RecoverRsaParameters(nInt, eInt, dInt);
var testEnc = BigInteger.ModPow(test, dInt, nInt); TestRsaKey(rsaParams);
var testDec = BigInteger.ModPow(testEnc, eInt, nInt); return rsaParams;
}
if (test != testDec) private static void TestRsaKey(RSAParameters keyParams)
{
var rsa = new RSACryptoServiceProvider();
rsa.ImportParameters(keyParams);
var test = new byte[] {12, 34, 56, 78};
byte[] testEnc = rsa.Encrypt(test, false);
byte[] testDec = rsa.Decrypt(testEnc, false);
if (!Util.ArraysEqual(test, testDec))
{ {
throw new InvalidDataException("Could not verify RSA key pair"); throw new InvalidDataException("Could not verify RSA key pair");
} }
return new RsaKey(n, e, d);
} }
public static byte[] DecryptTitleKey(byte[] titleKeyblock, RsaKey rsaKey) public static byte[] DecryptTitleKey(byte[] titleKeyblock, RSAParameters rsaParams)
{ {
if (rsaKey == null) return new byte[0x10]; #if USE_RSA_CNG
RSA rsa = new RSACng();
var tikInt = GetBigInteger(titleKeyblock); #else
var decInt = BigInteger.ModPow(tikInt, rsaKey.DInt, rsaKey.NInt); RSA rsa = RSA.Create();
var decBytes = decInt.ToByteArray(); #endif
Array.Reverse(decBytes); rsa.ImportParameters(rsaParams);
var decBlock = new byte[0x100]; return rsa.Decrypt(titleKeyblock, RSAEncryptionPadding.OaepSHA256);
Array.Copy(decBytes, 0, decBlock, decBlock.Length - decBytes.Length, decBytes.Length);
return UnwrapTitleKey(decBlock);
} }
public static byte[] UnwrapTitleKey(byte[] data) private static RSAParameters RecoverRsaParameters(BigInteger n, BigInteger e, BigInteger d)
{ {
var expectedLabelHash = Ticket.LabelHash; using (RandomNumberGenerator rng = RandomNumberGenerator.Create())
var salt = new byte[0x20]; {
var db = new byte[0xdf]; BigInteger k = d * e - 1;
Array.Copy(data, 1, salt, 0, salt.Length);
Array.Copy(data, 0x21, db, 0, db.Length);
CalculateMgf1AndXor(salt, db); if (!k.IsEven)
CalculateMgf1AndXor(db, salt);
for (int i = 0; i < 0x20; i++)
{ {
if (expectedLabelHash[i] != db[i]) throw new InvalidOperationException("d*e - 1 is odd");
{
return null;
}
} }
int keyOffset = 0x20; BigInteger two = 2;
while (keyOffset < 0xdf) BigInteger t = BigInteger.One;
BigInteger r = k / two;
while (r.IsEven)
{ {
var value = db[keyOffset++]; t++;
if (value == 1) 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; break;
} }
if (value != 0)
y = x;
}
}
if (!cracked)
{ {
return null; throw new InvalidOperationException("Prime factors not found");
}
} }
if (keyOffset + 0x10 > db.Length) return null; 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);
var key = new byte[0x10]; int modLen = rndBuf.Length;
Array.Copy(db, keyOffset, key, 0, 0x10); int halfModLen = (modLen + 1) / 2;
return key; return new RSAParameters
}
private static void CalculateMgf1AndXor(byte[] masked, byte[] seed)
{ {
SHA256 hash = SHA256.Create(); Modulus = GetBytes(n, modLen),
var hashBuf = new byte[seed.Length + 4]; Exponent = GetBytes(e, -1),
Array.Copy(seed, hashBuf, seed.Length); D = GetBytes(d, modLen),
P = GetBytes(p, halfModLen),
Q = GetBytes(q, halfModLen),
DP = GetBytes(dp, halfModLen),
DQ = GetBytes(dq, halfModLen),
InverseQ = GetBytes(inverseQ, halfModLen),
};
}
}
int maskedSize = masked.Length; private static byte[] GetBytes(BigInteger value, int size)
int roundNum = 0;
int pOut = 0;
while (maskedSize != 0)
{ {
hashBuf[hashBuf.Length - 4] = (byte)(roundNum >> 24); byte[] bytes = value.ToByteArray();
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); if (size == -1)
int curSize = Math.Min(maskedSize, 0x20);
for (int i = 0; i < curSize; i++, pOut++)
{ {
masked[pOut] ^= mask[i]; size = bytes.Length;
} }
maskedSize -= curSize; if (bytes.Length > size + 1)
}
}
}
public class RsaKey
{ {
public byte[] N { get; } throw new InvalidOperationException($"Cannot squeeze value {value} to {size} bytes from {bytes.Length}.");
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) if (bytes.Length == size + 1 && bytes[bytes.Length - 1] != 0)
{ {
N = n; throw new InvalidOperationException($"Cannot squeeze value {value} to {size} bytes from {bytes.Length}.");
E = e; }
D = d;
NInt = Crypto.GetBigInteger(n); Array.Resize(ref bytes, size);
EInt = Crypto.GetBigInteger(e); Array.Reverse(bytes);
DInt = Crypto.GetBigInteger(d); 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;
} }
} }
} }

View file

@ -4,6 +4,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Security.Cryptography;
namespace libhac namespace libhac
{ {
@ -46,7 +47,7 @@ namespace libhac
public byte[] device_key { get; set; } = new byte[0x10]; public byte[] device_key { get; set; } = new byte[0x10];
public byte[][] bis_keys { get; set; } = Util.CreateJaggedArray<byte[][]>(4, 0x20); public byte[][] bis_keys { get; set; } = Util.CreateJaggedArray<byte[][]>(4, 0x20);
public byte[] sd_seed { get; set; } = new byte[0x10]; public byte[] sd_seed { get; set; } = new byte[0x10];
public RsaKey eticket_ext_key_rsa { get; set; } public RSAParameters eticket_ext_key_rsa { get; set; }
public Dictionary<byte[], byte[]> TitleKeys { get; } = new Dictionary<byte[], byte[]>(new ByteArray128BitComparer()); public Dictionary<byte[], byte[]> TitleKeys { get; } = new Dictionary<byte[], byte[]>(new ByteArray128BitComparer());

View file

@ -2,12 +2,16 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Library</OutputType> <OutputType>Library</OutputType>
<TargetFrameworks>netcoreapp2.1;net45</TargetFrameworks> <TargetFrameworks>netcoreapp2.1;net46</TargetFrameworks>
<LangVersion>7.3</LangVersion> <LangVersion>7.3</LangVersion>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(TargetFramework)' == 'net46' ">
<DefineConstants>$(DefineConstants);USE_RSA_CNG</DefineConstants>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="System.Numerics.Vectors" Version="4.5.0" Condition=" '$(TargetFramework)' == 'net45' " /> <PackageReference Include="System.Numerics.Vectors" Version="4.5.0" Condition=" '$(TargetFramework)' == 'net46' " />
</ItemGroup> </ItemGroup>
</Project> </Project>