From d678275dee2ffa823c02b8845031498c8e961cec Mon Sep 17 00:00:00 2001 From: Lars Vahlenberg Date: Sat, 16 Dec 2023 02:53:09 +0100 Subject: [PATCH 1/2] Implement RAR5 decryption --- .../Archives/Rar/SeekableFilePart.cs | 11 +- src/SharpCompress/Common/Rar/CryptKey3.cs | 87 + src/SharpCompress/Common/Rar/CryptKey5.cs | 97 + .../Common/Rar/Headers/ArchiveCryptHeader.cs | 42 +- .../Common/Rar/Headers/FileHeader.cs | 61 +- src/SharpCompress/Common/Rar/Headers/Flags.cs | 11 + .../Common/Rar/Headers/RarHeader.cs | 2 +- .../Common/Rar/Headers/RarHeaderFactory.cs | 32 +- src/SharpCompress/Common/Rar/ICryptKey.cs | 12 + .../Common/Rar/Rar5CryptoInfo.cs | 59 + .../Common/Rar/RarCryptoBinaryReader.cs | 45 +- .../Common/Rar/RarCryptoWrapper.cs | 16 +- src/SharpCompress/Common/Rar/RarRijndael.cs | 114 - src/SharpCompress/Crypto/BlockTransformer.cs | 32 + src/SharpCompress/Crypto/RijndaelEngine.cs | 1909 ----------------- .../SharpCompress.Test/Rar/RarArchiveTests.cs | 32 +- 16 files changed, 425 insertions(+), 2137 deletions(-) create mode 100644 src/SharpCompress/Common/Rar/CryptKey3.cs create mode 100644 src/SharpCompress/Common/Rar/CryptKey5.cs create mode 100644 src/SharpCompress/Common/Rar/ICryptKey.cs create mode 100644 src/SharpCompress/Common/Rar/Rar5CryptoInfo.cs delete mode 100644 src/SharpCompress/Common/Rar/RarRijndael.cs create mode 100644 src/SharpCompress/Crypto/BlockTransformer.cs delete mode 100644 src/SharpCompress/Crypto/RijndaelEngine.cs diff --git a/src/SharpCompress/Archives/Rar/SeekableFilePart.cs b/src/SharpCompress/Archives/Rar/SeekableFilePart.cs index 08aceb13..13048d4c 100644 --- a/src/SharpCompress/Archives/Rar/SeekableFilePart.cs +++ b/src/SharpCompress/Archives/Rar/SeekableFilePart.cs @@ -25,10 +25,19 @@ internal SeekableFilePart( internal override Stream GetCompressedStream() { stream.Position = FileHeader.DataStartPosition; + if (FileHeader.R4Salt != null) { - return new RarCryptoWrapper(stream, password!, FileHeader.R4Salt); + var cryptKey = new CryptKey3(password!); + return new RarCryptoWrapper(stream, FileHeader.R4Salt, cryptKey); } + + if (FileHeader.Rar5CryptoInfo != null) + { + var cryptKey = new CryptKey5(password!, FileHeader.Rar5CryptoInfo); + return new RarCryptoWrapper(stream, FileHeader.Rar5CryptoInfo.Salt, cryptKey); + } + return stream; } diff --git a/src/SharpCompress/Common/Rar/CryptKey3.cs b/src/SharpCompress/Common/Rar/CryptKey3.cs new file mode 100644 index 00000000..254c692f --- /dev/null +++ b/src/SharpCompress/Common/Rar/CryptKey3.cs @@ -0,0 +1,87 @@ +#nullable disable + +using System.Security.Cryptography; +using System.Text; +using SharpCompress.Common.Rar.Headers; + +namespace SharpCompress.Common.Rar; + +internal class CryptKey3 : ICryptKey +{ + const int AES_128 = 128; + + private string _password; + + public CryptKey3(string password) + { + _password = password ?? ""; + } + + public ICryptoTransform Transformer(byte[] salt) + { + var aesIV = new byte[EncryptionConstV5.SIZE_INITV]; + + var rawLength = 2 * _password.Length; + var rawPassword = new byte[rawLength + EncryptionConstV5.SIZE_SALT30]; + var passwordBytes = Encoding.UTF8.GetBytes(_password); + for (var i = 0; i < _password.Length; i++) + { + rawPassword[i * 2] = passwordBytes[i]; + rawPassword[(i * 2) + 1] = 0; + } + + for (var i = 0; i < salt.Length; i++) + { + rawPassword[i + rawLength] = salt[i]; + } + + var msgDigest = SHA1.Create(); + const int noOfRounds = (1 << 18); + const int iblock = 3; + + byte[] digest; + var data = new byte[(rawPassword.Length + iblock) * noOfRounds]; + + //TODO slow code below, find ways to optimize + for (var i = 0; i < noOfRounds; i++) + { + rawPassword.CopyTo(data, i * (rawPassword.Length + iblock)); + + data[(i * (rawPassword.Length + iblock)) + rawPassword.Length + 0] = (byte)i; + data[(i * (rawPassword.Length + iblock)) + rawPassword.Length + 1] = (byte)(i >> 8); + data[(i * (rawPassword.Length + iblock)) + rawPassword.Length + 2] = (byte)(i >> 16); + + if (i % (noOfRounds / EncryptionConstV5.SIZE_INITV) == 0) + { + digest = msgDigest.ComputeHash(data, 0, (i + 1) * (rawPassword.Length + iblock)); + aesIV[i / (noOfRounds / EncryptionConstV5.SIZE_INITV)] = digest[19]; + } + } + digest = msgDigest.ComputeHash(data); + //slow code ends + + var aesKey = new byte[EncryptionConstV5.SIZE_INITV]; + for (var i = 0; i < 4; i++) + { + for (var j = 0; j < 4; j++) + { + aesKey[(i * 4) + j] = (byte)( + ( + ((digest[i * 4] * 0x1000000) & 0xff000000) + | (uint)((digest[(i * 4) + 1] * 0x10000) & 0xff0000) + | (uint)((digest[(i * 4) + 2] * 0x100) & 0xff00) + | (uint)(digest[(i * 4) + 3] & 0xff) + ) >> (j * 8) + ); + } + } + + var aes = Aes.Create(); + aes.KeySize = AES_128; + aes.Mode = CipherMode.CBC; + aes.Padding = PaddingMode.None; + aes.Key = aesKey; + aes.IV = aesIV; + return aes.CreateDecryptor(); + } +} diff --git a/src/SharpCompress/Common/Rar/CryptKey5.cs b/src/SharpCompress/Common/Rar/CryptKey5.cs new file mode 100644 index 00000000..46044368 --- /dev/null +++ b/src/SharpCompress/Common/Rar/CryptKey5.cs @@ -0,0 +1,97 @@ +using System.Collections.Generic; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using SharpCompress.Common.Rar.Headers; + +namespace SharpCompress.Common.Rar; + +internal class CryptKey5 : ICryptKey +{ + const int AES_256 = 256; + const int DERIVED_KEY_LENGTH = 0x10; + const int SHA256_DIGEST_SIZE = 32; + + private string _password; + private Rar5CryptoInfo _cryptoInfo; + private byte[] _pswCheck = { }; + private byte[] _hashKey = { }; + + public CryptKey5(string password, Rar5CryptoInfo rar5CryptoInfo) + { + _password = password; + _cryptoInfo = rar5CryptoInfo; + } + + public byte[] PswCheck => _pswCheck; + + public byte[] HashKey => _hashKey; + + private static List GenerateRarPBKDF2Key( + string password, + byte[] salt, + int iterations, + int keyLength + ) + { + using (HMACSHA256 hmac = new HMACSHA256(Encoding.UTF8.GetBytes(password))) + { + byte[] block = hmac.ComputeHash(salt); + byte[] finalHash = (byte[])block.Clone(); + + var loop = new int[] { iterations, 17, 17 }; + var res = new List { }; + + for (int x = 0; x < 3; x++) + { + for (int i = 1; i < loop[x]; i++) + { + block = hmac.ComputeHash(block); + for (int j = 0; j < finalHash.Length; j++) + { + finalHash[j] ^= block[j]; + } + } + + res.Add((byte[])finalHash.Clone()); + } + + return res; + } + } + + public ICryptoTransform Transformer(byte[] salt) + { + int iterations = (1 << _cryptoInfo.LG2Count); // Adjust the number of iterations as needed + + var salt_rar5 = salt.Concat(new byte[] { 0, 0, 0, 1 }); + var derivedKey = GenerateRarPBKDF2Key( + _password, + salt_rar5.ToArray(), + iterations, + DERIVED_KEY_LENGTH + ); + + _hashKey = derivedKey[1]; + + _pswCheck = new byte[EncryptionConstV5.SIZE_PSWCHECK]; + + for (int i = 0; i < SHA256_DIGEST_SIZE; i++) + { + _pswCheck[i % EncryptionConstV5.SIZE_PSWCHECK] ^= derivedKey[2][i]; + } + + if (_cryptoInfo.UsePswCheck && !_cryptoInfo.PswCheck.SequenceEqual(_pswCheck)) + { + throw new CryptographicException("The password did not match."); + } + + var aes = Aes.Create(); + aes.KeySize = AES_256; + aes.Mode = CipherMode.CBC; + aes.Padding = PaddingMode.None; + aes.Key = derivedKey[0]; + aes.IV = _cryptoInfo.InitV; + return aes.CreateDecryptor(); + } +} diff --git a/src/SharpCompress/Common/Rar/Headers/ArchiveCryptHeader.cs b/src/SharpCompress/Common/Rar/Headers/ArchiveCryptHeader.cs index 619e8fc9..a907002d 100644 --- a/src/SharpCompress/Common/Rar/Headers/ArchiveCryptHeader.cs +++ b/src/SharpCompress/Common/Rar/Headers/ArchiveCryptHeader.cs @@ -1,50 +1,22 @@ #nullable disable +using System; +using System.Security.Cryptography; + using SharpCompress.IO; +using SharpCompress.Common.Rar.Headers; namespace SharpCompress.Common.Rar.Headers; internal class ArchiveCryptHeader : RarHeader { - private const int CRYPT_VERSION = 0; // Supported encryption version. - private const int SIZE_SALT50 = 16; - private const int SIZE_PSWCHECK = 8; - private const int SIZE_PSWCHECK_CSUM = 4; - private const int CRYPT5_KDF_LG2_COUNT_MAX = 24; // LOG2 of maximum accepted iteration count. - - private bool _usePswCheck; - private uint _lg2Count; // Log2 of PBKDF2 repetition count. - private byte[] _salt; - private byte[] _pswCheck; - private byte[] _pswCheckCsm; - public ArchiveCryptHeader(RarHeader header, RarCrcBinaryReader reader) : base(header, reader, HeaderType.Crypt) { } + public Rar5CryptoInfo CryptInfo = new Rar5CryptoInfo(); + protected override void ReadFinish(MarkingBinaryReader reader) { - var cryptVersion = reader.ReadRarVIntUInt32(); - if (cryptVersion > CRYPT_VERSION) - { - //error? - return; - } - var encryptionFlags = reader.ReadRarVIntUInt32(); - _usePswCheck = FlagUtility.HasFlag(encryptionFlags, EncryptionFlagsV5.CHFL_CRYPT_PSWCHECK); - _lg2Count = reader.ReadRarVIntByte(1); - - //UsePswCheck = HasHeaderFlag(EncryptionFlagsV5.CHFL_CRYPT_PSWCHECK); - if (_lg2Count > CRYPT5_KDF_LG2_COUNT_MAX) - { - //error? - return; - } - - _salt = reader.ReadBytes(SIZE_SALT50); - if (_usePswCheck) - { - _pswCheck = reader.ReadBytes(SIZE_PSWCHECK); - _pswCheckCsm = reader.ReadBytes(SIZE_PSWCHECK_CSUM); - } + CryptInfo = new Rar5CryptoInfo(reader,false); } } diff --git a/src/SharpCompress/Common/Rar/Headers/FileHeader.cs b/src/SharpCompress/Common/Rar/Headers/FileHeader.cs index 0663baf9..3775f036 100644 --- a/src/SharpCompress/Common/Rar/Headers/FileHeader.cs +++ b/src/SharpCompress/Common/Rar/Headers/FileHeader.cs @@ -2,6 +2,8 @@ using System; using System.IO; +using System.Linq; +using System.Security.Cryptography; using System.Text; using SharpCompress.IO; #if !Rar2017_64bit @@ -103,7 +105,13 @@ private void ReadFromReaderV5(MarkingBinaryReader reader) throw new InvalidFormatException("rar5 header size / extra size inconsistency"); } - isEncryptedRar5 = false; + const ushort FHEXTRA_CRYPT = 0x01; + const ushort FHEXTRA_HASH = 0x02; + const ushort FHEXTRA_HTIME = 0x03; + // const ushort FHEXTRA_VERSION = 0x04; + // const ushort FHEXTRA_REDIR = 0x05; + // const ushort FHEXTRA_UOWNER = 0x06; + // const ushort FHEXTRA_SUBDATA = 0x07; while (RemainingHeaderBytes(reader) > 0) { @@ -113,21 +121,34 @@ private void ReadFromReaderV5(MarkingBinaryReader reader) switch (type) { //TODO - case 1: // file encryption + case FHEXTRA_CRYPT: // file encryption { - isEncryptedRar5 = true; + Rar5CryptoInfo = new Rar5CryptoInfo(reader, true); - //var version = reader.ReadRarVIntByte(); - //if (version != 0) throw new InvalidFormatException("unknown encryption algorithm " + version); + if (Rar5CryptoInfo.PswCheck.All(singleByte => singleByte == 0)) + { + Rar5CryptoInfo = null; + } } break; - // case 2: // file hash - // { - // - // } - // break; - case 3: // file time + case FHEXTRA_HASH: + + { + const uint FHEXTRA_HASH_BLAKE2 = 0x0; + const uint HASH_BLAKE2 = 0x03; + const int BLAKE2_DIGEST_SIZE = 0x20; + if ((uint)reader.ReadRarVInt() == FHEXTRA_HASH_BLAKE2) + { + var hash = HASH_BLAKE2; + var digest = reader.ReadBytes(BLAKE2_DIGEST_SIZE); + + throw new InvalidFormatException("Not yet implemented " + hash); + } + // enum HASH_TYPE {HASH_NONE,HASH_RAR14,HASH_CRC32,HASH_BLAKE2}; + } + break; + case FHEXTRA_HTIME: // file time { var flags = reader.ReadRarVIntUInt16(); @@ -147,22 +168,22 @@ private void ReadFromReaderV5(MarkingBinaryReader reader) } break; //TODO - // case 4: // file version + // case FHEXTRA_VERSION: // file version // { // // } // break; - // case 5: // file system redirection + // case FHEXTRA_REDIR: // file system redirection // { // // } // break; - // case 6: // unix owner + // case FHEXTRA_UOWNER: // unix owner // { // // } // break; - // case 7: // service data + // case FHEXTRA_SUBDATA: // service data // { // // } @@ -254,7 +275,6 @@ private void ReadFromReaderV4(MarkingBinaryReader reader) var fileNameBytes = reader.ReadBytes(nameSize); - const int saltSize = 8; const int newLhdSize = 32; switch (HeaderCode) @@ -292,7 +312,7 @@ private void ReadFromReaderV4(MarkingBinaryReader reader) var datasize = HeaderSize - newLhdSize - nameSize; if (HasFlag(FileFlagsV4.SALT)) { - datasize -= saltSize; + datasize -= EncryptionConstV5.SIZE_SALT30; } if (datasize > 0) { @@ -313,7 +333,7 @@ private void ReadFromReaderV4(MarkingBinaryReader reader) if (HasFlag(FileFlagsV4.SALT)) { - R4Salt = reader.ReadBytes(saltSize); + R4Salt = reader.ReadBytes(EncryptionConstV5.SIZE_SALT30); } if (HasFlag(FileFlagsV4.EXT_TIME)) { @@ -431,7 +451,7 @@ internal uint FileCrc internal size_t WindowSize { get; private set; } internal byte[] R4Salt { get; private set; } - + internal Rar5CryptoInfo Rar5CryptoInfo { get; private set; } private byte HostOs { get; set; } internal uint FileAttributes { get; private set; } internal long CompressedSize { get; private set; } @@ -449,8 +469,7 @@ internal uint FileCrc public bool IsDirectory => HasFlag(IsRar5 ? FileFlagsV5.DIRECTORY : FileFlagsV4.DIRECTORY); - private bool isEncryptedRar5 = false; - public bool IsEncrypted => IsRar5 ? isEncryptedRar5 : HasFlag(FileFlagsV4.PASSWORD); + public bool IsEncrypted => IsRar5 ? Rar5CryptoInfo != null : HasFlag(FileFlagsV4.PASSWORD); internal DateTime? FileLastModifiedTime { get; private set; } diff --git a/src/SharpCompress/Common/Rar/Headers/Flags.cs b/src/SharpCompress/Common/Rar/Headers/Flags.cs index 5a52f001..3414d1a3 100644 --- a/src/SharpCompress/Common/Rar/Headers/Flags.cs +++ b/src/SharpCompress/Common/Rar/Headers/Flags.cs @@ -50,6 +50,17 @@ internal static class EncryptionFlagsV5 public const uint FHEXTRA_CRYPT_HASHMAC = 0x02; } +internal static class EncryptionConstV5 +{ + public const int VERSION = 0; + public const uint CRYPT5_KDF_LG2_COUNT_MAX = 0x24; + public const int SIZE_SALT30 = 0x08; + public const int SIZE_SALT50 = 0x10; + public const int SIZE_INITV = 0x10; + public const int SIZE_PSWCHECK = 0x08; + public const int SIZE_PSWCHECK_CSUM = 0x04; +} + internal static class HeaderFlagsV5 { public const ushort HAS_EXTRA = 0x0001; diff --git a/src/SharpCompress/Common/Rar/Headers/RarHeader.cs b/src/SharpCompress/Common/Rar/Headers/RarHeader.cs index 81002fc4..0d8648e8 100644 --- a/src/SharpCompress/Common/Rar/Headers/RarHeader.cs +++ b/src/SharpCompress/Common/Rar/Headers/RarHeader.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using SharpCompress.IO; diff --git a/src/SharpCompress/Common/Rar/Headers/RarHeaderFactory.cs b/src/SharpCompress/Common/Rar/Headers/RarHeaderFactory.cs index de44e309..e38cb629 100644 --- a/src/SharpCompress/Common/Rar/Headers/RarHeaderFactory.cs +++ b/src/SharpCompress/Common/Rar/Headers/RarHeaderFactory.cs @@ -1,5 +1,7 @@ using System.Collections.Generic; using System.IO; +using System.Linq; +using SharpCompress.Common.Rar; using SharpCompress.IO; using SharpCompress.Readers; @@ -9,6 +11,8 @@ public class RarHeaderFactory { private bool _isRar5; + private Rar5CryptoInfo? _cryptInfo; + public RarHeaderFactory(StreamingMode mode, ReaderOptions options) { StreamingMode = mode; @@ -53,7 +57,19 @@ public IEnumerable ReadHeaders(Stream stream) "Encrypted Rar archive has no password specified." ); } - reader = new RarCryptoBinaryReader(stream, Options.Password); + + if (_isRar5 && _cryptInfo != null) + { + _cryptInfo.ReadInitV(new MarkingBinaryReader(stream)); + var _headerKey = new CryptKey5(Options.Password!, _cryptInfo); + + reader = new RarCryptoBinaryReader(stream, _headerKey, _cryptInfo.Salt); + } + else + { + var key = new CryptKey3(Options.Password); + reader = new RarCryptoBinaryReader(stream, key); + } } var header = RarHeader.TryReadBase(reader, _isRar5, Options.ArchiveEncoding); @@ -140,7 +156,7 @@ public IEnumerable ReadHeaders(Stream stream) { var ms = new ReadOnlySubStream(reader.BaseStream, fh.CompressedSize); - if (fh.R4Salt is null) + if (fh.R4Salt is null && fh.Rar5CryptoInfo is null) { fh.PackedStream = ms; } @@ -148,8 +164,10 @@ public IEnumerable ReadHeaders(Stream stream) { fh.PackedStream = new RarCryptoWrapper( ms, - Options.Password!, - fh.R4Salt + fh.R4Salt is null ? fh.Rar5CryptoInfo.Salt : fh.R4Salt, + fh.R4Salt is null + ? new CryptKey5(Options.Password!, fh.Rar5CryptoInfo) + : new CryptKey3(Options.Password!) ); } } @@ -168,9 +186,11 @@ public IEnumerable ReadHeaders(Stream stream) } case HeaderCodeV.RAR5_ARCHIVE_ENCRYPTION_HEADER: { - var ch = new ArchiveCryptHeader(header, reader); + var cryptoHeader = new ArchiveCryptHeader(header, reader); IsEncrypted = true; - return ch; + _cryptInfo = cryptoHeader.CryptInfo; + + return cryptoHeader; } default: { diff --git a/src/SharpCompress/Common/Rar/ICryptKey.cs b/src/SharpCompress/Common/Rar/ICryptKey.cs new file mode 100644 index 00000000..b4188a17 --- /dev/null +++ b/src/SharpCompress/Common/Rar/ICryptKey.cs @@ -0,0 +1,12 @@ +using System.IO; +using System.Collections.Generic; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; + +namespace SharpCompress.Common.Rar; +internal interface ICryptKey +{ + ICryptoTransform Transformer(byte[] salt); +} diff --git a/src/SharpCompress/Common/Rar/Rar5CryptoInfo.cs b/src/SharpCompress/Common/Rar/Rar5CryptoInfo.cs new file mode 100644 index 00000000..28053a3d --- /dev/null +++ b/src/SharpCompress/Common/Rar/Rar5CryptoInfo.cs @@ -0,0 +1,59 @@ +using System; +using System.Security.Cryptography; +using SharpCompress.Common.Rar.Headers; +using SharpCompress.IO; + +namespace SharpCompress.Common.Rar; + +internal class Rar5CryptoInfo +{ + public Rar5CryptoInfo() { } + + public Rar5CryptoInfo(MarkingBinaryReader reader, bool readInitV) + { + var cryptVersion = reader.ReadRarVIntUInt32(); + if (cryptVersion > EncryptionConstV5.VERSION) + { + throw new CryptographicException($"Unsupported crypto version of {cryptVersion}"); + } + var encryptionFlags = reader.ReadRarVIntUInt32(); + UsePswCheck = FlagUtility.HasFlag(encryptionFlags, EncryptionFlagsV5.CHFL_CRYPT_PSWCHECK); + LG2Count = reader.ReadRarVIntByte(1); + + if (LG2Count > EncryptionConstV5.CRYPT5_KDF_LG2_COUNT_MAX) + { + throw new CryptographicException($"Unsupported LG2 count of {LG2Count}."); + } + + Salt = reader.ReadBytes(EncryptionConstV5.SIZE_SALT50); + + if (readInitV) // File header needs to read IV here + { + ReadInitV(reader); + } + + if (UsePswCheck) + { + PswCheck = reader.ReadBytes(EncryptionConstV5.SIZE_PSWCHECK); + var _pswCheckCsm = reader.ReadBytes(EncryptionConstV5.SIZE_PSWCHECK_CSUM); + + var sha = SHA256.Create(); + UsePswCheck = sha.ComputeHash(PswCheck).AsSpan().StartsWith(_pswCheckCsm.AsSpan()); + } + } + + public void ReadInitV(MarkingBinaryReader reader) + { + InitV = reader.ReadBytes(EncryptionConstV5.SIZE_INITV); + } + + public bool UsePswCheck = false; + + public int LG2Count = 0; + + public byte[] InitV = { }; + + public byte[] Salt = { }; + + public byte[] PswCheck = { }; +} diff --git a/src/SharpCompress/Common/Rar/RarCryptoBinaryReader.cs b/src/SharpCompress/Common/Rar/RarCryptoBinaryReader.cs index 0be6e74e..982821c3 100644 --- a/src/SharpCompress/Common/Rar/RarCryptoBinaryReader.cs +++ b/src/SharpCompress/Common/Rar/RarCryptoBinaryReader.cs @@ -1,27 +1,30 @@ -using System.Collections.Generic; +#nullable disable + +using System.Collections.Generic; using System.IO; +using SharpCompress.Common.Rar.Headers; +using SharpCompress.Crypto; namespace SharpCompress.Common.Rar; internal sealed class RarCryptoBinaryReader : RarCrcBinaryReader { - private RarRijndael _rijndael; - private byte[] _salt; - private readonly string _password; + private BlockTransformer _rijndael; private readonly Queue _data = new Queue(); private long _readCount; - public RarCryptoBinaryReader(Stream stream, string password) + public RarCryptoBinaryReader(Stream stream, ICryptKey cryptKey) : base(stream) { - _password = password; - - // coderb: not sure why this was being done at this logical point - //SkipQueue(); - var salt = ReadBytes(8); + var salt = base.ReadBytes(EncryptionConstV5.SIZE_SALT30); + _readCount += EncryptionConstV5.SIZE_SALT30; + _rijndael = new BlockTransformer(cryptKey.Transformer(salt)); + } - _salt = salt; - _rijndael = RarRijndael.InitializeFrom(_password, salt); + public RarCryptoBinaryReader(Stream stream, ICryptKey cryptKey, byte[] salt) + : base(stream) + { + _rijndael = new BlockTransformer(cryptKey.Transformer(salt)); } // track read count ourselves rather than using the underlying stream since we buffer @@ -36,28 +39,14 @@ protected set public override void Mark() => _readCount = 0; - private bool UseEncryption => _salt != null; - public override byte ReadByte() { - if (UseEncryption) - { - return ReadAndDecryptBytes(1)[0]; - } - - _readCount++; - return base.ReadByte(); + return ReadAndDecryptBytes(1)[0]; } public override byte[] ReadBytes(int count) { - if (UseEncryption) - { - return ReadAndDecryptBytes(count); - } - - _readCount += count; - return base.ReadBytes(count); + return ReadAndDecryptBytes(count); } private byte[] ReadAndDecryptBytes(int count) diff --git a/src/SharpCompress/Common/Rar/RarCryptoWrapper.cs b/src/SharpCompress/Common/Rar/RarCryptoWrapper.cs index a1cee046..1907108c 100644 --- a/src/SharpCompress/Common/Rar/RarCryptoWrapper.cs +++ b/src/SharpCompress/Common/Rar/RarCryptoWrapper.cs @@ -2,20 +2,20 @@ using System.Collections.Generic; using System.IO; +using SharpCompress.Crypto; + namespace SharpCompress.Common.Rar; internal sealed class RarCryptoWrapper : Stream { private readonly Stream _actualStream; - private readonly byte[] _salt; - private RarRijndael _rijndael; + private BlockTransformer _rijndael; private readonly Queue _data = new Queue(); - public RarCryptoWrapper(Stream actualStream, string password, byte[] salt) + public RarCryptoWrapper(Stream actualStream, byte[] salt, ICryptKey key) { _actualStream = actualStream; - _salt = salt; - _rijndael = RarRijndael.InitializeFrom(password ?? "", salt); + _rijndael = new BlockTransformer(key.Transformer(salt)); } public override void Flush() => throw new NotSupportedException(); @@ -26,10 +26,6 @@ public RarCryptoWrapper(Stream actualStream, string password, byte[] salt) public override int Read(byte[] buffer, int offset, int count) { - if (_salt is null) - { - return _actualStream.Read(buffer, offset, count); - } return ReadAndDecrypt(buffer, offset, count); } @@ -41,7 +37,7 @@ public int ReadAndDecrypt(byte[] buffer, int offset, int count) if (sizeToRead > 0) { var alignedSize = sizeToRead + ((~sizeToRead + 1) & 0xf); - Span cipherText = stackalloc byte[RarRijndael.CRYPTO_BLOCK_SIZE]; + Span cipherText = stackalloc byte[16]; for (var i = 0; i < alignedSize / 16; i++) { //long ax = System.currentTimeMillis(); diff --git a/src/SharpCompress/Common/Rar/RarRijndael.cs b/src/SharpCompress/Common/Rar/RarRijndael.cs deleted file mode 100644 index 94592639..00000000 --- a/src/SharpCompress/Common/Rar/RarRijndael.cs +++ /dev/null @@ -1,114 +0,0 @@ -#nullable disable - -using System; -using System.Security.Cryptography; -using System.Text; -using SharpCompress.Crypto; - -namespace SharpCompress.Common.Rar; - -internal class RarRijndael : IDisposable -{ - internal const int CRYPTO_BLOCK_SIZE = 16; - - private readonly string _password; - private readonly byte[] _salt; - private byte[] _aesInitializationVector; - private RijndaelEngine _rijndael; - - private RarRijndael(string password, byte[] salt) - { - _password = password; - _salt = salt; - } - - private void Initialize() - { - _rijndael = new RijndaelEngine(); - _aesInitializationVector = new byte[CRYPTO_BLOCK_SIZE]; - var rawLength = 2 * _password.Length; - var rawPassword = new byte[rawLength + 8]; - var passwordBytes = Encoding.UTF8.GetBytes(_password); - for (var i = 0; i < _password.Length; i++) - { - rawPassword[i * 2] = passwordBytes[i]; - rawPassword[(i * 2) + 1] = 0; - } - for (var i = 0; i < _salt.Length; i++) - { - rawPassword[i + rawLength] = _salt[i]; - } - - const int noOfRounds = (1 << 18); - const int iblock = 3; - byte[] digest; - var data = new byte[(rawPassword.Length + iblock) * noOfRounds]; - - //TODO slow code below, find ways to optimize - for (var i = 0; i < noOfRounds; i++) - { - rawPassword.CopyTo(data, i * (rawPassword.Length + iblock)); - - data[(i * (rawPassword.Length + iblock)) + rawPassword.Length + 0] = (byte)i; - data[(i * (rawPassword.Length + iblock)) + rawPassword.Length + 1] = (byte)(i >> 8); - data[(i * (rawPassword.Length + iblock)) + rawPassword.Length + 2] = (byte)( - i >> CRYPTO_BLOCK_SIZE - ); - - if (i % (noOfRounds / CRYPTO_BLOCK_SIZE) == 0) - { - digest = SHA1.Create() - .ComputeHash(data, 0, (i + 1) * (rawPassword.Length + iblock)); - _aesInitializationVector[i / (noOfRounds / CRYPTO_BLOCK_SIZE)] = digest[19]; - } - } - digest = SHA1.Create().ComputeHash(data); - //slow code ends - - var aesKey = new byte[CRYPTO_BLOCK_SIZE]; - for (var i = 0; i < 4; i++) - { - for (var j = 0; j < 4; j++) - { - aesKey[(i * 4) + j] = (byte)( - ( - ((digest[i * 4] * 0x1000000) & 0xff000000) - | (uint)((digest[(i * 4) + 1] * 0x10000) & 0xff0000) - | (uint)((digest[(i * 4) + 2] * 0x100) & 0xff00) - | (uint)(digest[(i * 4) + 3] & 0xff) - ) >> (j * 8) - ); - } - } - - _rijndael.Init(false, new KeyParameter(aesKey)); - } - - public static RarRijndael InitializeFrom(string password, byte[] salt) - { - var rijndael = new RarRijndael(password, salt); - rijndael.Initialize(); - return rijndael; - } - - public byte[] ProcessBlock(ReadOnlySpan cipherText) - { - Span plainText = stackalloc byte[CRYPTO_BLOCK_SIZE]; // 16 bytes - var decryptedBytes = new byte[CRYPTO_BLOCK_SIZE]; - _rijndael.ProcessBlock(cipherText, plainText); - - for (var j = 0; j < CRYPTO_BLOCK_SIZE; j++) - { - decryptedBytes[j] = (byte)(plainText[j] ^ _aesInitializationVector[j % 16]); //32:114, 33:101 - } - - for (var j = 0; j < _aesInitializationVector.Length; j++) - { - _aesInitializationVector[j] = cipherText[j]; - } - - return decryptedBytes; - } - - public void Dispose() { } -} diff --git a/src/SharpCompress/Crypto/BlockTransformer.cs b/src/SharpCompress/Crypto/BlockTransformer.cs new file mode 100644 index 00000000..bcb937d0 --- /dev/null +++ b/src/SharpCompress/Crypto/BlockTransformer.cs @@ -0,0 +1,32 @@ +#nullable disable + +using System; +using System.Security.Cryptography; + +namespace SharpCompress.Crypto; + +internal class BlockTransformer : IDisposable +{ + private ICryptoTransform _transform; + + public BlockTransformer(ICryptoTransform transformer) + { + _transform = transformer; + } + + public byte[] ProcessBlock(ReadOnlySpan cipherText) + { + var decryptedBytes = new byte[cipherText.Length]; + var bytes = _transform.TransformBlock( + cipherText.ToArray(), + 0, + cipherText.Length, + decryptedBytes, + 0 + ); + + return decryptedBytes; + } + + public void Dispose() { } +} diff --git a/src/SharpCompress/Crypto/RijndaelEngine.cs b/src/SharpCompress/Crypto/RijndaelEngine.cs deleted file mode 100644 index 98dd5bc1..00000000 --- a/src/SharpCompress/Crypto/RijndaelEngine.cs +++ /dev/null @@ -1,1909 +0,0 @@ -using System; - -namespace SharpCompress.Crypto; - -public sealed class RijndaelEngine : IBlockCipher -{ - private const int MAXROUNDS = 14; - - private const int MAXKC = (256 / 4); - - private static ReadOnlySpan Logtable => - new byte[] - { - 0, - 0, - 25, - 1, - 50, - 2, - 26, - 198, - 75, - 199, - 27, - 104, - 51, - 238, - 223, - 3, - 100, - 4, - 224, - 14, - 52, - 141, - 129, - 239, - 76, - 113, - 8, - 200, - 248, - 105, - 28, - 193, - 125, - 194, - 29, - 181, - 249, - 185, - 39, - 106, - 77, - 228, - 166, - 114, - 154, - 201, - 9, - 120, - 101, - 47, - 138, - 5, - 33, - 15, - 225, - 36, - 18, - 240, - 130, - 69, - 53, - 147, - 218, - 142, - 150, - 143, - 219, - 189, - 54, - 208, - 206, - 148, - 19, - 92, - 210, - 241, - 64, - 70, - 131, - 56, - 102, - 221, - 253, - 48, - 191, - 6, - 139, - 98, - 179, - 37, - 226, - 152, - 34, - 136, - 145, - 16, - 126, - 110, - 72, - 195, - 163, - 182, - 30, - 66, - 58, - 107, - 40, - 84, - 250, - 133, - 61, - 186, - 43, - 121, - 10, - 21, - 155, - 159, - 94, - 202, - 78, - 212, - 172, - 229, - 243, - 115, - 167, - 87, - 175, - 88, - 168, - 80, - 244, - 234, - 214, - 116, - 79, - 174, - 233, - 213, - 231, - 230, - 173, - 232, - 44, - 215, - 117, - 122, - 235, - 22, - 11, - 245, - 89, - 203, - 95, - 176, - 156, - 169, - 81, - 160, - 127, - 12, - 246, - 111, - 23, - 196, - 73, - 236, - 216, - 67, - 31, - 45, - 164, - 118, - 123, - 183, - 204, - 187, - 62, - 90, - 251, - 96, - 177, - 134, - 59, - 82, - 161, - 108, - 170, - 85, - 41, - 157, - 151, - 178, - 135, - 144, - 97, - 190, - 220, - 252, - 188, - 149, - 207, - 205, - 55, - 63, - 91, - 209, - 83, - 57, - 132, - 60, - 65, - 162, - 109, - 71, - 20, - 42, - 158, - 93, - 86, - 242, - 211, - 171, - 68, - 17, - 146, - 217, - 35, - 32, - 46, - 137, - 180, - 124, - 184, - 38, - 119, - 153, - 227, - 165, - 103, - 74, - 237, - 222, - 197, - 49, - 254, - 24, - 13, - 99, - 140, - 128, - 192, - 247, - 112, - 7 - }; - - private static ReadOnlySpan Alogtable => - new byte[] - { - 0, - 3, - 5, - 15, - 17, - 51, - 85, - 255, - 26, - 46, - 114, - 150, - 161, - 248, - 19, - 53, - 95, - 225, - 56, - 72, - 216, - 115, - 149, - 164, - 247, - 2, - 6, - 10, - 30, - 34, - 102, - 170, - 229, - 52, - 92, - 228, - 55, - 89, - 235, - 38, - 106, - 190, - 217, - 112, - 144, - 171, - 230, - 49, - 83, - 245, - 4, - 12, - 20, - 60, - 68, - 204, - 79, - 209, - 104, - 184, - 211, - 110, - 178, - 205, - 76, - 212, - 103, - 169, - 224, - 59, - 77, - 215, - 98, - 166, - 241, - 8, - 24, - 40, - 120, - 136, - 131, - 158, - 185, - 208, - 107, - 189, - 220, - 127, - 129, - 152, - 179, - 206, - 73, - 219, - 118, - 154, - 181, - 196, - 87, - 249, - 16, - 48, - 80, - 240, - 11, - 29, - 39, - 105, - 187, - 214, - 97, - 163, - 254, - 25, - 43, - 125, - 135, - 146, - 173, - 236, - 47, - 113, - 147, - 174, - 233, - 32, - 96, - 160, - 251, - 22, - 58, - 78, - 210, - 109, - 183, - 194, - 93, - 231, - 50, - 86, - 250, - 21, - 63, - 65, - 195, - 94, - 226, - 61, - 71, - 201, - 64, - 192, - 91, - 237, - 44, - 116, - 156, - 191, - 218, - 117, - 159, - 186, - 213, - 100, - 172, - 239, - 42, - 126, - 130, - 157, - 188, - 223, - 122, - 142, - 137, - 128, - 155, - 182, - 193, - 88, - 232, - 35, - 101, - 175, - 234, - 37, - 111, - 177, - 200, - 67, - 197, - 84, - 252, - 31, - 33, - 99, - 165, - 244, - 7, - 9, - 27, - 45, - 119, - 153, - 176, - 203, - 70, - 202, - 69, - 207, - 74, - 222, - 121, - 139, - 134, - 145, - 168, - 227, - 62, - 66, - 198, - 81, - 243, - 14, - 18, - 54, - 90, - 238, - 41, - 123, - 141, - 140, - 143, - 138, - 133, - 148, - 167, - 242, - 13, - 23, - 57, - 75, - 221, - 124, - 132, - 151, - 162, - 253, - 28, - 36, - 108, - 180, - 199, - 82, - 246, - 1, - 3, - 5, - 15, - 17, - 51, - 85, - 255, - 26, - 46, - 114, - 150, - 161, - 248, - 19, - 53, - 95, - 225, - 56, - 72, - 216, - 115, - 149, - 164, - 247, - 2, - 6, - 10, - 30, - 34, - 102, - 170, - 229, - 52, - 92, - 228, - 55, - 89, - 235, - 38, - 106, - 190, - 217, - 112, - 144, - 171, - 230, - 49, - 83, - 245, - 4, - 12, - 20, - 60, - 68, - 204, - 79, - 209, - 104, - 184, - 211, - 110, - 178, - 205, - 76, - 212, - 103, - 169, - 224, - 59, - 77, - 215, - 98, - 166, - 241, - 8, - 24, - 40, - 120, - 136, - 131, - 158, - 185, - 208, - 107, - 189, - 220, - 127, - 129, - 152, - 179, - 206, - 73, - 219, - 118, - 154, - 181, - 196, - 87, - 249, - 16, - 48, - 80, - 240, - 11, - 29, - 39, - 105, - 187, - 214, - 97, - 163, - 254, - 25, - 43, - 125, - 135, - 146, - 173, - 236, - 47, - 113, - 147, - 174, - 233, - 32, - 96, - 160, - 251, - 22, - 58, - 78, - 210, - 109, - 183, - 194, - 93, - 231, - 50, - 86, - 250, - 21, - 63, - 65, - 195, - 94, - 226, - 61, - 71, - 201, - 64, - 192, - 91, - 237, - 44, - 116, - 156, - 191, - 218, - 117, - 159, - 186, - 213, - 100, - 172, - 239, - 42, - 126, - 130, - 157, - 188, - 223, - 122, - 142, - 137, - 128, - 155, - 182, - 193, - 88, - 232, - 35, - 101, - 175, - 234, - 37, - 111, - 177, - 200, - 67, - 197, - 84, - 252, - 31, - 33, - 99, - 165, - 244, - 7, - 9, - 27, - 45, - 119, - 153, - 176, - 203, - 70, - 202, - 69, - 207, - 74, - 222, - 121, - 139, - 134, - 145, - 168, - 227, - 62, - 66, - 198, - 81, - 243, - 14, - 18, - 54, - 90, - 238, - 41, - 123, - 141, - 140, - 143, - 138, - 133, - 148, - 167, - 242, - 13, - 23, - 57, - 75, - 221, - 124, - 132, - 151, - 162, - 253, - 28, - 36, - 108, - 180, - 199, - 82, - 246, - 1 - }; - - private static ReadOnlySpan S => - new byte[] - { - 99, - 124, - 119, - 123, - 242, - 107, - 111, - 197, - 48, - 1, - 103, - 43, - 254, - 215, - 171, - 118, - 202, - 130, - 201, - 125, - 250, - 89, - 71, - 240, - 173, - 212, - 162, - 175, - 156, - 164, - 114, - 192, - 183, - 253, - 147, - 38, - 54, - 63, - 247, - 204, - 52, - 165, - 229, - 241, - 113, - 216, - 49, - 21, - 4, - 199, - 35, - 195, - 24, - 150, - 5, - 154, - 7, - 18, - 128, - 226, - 235, - 39, - 178, - 117, - 9, - 131, - 44, - 26, - 27, - 110, - 90, - 160, - 82, - 59, - 214, - 179, - 41, - 227, - 47, - 132, - 83, - 209, - 0, - 237, - 32, - 252, - 177, - 91, - 106, - 203, - 190, - 57, - 74, - 76, - 88, - 207, - 208, - 239, - 170, - 251, - 67, - 77, - 51, - 133, - 69, - 249, - 2, - 127, - 80, - 60, - 159, - 168, - 81, - 163, - 64, - 143, - 146, - 157, - 56, - 245, - 188, - 182, - 218, - 33, - 16, - 255, - 243, - 210, - 205, - 12, - 19, - 236, - 95, - 151, - 68, - 23, - 196, - 167, - 126, - 61, - 100, - 93, - 25, - 115, - 96, - 129, - 79, - 220, - 34, - 42, - 144, - 136, - 70, - 238, - 184, - 20, - 222, - 94, - 11, - 219, - 224, - 50, - 58, - 10, - 73, - 6, - 36, - 92, - 194, - 211, - 172, - 98, - 145, - 149, - 228, - 121, - 231, - 200, - 55, - 109, - 141, - 213, - 78, - 169, - 108, - 86, - 244, - 234, - 101, - 122, - 174, - 8, - 186, - 120, - 37, - 46, - 28, - 166, - 180, - 198, - 232, - 221, - 116, - 31, - 75, - 189, - 139, - 138, - 112, - 62, - 181, - 102, - 72, - 3, - 246, - 14, - 97, - 53, - 87, - 185, - 134, - 193, - 29, - 158, - 225, - 248, - 152, - 17, - 105, - 217, - 142, - 148, - 155, - 30, - 135, - 233, - 206, - 85, - 40, - 223, - 140, - 161, - 137, - 13, - 191, - 230, - 66, - 104, - 65, - 153, - 45, - 15, - 176, - 84, - 187, - 22 - }; - - private static ReadOnlySpan Si => - new byte[] - { - 82, - 9, - 106, - 213, - 48, - 54, - 165, - 56, - 191, - 64, - 163, - 158, - 129, - 243, - 215, - 251, - 124, - 227, - 57, - 130, - 155, - 47, - 255, - 135, - 52, - 142, - 67, - 68, - 196, - 222, - 233, - 203, - 84, - 123, - 148, - 50, - 166, - 194, - 35, - 61, - 238, - 76, - 149, - 11, - 66, - 250, - 195, - 78, - 8, - 46, - 161, - 102, - 40, - 217, - 36, - 178, - 118, - 91, - 162, - 73, - 109, - 139, - 209, - 37, - 114, - 248, - 246, - 100, - 134, - 104, - 152, - 22, - 212, - 164, - 92, - 204, - 93, - 101, - 182, - 146, - 108, - 112, - 72, - 80, - 253, - 237, - 185, - 218, - 94, - 21, - 70, - 87, - 167, - 141, - 157, - 132, - 144, - 216, - 171, - 0, - 140, - 188, - 211, - 10, - 247, - 228, - 88, - 5, - 184, - 179, - 69, - 6, - 208, - 44, - 30, - 143, - 202, - 63, - 15, - 2, - 193, - 175, - 189, - 3, - 1, - 19, - 138, - 107, - 58, - 145, - 17, - 65, - 79, - 103, - 220, - 234, - 151, - 242, - 207, - 206, - 240, - 180, - 230, - 115, - 150, - 172, - 116, - 34, - 231, - 173, - 53, - 133, - 226, - 249, - 55, - 232, - 28, - 117, - 223, - 110, - 71, - 241, - 26, - 113, - 29, - 41, - 197, - 137, - 111, - 183, - 98, - 14, - 170, - 24, - 190, - 27, - 252, - 86, - 62, - 75, - 198, - 210, - 121, - 32, - 154, - 219, - 192, - 254, - 120, - 205, - 90, - 244, - 31, - 221, - 168, - 51, - 136, - 7, - 199, - 49, - 177, - 18, - 16, - 89, - 39, - 128, - 236, - 95, - 96, - 81, - 127, - 169, - 25, - 181, - 74, - 13, - 45, - 229, - 122, - 159, - 147, - 201, - 156, - 239, - 160, - 224, - 59, - 77, - 174, - 42, - 245, - 176, - 200, - 235, - 187, - 60, - 131, - 83, - 153, - 97, - 23, - 43, - 4, - 126, - 186, - 119, - 214, - 38, - 225, - 105, - 20, - 99, - 85, - 33, - 12, - 125 - }; - - private static ReadOnlySpan rcon => - new byte[] - { - 0x01, - 0x02, - 0x04, - 0x08, - 0x10, - 0x20, - 0x40, - 0x80, - 0x1b, - 0x36, - 0x6c, - 0xd8, - 0xab, - 0x4d, - 0x9a, - 0x2f, - 0x5e, - 0xbc, - 0x63, - 0xc6, - 0x97, - 0x35, - 0x6a, - 0xd4, - 0xb3, - 0x7d, - 0xfa, - 0xef, - 0xc5, - 0x91 - }; - - private static readonly byte[][] shifts0 = - { - new byte[] { 0, 8, 16, 24 }, - new byte[] { 0, 8, 16, 24 }, - new byte[] { 0, 8, 16, 24 }, - new byte[] { 0, 8, 16, 32 }, - new byte[] { 0, 8, 24, 32 } - }; - - private static readonly byte[][] shifts1 = - { - new byte[] { 0, 24, 16, 8 }, - new byte[] { 0, 32, 24, 16 }, - new byte[] { 0, 40, 32, 24 }, - new byte[] { 0, 48, 40, 24 }, - new byte[] { 0, 56, 40, 32 } - }; - - /** - * multiply two elements of GF(2^m) - * needed for MixColumn and InvMixColumn - */ - - private byte Mul0x2(int b) - { - if (b != 0) - { - return Alogtable[25 + (Logtable[b] & 0xff)]; - } - return 0; - } - - private byte Mul0x3(int b) - { - if (b != 0) - { - return Alogtable[1 + (Logtable[b] & 0xff)]; - } - return 0; - } - - private byte Mul0x9(int b) - { - if (b >= 0) - { - return Alogtable[199 + b]; - } - return 0; - } - - private byte Mul0xb(int b) - { - if (b >= 0) - { - return Alogtable[104 + b]; - } - return 0; - } - - private byte Mul0xd(int b) - { - if (b >= 0) - { - return Alogtable[238 + b]; - } - return 0; - } - - private byte Mul0xe(int b) - { - if (b >= 0) - { - return Alogtable[223 + b]; - } - return 0; - } - - /** - * xor corresponding text input and round key input bytes - */ - - private void KeyAddition(long[] rk) - { - A0 ^= rk[0]; - A1 ^= rk[1]; - A2 ^= rk[2]; - A3 ^= rk[3]; - } - - private long Shift(long r, int shift) - { - //return (((long)((ulong) r >> shift) | (r << (BC - shift)))) & BC_MASK; - - var temp = (ulong)r >> shift; - - // NB: This corrects for Mono Bug #79087 (fixed in 1.1.17) - if (shift > 31) - { - temp &= 0xFFFFFFFFUL; - } - - return ((long)temp | (r << (BC - shift))) & BC_MASK; - } - - /** - * Row 0 remains unchanged - * The other three rows are shifted a variable amount - */ - - private void ShiftRow(byte[] shiftsSC) - { - A1 = Shift(A1, shiftsSC[1]); - A2 = Shift(A2, shiftsSC[2]); - A3 = Shift(A3, shiftsSC[3]); - } - - private long ApplyS(long r, ReadOnlySpan box) - { - long res = 0; - - for (var j = 0; j < BC; j += 8) - { - res |= (long)(box[(int)((r >> j) & 0xff)] & 0xff) << j; - } - - return res; - } - - /** - * Replace every byte of the input by the byte at that place - * in the nonlinear S-box - */ - - private void Substitution(ReadOnlySpan box) - { - A0 = ApplyS(A0, box); - A1 = ApplyS(A1, box); - A2 = ApplyS(A2, box); - A3 = ApplyS(A3, box); - } - - /** - * Mix the bytes of every column in a linear way - */ - - private void MixColumn() - { - long r0, - r1, - r2, - r3; - - r0 = r1 = r2 = r3 = 0; - - for (var j = 0; j < BC; j += 8) - { - var a0 = (int)((A0 >> j) & 0xff); - var a1 = (int)((A1 >> j) & 0xff); - var a2 = (int)((A2 >> j) & 0xff); - var a3 = (int)((A3 >> j) & 0xff); - - r0 |= (long)((Mul0x2(a0) ^ Mul0x3(a1) ^ a2 ^ a3) & 0xff) << j; - - r1 |= (long)((Mul0x2(a1) ^ Mul0x3(a2) ^ a3 ^ a0) & 0xff) << j; - - r2 |= (long)((Mul0x2(a2) ^ Mul0x3(a3) ^ a0 ^ a1) & 0xff) << j; - - r3 |= (long)((Mul0x2(a3) ^ Mul0x3(a0) ^ a1 ^ a2) & 0xff) << j; - } - - A0 = r0; - A1 = r1; - A2 = r2; - A3 = r3; - } - - /** - * Mix the bytes of every column in a linear way - * This is the opposite operation of Mixcolumn - */ - - private void InvMixColumn() - { - long r0, - r1, - r2, - r3; - - r0 = r1 = r2 = r3 = 0; - for (var j = 0; j < BC; j += 8) - { - var a0 = (int)((A0 >> j) & 0xff); - var a1 = (int)((A1 >> j) & 0xff); - var a2 = (int)((A2 >> j) & 0xff); - var a3 = (int)((A3 >> j) & 0xff); - - // - // pre-lookup the log table - // - a0 = (a0 != 0) ? (Logtable[a0 & 0xff] & 0xff) : -1; - a1 = (a1 != 0) ? (Logtable[a1 & 0xff] & 0xff) : -1; - a2 = (a2 != 0) ? (Logtable[a2 & 0xff] & 0xff) : -1; - a3 = (a3 != 0) ? (Logtable[a3 & 0xff] & 0xff) : -1; - - r0 |= (long)((Mul0xe(a0) ^ Mul0xb(a1) ^ Mul0xd(a2) ^ Mul0x9(a3)) & 0xff) << j; - - r1 |= (long)((Mul0xe(a1) ^ Mul0xb(a2) ^ Mul0xd(a3) ^ Mul0x9(a0)) & 0xff) << j; - - r2 |= (long)((Mul0xe(a2) ^ Mul0xb(a3) ^ Mul0xd(a0) ^ Mul0x9(a1)) & 0xff) << j; - - r3 |= (long)((Mul0xe(a3) ^ Mul0xb(a0) ^ Mul0xd(a1) ^ Mul0x9(a2)) & 0xff) << j; - } - - A0 = r0; - A1 = r1; - A2 = r2; - A3 = r3; - } - - /** - * Calculate the necessary round keys - * The number of calculations depends on keyBits and blockBits - */ - - private long[][] GenerateWorkingKey(byte[] key) - { - int t, - rconpointer = 0; - var keyBits = key.Length * 8; - var tk = new byte[4, MAXKC]; - - //long[,] W = new long[MAXROUNDS+1,4]; - var W = new long[MAXROUNDS + 1][]; - - for (var i = 0; i < MAXROUNDS + 1; i++) - { - W[i] = new long[4]; - } - - var KC = keyBits switch - { - 128 => 4, - 160 => 5, - 192 => 6, - 224 => 7, - 256 => 8, - _ => throw new ArgumentException("Key length not 128/160/192/224/256 bits."), - }; - if (keyBits >= blockBits) - { - ROUNDS = KC + 6; - } - else - { - ROUNDS = (BC / 8) + 6; - } - - // - // copy the key into the processing area - // - var index = 0; - - for (var i = 0; i < key.Length; i++) - { - tk[i % 4, i / 4] = key[index++]; - } - - t = 0; - - // - // copy values into round key array - // - for (var j = 0; (j < KC) && (t < (ROUNDS + 1) * (BC / 8)); j++, t++) - { - for (var i = 0; i < 4; i++) - { - W[t / (BC / 8)][i] |= (long)(tk[i, j] & 0xff) << ((t * 8) % BC); - } - } - - // - // while not enough round key material calculated - // calculate new values - // - while (t < (ROUNDS + 1) * (BC / 8)) - { - for (var i = 0; i < 4; i++) - { - tk[i, 0] ^= S[tk[(i + 1) % 4, KC - 1] & 0xff]; - } - tk[0, 0] ^= rcon[rconpointer++]; - - if (KC <= 6) - { - for (var j = 1; j < KC; j++) - { - for (var i = 0; i < 4; i++) - { - tk[i, j] ^= tk[i, j - 1]; - } - } - } - else - { - for (var j = 1; j < 4; j++) - { - for (var i = 0; i < 4; i++) - { - tk[i, j] ^= tk[i, j - 1]; - } - } - for (var i = 0; i < 4; i++) - { - tk[i, 4] ^= S[tk[i, 3] & 0xff]; - } - for (var j = 5; j < KC; j++) - { - for (var i = 0; i < 4; i++) - { - tk[i, j] ^= tk[i, j - 1]; - } - } - } - - // - // copy values into round key array - // - for (var j = 0; (j < KC) && (t < (ROUNDS + 1) * (BC / 8)); j++, t++) - { - for (var i = 0; i < 4; i++) - { - W[t / (BC / 8)][i] |= (long)(tk[i, j] & 0xff) << ((t * 8) % (BC)); - } - } - } - return W; - } - - private readonly int BC; - private readonly long BC_MASK; - private int ROUNDS; - private readonly int blockBits; - private long[][]? workingKey; - private long A0, - A1, - A2, - A3; - private bool forEncryption; - private readonly byte[] shifts0SC; - private readonly byte[] shifts1SC; - - /** - * default constructor - 128 bit block size. - */ - - public RijndaelEngine() - : this(128) { } - - /** - * basic constructor - set the cipher up for a given blocksize - * - * @param blocksize the blocksize in bits, must be 128, 192, or 256. - */ - - public RijndaelEngine(int blockBits) - { - switch (blockBits) - { - case 128: - BC = 32; - BC_MASK = 0xffffffffL; - shifts0SC = shifts0[0]; - shifts1SC = shifts1[0]; - break; - case 160: - BC = 40; - BC_MASK = 0xffffffffffL; - shifts0SC = shifts0[1]; - shifts1SC = shifts1[1]; - break; - case 192: - BC = 48; - BC_MASK = 0xffffffffffffL; - shifts0SC = shifts0[2]; - shifts1SC = shifts1[2]; - break; - case 224: - BC = 56; - BC_MASK = 0xffffffffffffffL; - shifts0SC = shifts0[3]; - shifts1SC = shifts1[3]; - break; - case 256: - BC = 64; - BC_MASK = unchecked((long)0xffffffffffffffffL); - shifts0SC = shifts0[4]; - shifts1SC = shifts1[4]; - break; - default: - throw new ArgumentException("unknown blocksize to Rijndael"); - } - - this.blockBits = blockBits; - } - - /** - * initialise a Rijndael cipher. - * - * @param forEncryption whether or not we are for encryption. - * @param parameters the parameters required to set up the cipher. - * @exception ArgumentException if the parameters argument is - * inappropriate. - */ - - public void Init(bool forEncryption, ICipherParameters parameters) - { - if (parameters is KeyParameter parameter) - { - workingKey = GenerateWorkingKey(parameter.GetKey()); - this.forEncryption = forEncryption; - return; - } - - throw new ArgumentException( - "invalid parameter passed to Rijndael init - " + parameters.GetType() - ); - } - - public string AlgorithmName => "Rijndael"; - - public bool IsPartialBlockOkay => false; - - public int GetBlockSize() => BC / 2; - - public int ProcessBlock(ReadOnlySpan input, Span output) - { - if (workingKey is null) - { - throw new InvalidOperationException("Rijndael engine not initialised"); - } - - if (BC / 2 > input.Length) - { - throw new DataLengthException("input buffer too short"); - } - - if (BC / 2 > output.Length) - { - throw new DataLengthException("output buffer too short"); - } - - UnPackBlock(input); - - if (forEncryption) - { - EncryptBlock(workingKey); - } - else - { - DecryptBlock(workingKey); - } - - PackBlock(output); - - return BC / 2; - } - - public void Reset() { } - - private void UnPackBlock(ReadOnlySpan bytes) - { - var index = 0; - - A0 = bytes[index++] & 0xff; - A1 = bytes[index++] & 0xff; - A2 = bytes[index++] & 0xff; - A3 = bytes[index++] & 0xff; - - for (var j = 8; j != BC; j += 8) - { - A0 |= (long)(bytes[index++] & 0xff) << j; - A1 |= (long)(bytes[index++] & 0xff) << j; - A2 |= (long)(bytes[index++] & 0xff) << j; - A3 |= (long)(bytes[index++] & 0xff) << j; - } - } - - private void PackBlock(Span bytes) - { - var index = 0; - - for (var j = 0; j != BC; j += 8) - { - bytes[index++] = (byte)(A0 >> j); - bytes[index++] = (byte)(A1 >> j); - bytes[index++] = (byte)(A2 >> j); - bytes[index++] = (byte)(A3 >> j); - } - } - - private void EncryptBlock(long[][] rk) - { - int r; - - // - // begin with a key addition - // - KeyAddition(rk[0]); - - // - // ROUNDS-1 ordinary rounds - // - for (r = 1; r < ROUNDS; r++) - { - Substitution(S); - ShiftRow(shifts0SC); - MixColumn(); - KeyAddition(rk[r]); - } - - // - // Last round is special: there is no MixColumn - // - Substitution(S); - ShiftRow(shifts0SC); - KeyAddition(rk[ROUNDS]); - } - - private void DecryptBlock(long[][] rk) - { - int r; - - // To decrypt: apply the inverse operations of the encrypt routine, - // in opposite order - // - // (KeyAddition is an involution: it 's equal to its inverse) - // (the inverse of Substitution with table S is Substitution with the inverse table of S) - // (the inverse of Shiftrow is Shiftrow over a suitable distance) - // - - // First the special round: - // without InvMixColumn - // with extra KeyAddition - // - KeyAddition(rk[ROUNDS]); - Substitution(Si); - ShiftRow(shifts1SC); - - // - // ROUNDS-1 ordinary rounds - // - for (r = ROUNDS - 1; r > 0; r--) - { - KeyAddition(rk[r]); - InvMixColumn(); - Substitution(Si); - ShiftRow(shifts1SC); - } - - // - // End with the extra key addition - // - KeyAddition(rk[0]); - } -} diff --git a/tests/SharpCompress.Test/Rar/RarArchiveTests.cs b/tests/SharpCompress.Test/Rar/RarArchiveTests.cs index 6496e15e..acc5bdc8 100644 --- a/tests/SharpCompress.Test/Rar/RarArchiveTests.cs +++ b/tests/SharpCompress.Test/Rar/RarArchiveTests.cs @@ -22,11 +22,16 @@ public void Rar_EncryptedFileAndHeader_NoPasswordExceptionTest() => () => ReadRarPassword("Rar.encrypted_filesAndHeader.rar", null) ); - /*[Fact] - public void Rar5_EncryptedFileAndHeader_Archive() - { + [Fact] + public void Rar5_EncryptedFileAndHeader_Archive() => ReadRarPassword("Rar5.encrypted_filesAndHeader.rar", "test"); - }*/ + + [Fact] + public void Rar5_EncryptedFileAndHeader_Archive_Err() => + Assert.Throws( + typeof(CryptographicException), + () => ReadRarPassword("Rar5.encrypted_filesAndHeader.rar", "failed") + ); [Fact] public void Rar5_EncryptedFileAndHeader_NoPasswordExceptionTest() => @@ -39,20 +44,23 @@ public void Rar5_EncryptedFileAndHeader_NoPasswordExceptionTest() => public void Rar_EncryptedFileOnly_Archive() => ReadRarPassword("Rar.encrypted_filesOnly.rar", "test"); - /*[Fact] - public void Rar5_EncryptedFileOnly_Archive() - { + [Fact] + public void Rar_EncryptedFileOnly_Archive_Err() => + Assert.Throws( + typeof(CryptographicException), + () => ReadRarPassword("Rar5.encrypted_filesOnly.rar", "failed") + ); + + [Fact] + public void Rar5_EncryptedFileOnly_Archive() => ReadRarPassword("Rar5.encrypted_filesOnly.rar", "test"); - }*/ [Fact] public void Rar_Encrypted_Archive() => ReadRarPassword("Rar.Encrypted.rar", "test"); - /*[Fact] - public void Rar5_Encrypted_Archive() - { + [Fact] + public void Rar5_Encrypted_Archive() => ReadRarPassword("Rar5.encrypted_filesAndHeader.rar", "test"); - }*/ private void ReadRarPassword(string testArchive, string? password) { From 619e44b30f571999b83e544af7f634341407df35 Mon Sep 17 00:00:00 2001 From: Lars Vahlenberg Date: Sat, 16 Dec 2023 03:08:51 +0100 Subject: [PATCH 2/2] CSharpier fixes --- src/SharpCompress/Common/Rar/Headers/ArchiveCryptHeader.cs | 5 ++--- src/SharpCompress/Common/Rar/ICryptKey.cs | 6 +----- src/SharpCompress/Common/Rar/RarCryptoWrapper.cs | 1 - 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/src/SharpCompress/Common/Rar/Headers/ArchiveCryptHeader.cs b/src/SharpCompress/Common/Rar/Headers/ArchiveCryptHeader.cs index a907002d..529b85cc 100644 --- a/src/SharpCompress/Common/Rar/Headers/ArchiveCryptHeader.cs +++ b/src/SharpCompress/Common/Rar/Headers/ArchiveCryptHeader.cs @@ -2,9 +2,8 @@ using System; using System.Security.Cryptography; - -using SharpCompress.IO; using SharpCompress.Common.Rar.Headers; +using SharpCompress.IO; namespace SharpCompress.Common.Rar.Headers; @@ -17,6 +16,6 @@ public ArchiveCryptHeader(RarHeader header, RarCrcBinaryReader reader) protected override void ReadFinish(MarkingBinaryReader reader) { - CryptInfo = new Rar5CryptoInfo(reader,false); + CryptInfo = new Rar5CryptoInfo(reader, false); } } diff --git a/src/SharpCompress/Common/Rar/ICryptKey.cs b/src/SharpCompress/Common/Rar/ICryptKey.cs index b4188a17..94f068f5 100644 --- a/src/SharpCompress/Common/Rar/ICryptKey.cs +++ b/src/SharpCompress/Common/Rar/ICryptKey.cs @@ -1,11 +1,7 @@ -using System.IO; -using System.Collections.Generic; -using System.Linq; using System.Security.Cryptography; -using System.Text; -using System.Threading.Tasks; namespace SharpCompress.Common.Rar; + internal interface ICryptKey { ICryptoTransform Transformer(byte[] salt); diff --git a/src/SharpCompress/Common/Rar/RarCryptoWrapper.cs b/src/SharpCompress/Common/Rar/RarCryptoWrapper.cs index 1907108c..5f5e8de7 100644 --- a/src/SharpCompress/Common/Rar/RarCryptoWrapper.cs +++ b/src/SharpCompress/Common/Rar/RarCryptoWrapper.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.IO; - using SharpCompress.Crypto; namespace SharpCompress.Common.Rar;