Decrypt and hash files, further cleaning

This commit is contained in:
Deterous
2023-11-10 16:13:56 +13:00
parent db453da1ee
commit 8bebd9a722
4 changed files with 355 additions and 156 deletions
+222 -59
View File
@@ -1,5 +1,5 @@
using DiscUtils.Iso9660;
using DiscUtils;
using DiscUtils;
using DiscUtils.Iso9660;
using System;
using System.IO;
using System.IO.Compression;
@@ -12,27 +12,33 @@ namespace LibIRD
/// <summary>
/// ISO Rebuild Data
/// </summary>
/// <remarks>Generates IRD fields and writes to an IRD file</remarks>
/// <remarks>Generates IRD fields and reads/writes IRD files</remarks>
public class IRD
{
#region Constants
/// <summary>
/// Blu-ray ISO sector size in bytes
/// </summary>
/// <remarks>2048</remarks>
private protected const uint SectorSize = 2048;
/// <summary>
/// Size of a blu-ray layer in bytes (BD-25 max size)
/// </summary>
/// <remarks>12219392 sectors, default PS3 layerbreak value</remarks>
private protected const long BDLayerSize = 25025314816;
/// <summary>
/// IRD file signature
/// </summary>
/// <remarks>"3IRD"</remarks>
private static readonly byte[] Magic = { 0x33, 0x49, 0x52, 0x44 };
/// <summary>
/// Blu-ray ISO sector size in bytes
/// </summary>
/// <remarks>2048</remarks>
private protected static readonly uint SectorSize = 2048;
/// <summary>
/// MD5 hash of null
/// </summary>
private protected static readonly byte[] NullMD5 = new byte[] { 0xd4, 0x1d, 0x8c, 0xd9, 0x8f, 0x00, 0xb2, 0x04, 0xe9, 0x80, 0x09, 0x98, 0xec, 0xf8, 0x42, 0x7e };
private static readonly byte[] NullMD5 = new byte[] { 0xd4, 0x1d, 0x8c, 0xd9, 0x8f, 0x00, 0xb2, 0x04, 0xe9, 0x80, 0x09, 0x98, 0xec, 0xf8, 0x42, 0x7e };
/// <summary>
/// AES CBC Encryption Key for Data 1 (Disc Key)
@@ -91,6 +97,27 @@ namespace LibIRD
/// <remarks>Reserved, usually set to 0x0000</remarks>
public ushort Attachments { get; set; } = 0x0000; // Default to zero
/// <summary>
/// Disc Key
/// </summary>
/// <remarks>16 bytes</remarks>
public byte[] DiscKey
{
get { return _discKey; }
set
{
if (value != null && value.Length == 16)
{
_discKey = value;
_data1Key = GenerateD1(value);
}
else
throw new ArgumentException("Disc Key must be a byte array of length 16", nameof(value));
}
}
private byte[] _discKey;
// TODO: Link Data1Key and Disc Key
/// <summary>
/// D1 key
/// </summary>
@@ -101,13 +128,36 @@ namespace LibIRD
set
{
if (value != null && value.Length == 16)
{
_data1Key = value;
_discKey = GenerateDiscKey(value);
}
else
throw new ArgumentException("Data 1 Key must be a byte array of length 16", nameof(value));
}
}
private byte[] _data1Key;
/// <summary>
/// D2 key
/// </summary>
/// <remarks>16 bytes</remarks>
public byte[] DiscID
{
get { return _discID; }
set
{
if (value != null && value.Length == 16)
{
_discID = value;
_data2Key = GenerateD2(value);
}
else
throw new ArgumentException("Disc ID must be a byte array of length 16", nameof(value));
}
}
private byte[] _discID;
/// <summary>
/// D2 key
/// </summary>
@@ -118,7 +168,10 @@ namespace LibIRD
set
{
if (value != null && value.Length == 16)
{
_data2Key = value;
_discID = GenerateDiscID(value);
}
else
throw new ArgumentException("Data 2 Key must be a byte array of length 16", nameof(value));
}
@@ -247,8 +300,6 @@ namespace LibIRD
/// </summary>
private long[] RegionEnd { get; set; }
private protected long Size { get; private set; }
#endregion
#region Constructors
@@ -301,10 +352,9 @@ namespace LibIRD
/// <summary>
/// Default constructor for internal derived classes only: resulting object not in usable state
/// </summary>
private protected IRD(string isoPath)
private protected IRD()
{
// Assumes that internally derived class will set D1/D2/PIC in its own constructor
GenerateIRD(isoPath);
// Assumes that internally derived class will set fields in its own constructor
}
/// <summary>
@@ -317,6 +367,7 @@ namespace LibIRD
public IRD(string isoPath, byte[] discKey, byte[] discID, byte[] discPIC)
{
// Parse ISO, Disc Key, Disc ID, and PIC
DiscKey = discKey;
GenerateD1(discKey);
GenerateD2(discID);
PIC = discPIC;
@@ -343,7 +394,7 @@ namespace LibIRD
#endregion
#region Methods
#region Property Generation
/// <summary>
/// Generates Data 1, via AES-128 CBC decryption of a Disc Key
@@ -351,7 +402,8 @@ namespace LibIRD
/// <param name="key">Byte array containing AES encrypted Disc Key</param>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="ArgumentException"></exception>
private protected void GenerateD1(byte[] key)
/// <exception cref="InvalidOperationException"></exception>
private protected static byte[] GenerateD1(byte[] key)
{
// Validate key
if (key == null)
@@ -376,22 +428,58 @@ namespace LibIRD
cs.FlushFinalBlock();
// Save decrypted key to field
Data1Key = stream.ToArray();
return stream.ToArray();
}
/// <summary>
/// Generates the Disc key, via AES-128 CBC encryption of a Data 1 Key
/// </summary>
/// <param name="d1">Byte array containing AES decrypted Data 1 Key</param>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="ArgumentException"></exception>
/// <exception cref="InvalidOperationException"></exception>
private protected static byte[] GenerateDiscKey(byte[] d1)
{
// Validate key
if (d1 == null)
throw new ArgumentNullException(nameof(d1));
if (d1.Length != 16)
throw new ArgumentException("Disc Key must be a byte array of length 16", nameof(d1));
// Setup AES decryption
using Aes aes = Aes.Create() ?? throw new InvalidOperationException("AES not available. Change your system settings");
// Set AES settings
aes.Key = D1AesKey;
aes.IV = D1AesIV;
aes.Padding = PaddingMode.None;
aes.Mode = CipherMode.CBC;
// Perform AES decryption
using MemoryStream stream = new();
using ICryptoTransform enc = aes.CreateEncryptor();
using CryptoStream cs = new(stream, enc, CryptoStreamMode.Write);
cs.Write(d1, 0, 16);
cs.FlushFinalBlock();
// Save decrypted key to field
return stream.ToArray();
}
/// <summary>
/// Generates Data 2, via AES-128 CBC encryption of a Disc ID
/// </summary>
/// <param name="id">Byte array containing AES decrypted Disc ID</param>
/// <param name="d2">Byte array containing AES decrypted Disc ID</param>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="ArgumentException"></exception>
private protected void GenerateD2(byte[] id)
/// <exception cref="InvalidOperationException"></exception>
private protected static byte[] GenerateD2(byte[] d2)
{
// Validate id
if (id == null)
throw new ArgumentNullException(nameof(id));
if (id.Length != 16)
throw new ArgumentException("Disc ID must be a byte array of length 16", nameof(id));
if (d2 == null)
throw new ArgumentNullException(nameof(d2));
if (d2.Length != 16)
throw new ArgumentException("Disc ID must be a byte array of length 16", nameof(d2));
// Setup AES encryption
using Aes aes = Aes.Create() ?? throw new InvalidOperationException("AES not available. Change your system settings");
@@ -406,14 +494,56 @@ namespace LibIRD
using MemoryStream stream = new();
using ICryptoTransform enc = aes.CreateEncryptor();
using CryptoStream cs = new(stream, enc, CryptoStreamMode.Write);
cs.Write(id, 0, 16);
cs.Write(d2, 0, 16);
cs.FlushFinalBlock();
// Save encrypted key to field
Data2Key = stream.ToArray();
return stream.ToArray();
}
private void ParseGetKeyLog(string getKeyLog)
/// <summary>
/// Generates Disc ID, via AES-128 CBC decryption of a Data 2 Key
/// </summary>
/// <param name="d2">Byte array containing AES encrypted Data 2 Key</param>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="ArgumentException"></exception>
/// <exception cref="InvalidOperationException"></exception>
private protected static byte[] GenerateDiscID(byte[] d2)
{
// Validate id
if (d2 == null)
throw new ArgumentNullException(nameof(d2));
if (d2.Length != 16)
throw new ArgumentException("Disc ID must be a byte array of length 16", nameof(d2));
// Setup AES encryption
using Aes aes = Aes.Create() ?? throw new InvalidOperationException("AES not available. Change your system settings");
// Set AES settings
aes.Key = D2AesKey;
aes.IV = D2AesIV;
aes.Padding = PaddingMode.None;
aes.Mode = CipherMode.CBC;
// Perform AES encryption
using MemoryStream stream = new();
using ICryptoTransform dec = aes.CreateDecryptor();
using CryptoStream cs = new(stream, dec, CryptoStreamMode.Write);
cs.Write(d2, 0, 16);
cs.FlushFinalBlock();
// Save encrypted key to field
return stream.ToArray();
}
/// <summary>
/// Generates Data1Key, Data2Key, and PIC from the .getkey.log file
/// </summary>
/// <param name="getKeyLog">Path to the .getkey.log file</param>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="FileNotFoundException"></exception>
/// <exception cref="InvalidDataException"></exception>
private protected void ParseGetKeyLog(string getKeyLog)
{
// Validate .getkey.log file path
@@ -485,8 +615,8 @@ namespace LibIRD
}
// Parse DiscKey, DiscID, and PIC
GenerateD1(discKey);
GenerateD2(discID);
DiscKey = discKey;
DiscID = discID;
PIC = discPIC;
}
@@ -497,20 +627,8 @@ namespace LibIRD
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="FileNotFoundException"></exception>
/// <exception cref="InvalidFileSystemException"></exception>
private void GenerateIRD(string isoPath)
private protected void GenerateIRD(string isoPath)
{
// Validate ISO path
if (isoPath == null || isoPath.Length <= 0)
throw new ArgumentNullException(nameof(isoPath));
// Check file exists
var iso = new FileInfo(isoPath);
if (!iso.Exists)
throw new FileNotFoundException(nameof(isoPath));
// Calculate file size
Size = iso.Length;
// Parse ISO file as a file stream
using FileStream fs = new FileStream(isoPath, FileMode.Open, FileAccess.Read) ?? throw new FileNotFoundException(isoPath);
// Validate ISO file stream
@@ -562,6 +680,10 @@ namespace LibIRD
Array.Sort(FileKeys, FileHashes);
}
#endregion
#region Reading ISO
/// <summary>
/// Retreives and stores the system version
/// </summary>
@@ -782,47 +904,51 @@ namespace LibIRD
if (fileExtents.Length > 1)
throw new InvalidFileSystemException("Non-contiguous file detected");
long firstByte = fileExtents[0].Start;
int firstSector = (int)(firstByte / 2048);
long fileLength = fileExtents[0].Length;
FileKeys[FileCount] = firstByte / 2048;
FileKeys[FileCount] = firstSector;
// Determine whether file is in encrypted or decrypted region
bool encrypted = false;
for (int i = RegionCount - 1; i > 0; i--)
{
if (RegionStart[i] <= firstByte / 2048)
if (RegionStart[i] <= firstSector)
{
encrypted = i % 2 == 1;
break;
}
}
// Decrypt file if encrypted
if (encrypted)
{
FileHashes[FileCount] = NullMD5;
FileCount++;
continue;
}
// Start reading data from the beginning of the ISO file
fs.Seek(firstByte, SeekOrigin.Begin);
byte[] buf = new byte[SectorSize];
int numBytes;
// Read all data before the first data sector
MD5 md5 = MD5.Create();
for (long i = 0; i < (fileLength / SectorSize); i++)
for (int i = 0; i < (fileLength / SectorSize); i++)
{
numBytes = fs.Read(buf, 0, buf.Length);
// Check that an entire sector was read
if (numBytes < buf.Length)
throw new InvalidFileSystemException("Disc region ended unexpectedly");
// Decrypt sector if necessary
if (encrypted)
buf = DecryptSector(buf, firstSector + i);
// Hash sector
md5.TransformBlock(buf, 0, numBytes, null, 0);
}
// Read remaining partial sector
if (fileLength % SectorSize != 0)
{
numBytes = fs.Read(buf, 0, (int)(fileLength % SectorSize));
md5.TransformBlock(buf, 0, numBytes, null, 0);
numBytes = fs.Read(buf, 0, buf.Length);
// Check that an entire sector was read
if (numBytes < buf.Length)
throw new InvalidFileSystemException("Disc region ended unexpectedly");
// Decrypt partial sector if necessary
if (encrypted)
buf = DecryptSector(buf, firstSector + (int)(fileLength / SectorSize));
// Hash partial sector
md5.TransformBlock(buf, 0, (int)(fileLength % SectorSize), null, 0);
}
// Finalise and store MD5 hash
@@ -838,6 +964,41 @@ namespace LibIRD
}
}
/// <summary>
/// Decrypts a given sector byte array
/// </summary>
/// <param name="sector">Byte array to be decrypted</param>
/// <exception cref="InvalidOperationException"></exception>
private protected byte[] DecryptSector(byte[] sector, int sectorNumber)
{
// Setup AES decryption
using Aes aes = Aes.Create() ?? throw new InvalidOperationException("AES not available. Change your system settings");
// Set AES settings
aes.Key = DiscKey;
aes.Padding = PaddingMode.None;
aes.Mode = CipherMode.CBC;
// Determine Initial Value based on sector number
byte[] iv = new byte[16];
for (int i = 0; i < 16; i++)
{
byte a = (byte)(sectorNumber & 0xFF);
iv[16 - i - 1] = (byte)(sectorNumber & 0xFF);
sectorNumber >>= 8;
}
aes.IV = iv;
// Perform AES decryption
using MemoryStream stream = new();
using ICryptoTransform dec = aes.CreateDecryptor();
using CryptoStream cs = new(stream, dec, CryptoStreamMode.Write);
cs.Write(sector, 0, sector.Length);
cs.FlushFinalBlock();
return stream.ToArray();
}
/// <summary>
/// Recursively determines file count
/// </summary>
@@ -849,6 +1010,10 @@ namespace LibIRD
CountFiles(dirInfo);
}
#endregion
#region IRD File
/// <summary>
/// Write IRD data to file
/// </summary>
@@ -1011,13 +1176,11 @@ namespace LibIRD
// Read Header
uint headerLength = br.ReadUInt32();
byte[] header = new byte[headerLength];
br.Read(header, 0, header.Length);
byte[] header = br.ReadBytes((int)headerLength);
// Read Footer
uint footerLength = br.ReadUInt32();
byte[] footer = new byte[footerLength];
br.Read(footer, 0, footer.Length);
byte[] footer = br.ReadBytes((int)footerLength);
// Read region hashes
byte regionCount = br.ReadByte();