-
Notifications
You must be signed in to change notification settings - Fork 519
Added support for decryption of RAR files #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 22 commits
1d10ea2
ad42193
af8e4f9
22d181e
c0b630c
bb2dcce
e78ead7
e1511bf
13de04d
78cecda
34190f6
ab19ded
d15c878
37c4665
1e7d6dc
6c2b447
203a7be
a73c831
3370fad
1ab89ba
c7f6b50
f9194b6
a29f7d4
cc902bc
430263b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,4 +6,5 @@ bin/ | |
| *.user | ||
| TestArchives/Scratch/ | ||
| TestArchives/Scratch2/ | ||
| TestResults/ | ||
| *.nupkg | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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); | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -14,7 +14,7 @@ | |
| [assembly: AssemblyProduct("SharpCompress")] | ||
| [assembly: | ||
| InternalsVisibleTo( | ||
| "SharpCompress.Test, PublicKey=002400000480000094000000060200000024000052534131000400000100010005d6ae1b0f6875393da83c920a5b9408f5191aaf4e8b3c2c476ad2a11f5041ecae84ce9298bc4c203637e2fd3a80ad5378a9fa8da1363e98cea45c73969198a4b64510927c910001491cebbadf597b22448ad103b0a4007e339faf8fe8665dcdb70d65b27ac05b1977c0655fad06b372b820ecbdccf10a0f214fee0986dfeded" | ||
| "SharpCompress.Test" | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Revert this please. |
||
| )] | ||
| #endif | ||
| #endif | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 @@ internal class RarHeaderFactory | |
| { | ||
| 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; } | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. private setter? |
||
|
|
||
| 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 @@ private RewindableStream GetRewindableStream(Stream stream) | |
| 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 @@ private RarHeader ReadNextHeader(Stream stream) | |
| { | ||
| 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 @@ private RarHeader ReadNextHeader(Stream stream) | |
| { | ||
| ReadOnlySubStream ms | ||
| = new ReadOnlySubStream(reader.BaseStream, fh.CompressedSize); | ||
| fh.PackedStream = ms; | ||
| fh.PackedStream = new RarCryptoWrapper(ms, Password) { Salt = fh.Salt}; | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't this be only wrapped if it is encrypted? |
||
| } | ||
| break; | ||
| default: | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,116 @@ | ||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.IO; | ||
|
|
||
| namespace SharpCompress.Common.Rar | ||
| { | ||
| internal class RarCryptoWrapper : Stream | ||
| { | ||
| private readonly Stream _actualStream; | ||
| private byte[] _salt; | ||
| private RarRijndael _rijndael; | ||
| private readonly string _password; | ||
| private readonly Queue<byte> _data = new Queue<byte>(); | ||
|
|
||
| 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 = RarRijndael.InitializeFrom(_password, _salt); | ||
| } | ||
|
|
||
| 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[RarRijndael.CryptoBlockSize]; | ||
| _actualStream.Read(cipherText, 0, RarRijndael.CryptoBlockSize); | ||
|
|
||
| var readBytes = _rijndael.ProcessBlock(cipherText); | ||
| foreach(var readByte in readBytes) | ||
| _data.Enqueue(readByte); | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would using a queue like this be a source of performance problems? Also, please nest everything with braces. Even if it's just a single line under an foreach for if statement.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The queue is necessary because we can only decrypt 16 bytes at a time, but the client of this code does not necessarily read 16 block at a time. As a solution for this, all 16 bytes will go to a queue, and if for instance the client only reads 2 bytes, the other 14 will stay in the queue and will be directly read (without decrypting) the next time. Also, performance wise I don't think there's an issue, because generarlly, queues only involve in-memory access without any difficult calculation. I checked, the performance culprit in this class is the SHA1 calculation.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. About braces, don't additional braces make the code look more verbose? If you're worried that people might add another line and forget to use braces, I think with IDEs like Visual studio it won't happen because the indentation will make it look obvious
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It does add more lines yes. However, there are two reasons I do it. First, it just makes it perfectly clear on a quick skim of the code that a line is nested. Second, and probably more important, merging goes a lot smoother with the nesting. I've shot myself in the foot with merging because the tool couldn't see the differences with the single line versus the nested approach. Having a couple of extra lines because of braces never hurt anyone :) Of course, you can write some nasty code because of too many levels of nesting in a method, but that's a different story.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ok.. updated to use braces On 16 July 2013 22:22, Adam Hathcock notifications@github.com wrote:
|
||
|
|
||
| } | ||
|
|
||
| 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); | ||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Revert this please.