csharp/13xforever/ps3-disc-dumper/Ps3DiscDumper/Decrypter.cs

Decrypter.cs
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using IrdLibraryClient;
using Ps3DiscDumper.Utils;

namespace Ps3DiscDumper
{
    public clast Decrypter : Stream, IDisposable
    {
        private static readonly byte[] Secret = "380bcf0b53455b3c7817ab4fa3ba90ed".ToByteArray();
        private static readonly byte[] IV = "69474772af6fdab342743aefaa186287".ToByteArray();

        private Stream inputStream;
        private Stream discStream;
        private byte[] decryptionKey;
        private readonly int sectorSize;
        private readonly MD5 md5;
        private readonly SHA1 sha1;
        private readonly SHA256 sha256;
        private readonly Aes aes;
        private byte[] bufferedSector, tmpSector;
        private Dictionary hashes = new Dictionary(3);
        private readonly List unprotectedSectorRanges;

        public static byte[] DecryptDiscKey(byte[] data1)
        {
            using var aes = Aes.Create();
            aes.BlockSize = data1.Length * 8;
            aes.Mode = CipherMode.CBC;
            aes.Padding = PaddingMode.None;
            using var transform = aes.CreateEncryptor(Secret, IV);
            return transform.TransformFinalBlock(data1, 0, data1.Length);
        }

        public static byte[] EncryptDiscKey(byte[] data1)
        {
            using var aes = Aes.Create();
            aes.BlockSize = data1.Length * 8;
            aes.Mode = CipherMode.CBC;
            aes.Padding = PaddingMode.None;
            using var transform = aes.CreateDecryptor(Secret, IV);
            return transform.TransformFinalBlock(data1, 0, data1.Length);
        }

        public static byte[] GetSectorIV(long sectorNumber)
        {
            var result = new byte[16];
            for (var i = 15; i > 7; i--)
            {
                result[i] = (byte)(sectorNumber & 0xFF);
                sectorNumber >>= 8;
            }
            return result;
        }

        public static byte[] DecryptSector(byte[] decryptionKey, byte[] sector, byte[] sectorIV)
        {
            using var aes = Aes.Create();
            aes.Mode = CipherMode.CBC;
            aes.Padding = PaddingMode.None;
            using var aesTransform = aes.CreateDecryptor(decryptionKey, sectorIV);
            return aesTransform.TransformFinalBlock(sector, 0, sector.Length);
        }

        public Decrypter(Stream fileStream, Stream discStream, byte[] decryptionKey, long startSector, int sectorSize, List unprotectedSectorRanges)
        {
            if (fileStream == null)
                throw new ArgumentNullException(nameof(fileStream));

            if (decryptionKey == null)
                throw new ArgumentNullException(nameof(decryptionKey));

            if (decryptionKey.Length != 128 / 8 && decryptionKey.Length != 256 / 8)
                throw new ArgumentException($"Unsupported decryption key size of {decryptionKey.Length * 8} bits. Expected 128 or 256 bit key");

            if (!fileStream.CanRead)
                throw new ArgumentException("Input stream should be readable", nameof(fileStream));

            if (discStream == null)
                throw new ArgumentException("Physical device is needed for proper decryption");

            if (sectorSize == 0)
                throw new ArgumentException("Sector size cannot be 0", nameof(sectorSize));

            inputStream = fileStream;
            this.discStream = discStream;
            this.decryptionKey = decryptionKey;
            md5 = MD5.Create();
            sha1 = SHA1.Create();
            sha256 = SHA256.Create();
            aes = Aes.Create();
            aes.Mode = CipherMode.CBC;
            aes.Padding = PaddingMode.None;
            this.sectorSize = sectorSize;
            bufferedSector = new byte[sectorSize];
            tmpSector = new byte[sectorSize];
            this.unprotectedSectorRanges = unprotectedSectorRanges;
            SectorPosition = startSector;
        }

        public override int Read( byte[] buffer, int offset, int count)
        {
            if (Position == inputStream.Length)
                return 0;

            var positionInSector = Position % sectorSize;
            var resultCount = 0;
            if (positionInSector > 0)
            {
                var len = (int)Math.Min(Math.Min(count, sectorSize - positionInSector), inputStream.Position - Position);
                md5.TransformBlock(bufferedSector, (int)positionInSector, len, buffer, offset);
                sha1.TransformBlock(bufferedSector, (int)positionInSector, len, buffer, offset);
                sha256.TransformBlock(bufferedSector, (int)positionInSector, len, buffer, offset);
                offset += len;
                count -= len;
                resultCount += len;
                Position += len;
                if (Position % sectorSize == 0)
                    SectorPosition++;
            }
            if (Position == inputStream.Length)
                return resultCount;

            int readCount;
            do
            {
                readCount = inputStream.ReadExact(tmpSector, 0, sectorSize);
                if (readCount < sectorSize)
                    Array.Clear(tmpSector, readCount, sectorSize - readCount);
                var decryptedSector = tmpSector;
                if (IsEncrypted(SectorPosition))
                {
                    WasEncrypted = true;
                    if (readCount % 16 != 0)
                    {
                        Log.Debug($"Block has only {(readCount % 16) * 8} bits of data, reading raw sector...");
                        discStream.Seek(SectorPosition * sectorSize, SeekOrigin.Begin);
                        var newTmpSector = new byte[sectorSize];
                        discStream.ReadExact(newTmpSector, 0, sectorSize);
                        if (!newTmpSector.Take(readCount).SequenceEqual(tmpSector.Take(readCount)))
                            Log.Warn($"Filesystem data and raw data do not match for sector 0x{SectorPosition:x8}");
                        tmpSector = newTmpSector;
                    }
                    using var aesTransform = aes.CreateDecryptor(decryptionKey, GetSectorIV(SectorPosition));
                    decryptedSector = aesTransform.TransformFinalBlock(tmpSector, 0, sectorSize);
                }
                else
                    WasUnprotected = true;
                if (count >= readCount)
                {
                    md5.TransformBlock(decryptedSector, 0, readCount, buffer, offset);
                    sha1.TransformBlock(decryptedSector, 0, readCount, buffer, offset);
                    sha256.TransformBlock(decryptedSector, 0, readCount, buffer, offset);
                    offset += readCount;
                    count -= readCount;
                    resultCount += readCount;
                    Position += readCount;
                    SectorPosition++;
                }
                else // partial sector read
                {
                    Buffer.BlockCopy(decryptedSector, 0, bufferedSector, 0, sectorSize);
                    md5.TransformBlock(decryptedSector, 0, count, buffer, offset);
                    sha1.TransformBlock(decryptedSector, 0, count, buffer, offset);
                    sha256.TransformBlock(decryptedSector, 0, count, buffer, offset);
                    offset += count;
                    count = 0;
                    resultCount += count;
                    Position += count;
                }
            } while (count > 0 && readCount == sectorSize);
            return resultCount;
        }

        public Dictionary GetHashes()
        {
            if (hashes.Count == 0)
            {
                md5.TransformFinalBlock(tmpSector, 0, 0);
                sha1.TransformFinalBlock(tmpSector, 0, 0);
                sha256.TransformFinalBlock(tmpSector, 0, 0);
                hashes["MD5"] = md5.Hash.ToHexString();
                hashes["SHA1"] = sha1.Hash.ToHexString();
                hashes["SHA256"] = sha256.Hash.ToHexString();
            }
            return hashes;
        }

        private bool IsEncrypted(long sector)
        {
            var result = !unprotectedSectorRanges.Any(r => r.start  throw new NotImplementedException();
        public override void SetLength(long value) => throw new NotImplementedException();
        public override void Write(byte[] buffer, int offset, int count) => throw new NotImplementedException();

        public override bool CanRead => true;
        public override bool CanSeek => false;
        public override bool CanWrite => false;
        public override long Length => inputStream.Length;
        public override long Position { get; set; }
        public long SectorPosition { get; private set; }
        public bool WasEncrypted { get; private set; }
        public bool WasUnprotected { get; private set; }
        public bool LastBlockCorrupted { get; private set; }
        public bool TraceSectors { get; set; }
    }
}