Merge pull request #1 from hrasyid/dev_rardecryption

Dev rardecryption
This commit is contained in:
hrasyid
2013-07-14 09:56:39 -07:00
17 changed files with 512 additions and 49 deletions

View File

@@ -79,6 +79,38 @@ namespace SharpCompress.Test
Read("Rar.rar", CompressionType.Rar);
}
[TestMethod]
public void Rar_EncryptedFileAndHeader_Reader()
{
ReadRar("Rar.encrypted_filesAndHeader.rar", "test");
}
[TestMethod]
public void Rar_EncryptedFileOnly_Reader()
{
ReadRar("Rar.encrypted_filesOnly.rar", "test");
}
private void ReadRar(string testArchive, string password)
{
ResetScratch();
using (Stream stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, testArchive)))
using (var reader = RarReader.Open(stream, password))
{
while (reader.MoveToNextEntry())
{
if (!reader.Entry.IsDirectory)
{
Assert.AreEqual(reader.Entry.CompressionType, CompressionType.Rar);
reader.WriteEntryToDirectory(SCRATCH_FILES_PATH, ExtractOptions.ExtractFullPath | ExtractOptions.Overwrite);
}
}
}
VerifyFiles();
}
[TestMethod]
public void Rar_Entry_Stream()
{

View File

@@ -0,0 +1,65 @@
using System;
using System.IO;
using System.Net.Security;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using SharpCompress.Common;
using SharpCompress.Common.Rar.Headers;
using SharpCompress.IO;
namespace SharpCompress.Test.Rar.Unit
{
/// <summary>
/// Summary description for RarFactoryReaderTest
/// </summary>
[TestClass]
public class RarHeaderFactoryTest : TestBase
{
private RarHeaderFactory rarHeaderFactory;
[TestInitialize]
public void Initialize()
{
ResetScratch();
rarHeaderFactory = new RarHeaderFactory(StreamingMode.Seekable, Options.KeepStreamsOpen);
}
[TestMethod]
public void ReadHeaders_RecognizeEncryptedFlag()
{
ReadEncryptedFlag("Rar.Encrypted_filesAndHeader.rar", true);
}
private void ReadEncryptedFlag(string testArchive, bool isEncrypted)
{
using (var stream = GetReaderStream(testArchive))
foreach (var header in rarHeaderFactory.ReadHeaders(stream))
{
if (header.HeaderType == HeaderType.ArchiveHeader)
{
Assert.AreEqual(isEncrypted, rarHeaderFactory.IsEncrypted);
break;
}
}
}
[TestMethod]
public void ReadHeaders_RecognizeNoEncryptedFlag()
{
ReadEncryptedFlag("Rar.rar", false);
}
private FileStream GetReaderStream(string testArchive)
{
return new FileStream(Path.Combine(TEST_ARCHIVES_PATH, testArchive),
FileMode.Open);
}
}
}

View File

@@ -56,6 +56,7 @@
<Compile Include="ArchiveTests.cs" />
<Compile Include="GZip\GZipWriterTests.cs" />
<Compile Include="GZip\GZipArchiveTests.cs" />
<Compile Include="Rar\Unit\RarHeaderFactoryTest.cs" />
<Compile Include="SevenZip\SevenZipArchiveTests.cs" />
<Compile Include="Streams\StreamTests.cs" />
<Compile Include="Tar\TarWriterTests.cs" />

View File

@@ -9,7 +9,7 @@ namespace SharpCompress.Test
{
public class TestBase
{
protected const string TEST_BASE_PATH = @"C:\Git\sharpcompress";
protected const string TEST_BASE_PATH = @"D:\Codes\sharpcompress";
protected static readonly string TEST_ARCHIVES_PATH = Path.Combine(TEST_BASE_PATH, "TestArchives", "Archives");
protected static readonly string ORIGINAL_FILES_PATH = Path.Combine(TEST_BASE_PATH, "TestArchives", "Original");
protected static readonly string MISC_TEST_FILES_PATH = Path.Combine(TEST_BASE_PATH, "TestArchives", "MiscTest");
@@ -122,14 +122,14 @@ namespace SharpCompress.Test
using (var file2Stream = File.OpenRead(file2))
{
Assert.AreEqual(file1Stream.Length, file2Stream.Length);
int byte1 = 0;
int byte2 = 0;
while (byte1 != -1)
for (int counter = 0; byte1 != -1; counter++ )
{
byte1 = file1Stream.ReadByte();
byte2 = file2Stream.ReadByte();
Assert.AreEqual(byte1, byte2);
if (byte1 != byte2) Assert.AreEqual(byte1, byte2, string.Format("Byte {0} differ between {1} and {2}",
counter, file1, file2));
}
}
}

View File

@@ -27,5 +27,10 @@ namespace SharpCompress.Common.Rar.Headers
internal int PosAv { get; private set; }
internal byte EncryptionVersion { get; private set; }
public bool HasPassword
{
get { return ArchiveHeaderFlags.HasFlag(ArchiveFlags.PASSWORD); }
}
}
}

View File

@@ -35,11 +35,10 @@ namespace SharpCompress.Common.Rar.Headers
return null;
}
}
protected virtual void ReadFromReader(MarkingBinaryReader reader)
{
HeadCRC = reader.ReadInt16();
HeaderType = (HeaderType) (int) (reader.ReadByte() & 0xff);
HeaderType = (HeaderType)(int)(reader.ReadByte() & 0xff);
Flags = reader.ReadInt16();
HeaderSize = reader.ReadInt16();
if (FlagUtility.HasFlag(Flags, LONG_BLOCK))
@@ -58,7 +57,7 @@ namespace SharpCompress.Common.Rar.Headers
header.ReadFromReader(reader);
header.ReadBytes += reader.CurrentReadByteCount;
int headerSizeDiff = header.HeaderSize - (int) header.ReadBytes;
int headerSizeDiff = header.HeaderSize - (int)header.ReadBytes;
if (headerSizeDiff > 0)
{

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using SharpCompress.Compressor.Rar;
using SharpCompress.IO;
namespace SharpCompress.Common.Rar.Headers
@@ -9,22 +10,27 @@ namespace SharpCompress.Common.Rar.Headers
{
private int MAX_SFX_SIZE = 0x80000 - 16; //archive.cpp line 136
internal RarHeaderFactory(StreamingMode mode, Options options)
internal RarHeaderFactory(StreamingMode mode, Options options, string password = null)
{
StreamingMode = mode;
Options = options;
Password = password;
}
private Options Options { get; set; }
public string Password { get; set; }
internal StreamingMode StreamingMode { get; private set; }
internal bool IsEncrypted { get; set; }
internal IEnumerable<RarHeader> ReadHeaders(Stream stream)
{
if (Options.HasFlag(Options.LookForHeader))
{
stream = CheckSFX(stream);
}
RarHeader header;
while ((header = ReadNextHeader(stream)) != null)
{
@@ -107,9 +113,19 @@ namespace SharpCompress.Common.Rar.Headers
return rewindableStream;
}
private RarHeader ReadNextHeader(Stream stream)
{
MarkingBinaryReader reader = new MarkingBinaryReader(stream);
MarkingBinaryReader reader = new MarkingBinaryReader(stream, Password);
if (IsEncrypted)
{
reader.Salt = null;
reader.SkipQueue();
byte[] salt = reader.ReadBytes(8);
reader.Salt = salt;
}
RarHeader header = RarHeader.Create(reader);
if (header == null)
{
@@ -119,7 +135,9 @@ namespace SharpCompress.Common.Rar.Headers
{
case HeaderType.ArchiveHeader:
{
return header.PromoteHeader<ArchiveHeader>(reader);
var ah = header.PromoteHeader<ArchiveHeader>(reader);
IsEncrypted = ah.HasPassword;
return ah;
}
case HeaderType.MarkHeader:
{
@@ -164,7 +182,7 @@ namespace SharpCompress.Common.Rar.Headers
{
ReadOnlySubStream ms
= new ReadOnlySubStream(reader.BaseStream, fh.CompressedSize);
fh.PackedStream = ms;
fh.PackedStream = new RarCryptoWrapper(ms, Password) { Salt = fh.Salt};
}
break;
default:

View File

@@ -0,0 +1,186 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
namespace SharpCompress.Common.Rar
{
internal class RarCryptoWrapper : Stream
{
private readonly Stream _actualStream;
private byte[] _salt;
private Rijndael _rijndael;
private readonly string _password;
private byte[] _aesInitializationVector = new byte[CryptoBlockSize];
private byte[] _aesKey = new byte[CryptoBlockSize];
private Queue<byte> _data = new Queue<byte>();
private const int CryptoBlockSize = 16;
public RarCryptoWrapper(Stream actualStream, string password)
{
_actualStream = actualStream;
_password = password;
}
internal byte[] Salt
{
get { return _salt; }
set
{
_salt = value;
if (value != null) InitializeAes();
}
}
private void InitializeAes()
{
_rijndael = new RijndaelManaged() { Padding = PaddingMode.None };
int rawLength = 2 * _password.Length;
byte[] rawPassword = new byte[rawLength + 8];
byte[] passwordBytes = Encoding.UTF8.GetBytes(_password);
for (int i = 0; i < _password.Length; i++)
{
rawPassword[i * 2] = passwordBytes[i];
rawPassword[i * 2 + 1] = 0;
}
for (int i = 0; i < _salt.Length; i++)
{
rawPassword[i + rawLength] = _salt[i];
}
var sha = new SHA1Managed();
const int noOfRounds = (1 << 18);
IList<byte> bytes = new List<byte>();
byte[] digest;
for (int i = 0; i < noOfRounds; i++)
{
bytes.AddRange(rawPassword);
bytes.AddRange(new[] { (byte)i, (byte)(i >> 8), (byte)(i >> CryptoBlockSize) });
if (i % (noOfRounds / CryptoBlockSize) == 0)
{
digest = sha.ComputeHash(bytes.ToArray());
_aesInitializationVector[i / (noOfRounds / CryptoBlockSize)] = digest[19];
}
}
digest = sha.ComputeHash(bytes.ToArray());
for (int i = 0; i < 4; i++)
for (int j = 0; j < 4; j++)
_aesKey[i * 4 + j] = (byte)
(((digest[i * 4] * 0x1000000) & 0xff000000 |
((digest[i * 4 + 1] * 0x10000) & 0xff0000) |
((digest[i * 4 + 2] * 0x100) & 0xff00) |
digest[i * 4 + 3] & 0xff) >> (j * 8));
_rijndael.IV = new byte[CryptoBlockSize];
_rijndael.Key = _aesKey;
_rijndael.BlockSize = CryptoBlockSize * 8;
}
public override void Flush()
{
throw new NotImplementedException();
}
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotImplementedException();
}
public override void SetLength(long value)
{
throw new NotImplementedException();
}
public override int Read(byte[] buffer, int offset, int count)
{
if (Salt == null) return _actualStream.Read(buffer, offset, count);
return ReadAndDecrypt(buffer, offset, count);
}
public int ReadAndDecrypt(byte[] buffer, int offset, int count)
{
int queueSize = _data.Count;
int sizeToRead = count - queueSize;
if (sizeToRead > 0)
{
int alignedSize = sizeToRead + ((~sizeToRead + 1) & 0xf);
for (int i = 0; i < alignedSize/16; i++)
{
//long ax = System.currentTimeMillis();
byte[] cipherText = new byte[CryptoBlockSize];
_actualStream.Read(cipherText, 0, CryptoBlockSize);
byte[] plainText = new byte[CryptoBlockSize];
var decryptor = _rijndael.CreateDecryptor();
using (MemoryStream msDecrypt = new MemoryStream(cipherText))
{
using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
{
csDecrypt.ReadFully(plainText);
}
}
for (int j = 0; j < plainText.Length; j++)
{
_data.Enqueue((byte) (plainText[j] ^ _aesInitializationVector[j%16])); //32:114, 33:101
}
for (int j = 0; j < _aesInitializationVector.Length; j++)
{
_aesInitializationVector[j] = cipherText[j];
}
}
for (int i = 0; i < count; i++)
{
buffer[offset+i] = _data.Dequeue();
}
}
return count;
}
public override void Write(byte[] buffer, int offset, int count)
{
throw new NotImplementedException();
}
public override bool CanRead
{
get { throw new NotImplementedException(); }
}
public override bool CanSeek
{
get { throw new NotImplementedException(); }
}
public override bool CanWrite
{
get { throw new NotImplementedException(); }
}
public override long Length
{
get { throw new NotImplementedException(); }
}
public override long Position { get; set; }
protected override void Dispose(bool disposing)
{
if(_rijndael!= null) _rijndael.Dispose();
base.Dispose(disposing);
}
}
}

View File

@@ -13,11 +13,18 @@ namespace SharpCompress.Common.Rar
public abstract class RarVolume : Volume
{
private readonly RarHeaderFactory headerFactory;
public string Password { get; set; }
internal RarVolume(StreamingMode mode, Stream stream, Options options)
internal RarVolume(StreamingMode mode, Stream stream, Options options)
: this(mode, stream, null, options)
{
}
internal RarVolume(StreamingMode mode, Stream stream, string password, Options options)
: base(stream, options)
{
headerFactory = new RarHeaderFactory(mode, options);
headerFactory = new RarHeaderFactory(mode, options, password);
}
internal StreamingMode Mode

View File

@@ -1,17 +1,89 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
namespace SharpCompress.IO
{
internal class MarkingBinaryReader : BinaryReader
{
public MarkingBinaryReader(Stream stream)
private byte[] _salt;
private readonly string _password;
private byte[] _aesInitializationVector = new byte[16];
private byte[] _aesKey = new byte[16];
private Rijndael _rijndael;
private Queue<byte> _data = new Queue<byte>();
public MarkingBinaryReader(Stream stream, string password = null)
: base(stream)
{
_password = password;
}
public long CurrentReadByteCount { get; private set; }
internal byte[] Salt
{
get { return _salt; }
set
{
_salt = value;
if (value != null) InitializeAes();
}
}
private void InitializeAes()
{
_rijndael = new RijndaelManaged() { Padding = PaddingMode.None };
int rawLength = 2 * _password.Length;
byte[] rawPassword = new byte[rawLength + 8];
byte[] passwordBytes = Encoding.UTF8.GetBytes(_password);
for (int i = 0; i < _password.Length; i++)
{
rawPassword[i * 2] = passwordBytes[i];
rawPassword[i * 2 + 1] = 0;
}
for (int i = 0; i < _salt.Length; i++)
{
rawPassword[i + rawLength] = _salt[i];
}
var sha = new SHA1Managed();
const int noOfRounds = (1 << 18);
IList<byte> bytes = new List<byte>();
byte[] digest;
for (int i = 0; i < noOfRounds; i++)
{
bytes.AddRange(rawPassword);
bytes.AddRange(new[] { (byte)i, (byte)(i >> 8), (byte)(i >> 16) });
if (i % (noOfRounds / 16) == 0)
{
digest = sha.ComputeHash(bytes.ToArray());
_aesInitializationVector[i / (noOfRounds / 16)] = digest[19];
}
}
digest = sha.ComputeHash(bytes.ToArray());
for (int i = 0; i < 4; i++)
for (int j = 0; j < 4; j++)
_aesKey[i * 4 + j] = (byte)
(((digest[i * 4] * 0x1000000) & 0xff000000 |
((digest[i * 4 + 1] * 0x10000) & 0xff0000) |
((digest[i * 4 + 2] * 0x100) & 0xff00) |
digest[i * 4 + 3] & 0xff) >> (j * 8));
_rijndael.IV = new byte[16];
_rijndael.Key = _aesKey;
_rijndael.BlockSize = 16 * 8;
}
public void Mark()
{
CurrentReadByteCount = 0;
@@ -42,14 +114,70 @@ namespace SharpCompress.IO
public override byte ReadByte()
{
CurrentReadByteCount++;
return base.ReadByte();
return ReadBytes(1).Single();
}
public override byte[] ReadBytes(int count)
{
CurrentReadByteCount += count;
return base.ReadBytes(count);
return UseEncryption ?
ReadAndDecryptBytes(count)
: base.ReadBytes(count);
}
protected bool UseEncryption
{
get { return Salt != null; }
}
private byte[] ReadAndDecryptBytes(int count)
{
int queueSize = _data.Count;
int sizeToRead = count - queueSize;
if (sizeToRead > 0)
{
int alignedSize = sizeToRead + ((~sizeToRead + 1) & 0xf);
for (int i = 0; i < alignedSize / 16; i++)
{
//long ax = System.currentTimeMillis();
byte[] cipherText = base.ReadBytes(16);
byte[] plainText = new byte[16];
var decryptor = _rijndael.CreateDecryptor();
using (MemoryStream msDecrypt = new MemoryStream(cipherText))
{
using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
{
csDecrypt.ReadFully(plainText);
}
}
for (int j = 0; j < plainText.Length; j++)
{
_data.Enqueue((byte)(plainText[j] ^ _aesInitializationVector[j % 16])); //32:114, 33:101
}
for (int j = 0; j < _aesInitializationVector.Length; j++)
{
_aesInitializationVector[j] = cipherText[j];
}
}
}
var decryptedBytes = new byte[count];
for (int i = 0; i < count; i++)
{
decryptedBytes[i] = _data.Dequeue();
}
return decryptedBytes;
}
public override char ReadChar()
@@ -65,45 +193,49 @@ namespace SharpCompress.IO
#if !PORTABLE
public override decimal ReadDecimal()
{
CurrentReadByteCount += 16;
return base.ReadDecimal();
return ByteArrayToDecimal(ReadBytes(16), 0);
}
private decimal ByteArrayToDecimal(byte[] src, int offset)
{
//http://stackoverflow.com/a/16984356/385387
var i1 = BitConverter.ToInt32(src, offset);
var i2 = BitConverter.ToInt32(src, offset + 4);
var i3 = BitConverter.ToInt32(src, offset + 8);
var i4 = BitConverter.ToInt32(src, offset + 12);
return new decimal(new[] { i1, i2, i3, i4 });
}
#endif
public override double ReadDouble()
{
CurrentReadByteCount += 8;
return base.ReadDouble();
return BitConverter.ToDouble(ReadBytes(8), 0);
}
public override short ReadInt16()
{
CurrentReadByteCount += 2;
return base.ReadInt16();
return BitConverter.ToInt16(ReadBytes(2), 0);
}
public override int ReadInt32()
{
CurrentReadByteCount += 4;
return base.ReadInt32();
return BitConverter.ToInt32(ReadBytes(4), 0);
}
public override long ReadInt64()
{
CurrentReadByteCount += 8;
return base.ReadInt64();
return BitConverter.ToInt64(ReadBytes(8), 0);
}
public override sbyte ReadSByte()
{
CurrentReadByteCount++;
return base.ReadSByte();
return (sbyte)ReadByte();
}
public override float ReadSingle()
{
CurrentReadByteCount += 4;
return base.ReadSingle();
return BitConverter.ToSingle(ReadBytes(4), 0);
}
public override string ReadString()
@@ -113,20 +245,29 @@ namespace SharpCompress.IO
public override ushort ReadUInt16()
{
CurrentReadByteCount += 2;
return base.ReadUInt16();
return BitConverter.ToUInt16(ReadBytes(2), 0);
}
public override uint ReadUInt32()
{
CurrentReadByteCount += 4;
return base.ReadUInt32();
return BitConverter.ToUInt32(ReadBytes(4), 0);
}
public override ulong ReadUInt64()
{
CurrentReadByteCount += 8;
return base.ReadUInt64();
return BitConverter.ToUInt64(ReadBytes(8), 0);
}
public void ClearQueue()
{
_data.Clear();
}
public void SkipQueue()
{
var position = BaseStream.Position;
BaseStream.Position = position + _data.Count;
ClearQueue();
}
}
}

View File

@@ -11,10 +11,7 @@ namespace SharpCompress.IO
protected override void Dispose(bool disposing)
{
if (disposing)
{
//Stream.Dispose();
}
//don't dispose anything
}
public Stream Stream { get; private set; }

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using SharpCompress.Common;
@@ -12,6 +13,7 @@ namespace SharpCompress.Reader.Rar
/// </summary>
public abstract class RarReader : AbstractReader<RarReaderEntry, RarVolume>
{
public string Password { get; set; }
private RarVolume volume;
private readonly Unpack pack = new Unpack();
@@ -37,8 +39,7 @@ namespace SharpCompress.Reader.Rar
/// <returns></returns>
public static RarReader Open(Stream stream, Options options = Options.KeepStreamsOpen)
{
stream.CheckNotNull("stream");
return new SingleVolumeRarReader(stream, options);
return Open(stream, null, options);
}
/// <summary>
@@ -57,7 +58,7 @@ namespace SharpCompress.Reader.Rar
internal override IEnumerable<RarReaderEntry> GetEntries(Stream stream)
{
volume = new RarReaderVolume(stream, Options);
volume = new RarReaderVolume(stream, Password, Options);
foreach (RarFilePart fp in volume.ReadFileParts())
{
ValidateArchive(volume);
@@ -76,5 +77,11 @@ namespace SharpCompress.Reader.Rar
new MultiVolumeReadOnlyStream(
CreateFilePartEnumerableForCurrentEntry().Cast<RarFilePart>(), this)));
}
public static RarReader Open(Stream stream, string password, Options options = Options.KeepStreamsOpen)
{
stream.CheckNotNull("stream");
return new SingleVolumeRarReader(stream, password, options);
}
}
}

View File

@@ -9,8 +9,10 @@ namespace SharpCompress.Reader.Rar
{
public class RarReaderVolume : RarVolume
{
internal RarReaderVolume(Stream stream, Options options)
: base(StreamingMode.Streaming, stream, options)
internal RarReaderVolume(Stream stream, string password, Options options)
: base(StreamingMode.Streaming, stream, password, options)
{
}

View File

@@ -7,11 +7,13 @@ namespace SharpCompress.Reader.Rar
{
internal class SingleVolumeRarReader : RarReader
{
private readonly Stream stream;
internal SingleVolumeRarReader(Stream stream, Options options)
internal SingleVolumeRarReader(Stream stream, string password, Options options)
: base(options)
{
Password = password;
this.stream = stream;
}

View File

@@ -54,7 +54,7 @@
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
<CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
<NoWarn>1591</NoWarn>
<DocumentationFile>..\bin\SharpCompress.XML</DocumentationFile>
@@ -127,6 +127,7 @@
<Compile Include="Common\Entry.cs" />
<Compile Include="Common\IEntry.cs" />
<Compile Include="Common\IVolume.cs" />
<Compile Include="Common\Rar\RarCryptoWrapper.cs" />
<Compile Include="Common\SevenZip\CBindPair.cs" />
<Compile Include="Common\SevenZip\CCoderInfo.cs" />
<Compile Include="Common\SevenZip\CFileItem.cs" />

Binary file not shown.

Binary file not shown.