mirror of
https://github.com/adamhathcock/sharpcompress.git
synced 2026-09-22 23:15:20 +00:00
@@ -25,10 +25,19 @@ internal class SeekableFilePart : RarFilePart
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
87
src/SharpCompress/Common/Rar/CryptKey3.cs
Normal file
87
src/SharpCompress/Common/Rar/CryptKey3.cs
Normal file
@@ -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();
|
||||
}
|
||||
}
|
||||
97
src/SharpCompress/Common/Rar/CryptKey5.cs
Normal file
97
src/SharpCompress/Common/Rar/CryptKey5.cs
Normal file
@@ -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<byte[]> 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<byte[]> { };
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -1,50 +1,21 @@
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Security.Cryptography;
|
||||
using SharpCompress.Common.Rar.Headers;
|
||||
using SharpCompress.IO;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 @@ internal class FileHeader : RarHeader
|
||||
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 @@ internal class FileHeader : RarHeader
|
||||
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 @@ internal class FileHeader : RarHeader
|
||||
}
|
||||
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 @@ internal class FileHeader : RarHeader
|
||||
|
||||
var fileNameBytes = reader.ReadBytes(nameSize);
|
||||
|
||||
const int saltSize = 8;
|
||||
const int newLhdSize = 32;
|
||||
|
||||
switch (HeaderCode)
|
||||
@@ -292,7 +312,7 @@ internal class FileHeader : RarHeader
|
||||
var datasize = HeaderSize - newLhdSize - nameSize;
|
||||
if (HasFlag(FileFlagsV4.SALT))
|
||||
{
|
||||
datasize -= saltSize;
|
||||
datasize -= EncryptionConstV5.SIZE_SALT30;
|
||||
}
|
||||
if (datasize > 0)
|
||||
{
|
||||
@@ -313,7 +333,7 @@ internal class FileHeader : RarHeader
|
||||
|
||||
if (HasFlag(FileFlagsV4.SALT))
|
||||
{
|
||||
R4Salt = reader.ReadBytes(saltSize);
|
||||
R4Salt = reader.ReadBytes(EncryptionConstV5.SIZE_SALT30);
|
||||
}
|
||||
if (HasFlag(FileFlagsV4.EXT_TIME))
|
||||
{
|
||||
@@ -431,7 +451,7 @@ internal class FileHeader : RarHeader
|
||||
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 class FileHeader : RarHeader
|
||||
|
||||
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; }
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.IO;
|
||||
using SharpCompress.IO;
|
||||
|
||||
|
||||
@@ -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 class RarHeaderFactory
|
||||
"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 class RarHeaderFactory
|
||||
|
||||
{
|
||||
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 class RarHeaderFactory
|
||||
{
|
||||
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 class RarHeaderFactory
|
||||
}
|
||||
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:
|
||||
{
|
||||
|
||||
8
src/SharpCompress/Common/Rar/ICryptKey.cs
Normal file
8
src/SharpCompress/Common/Rar/ICryptKey.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace SharpCompress.Common.Rar;
|
||||
|
||||
internal interface ICryptKey
|
||||
{
|
||||
ICryptoTransform Transformer(byte[] salt);
|
||||
}
|
||||
59
src/SharpCompress/Common/Rar/Rar5CryptoInfo.cs
Normal file
59
src/SharpCompress/Common/Rar/Rar5CryptoInfo.cs
Normal file
@@ -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 = { };
|
||||
}
|
||||
@@ -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<byte> _data = new Queue<byte>();
|
||||
private long _readCount;
|
||||
|
||||
public RarCryptoBinaryReader(Stream stream, string password)
|
||||
public RarCryptoBinaryReader(Stream stream, ICryptKey cryptKey)
|
||||
: base(stream)
|
||||
{
|
||||
_password = password;
|
||||
var salt = base.ReadBytes(EncryptionConstV5.SIZE_SALT30);
|
||||
_readCount += EncryptionConstV5.SIZE_SALT30;
|
||||
_rijndael = new BlockTransformer(cryptKey.Transformer(salt));
|
||||
}
|
||||
|
||||
// coderb: not sure why this was being done at this logical point
|
||||
//SkipQueue();
|
||||
var salt = ReadBytes(8);
|
||||
|
||||
_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 @@ internal sealed class RarCryptoBinaryReader : RarCrcBinaryReader
|
||||
|
||||
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)
|
||||
|
||||
@@ -1,21 +1,20 @@
|
||||
using System;
|
||||
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<byte> _data = new Queue<byte>();
|
||||
|
||||
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 +25,6 @@ internal sealed class RarCryptoWrapper : Stream
|
||||
|
||||
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 +36,7 @@ internal sealed class RarCryptoWrapper : Stream
|
||||
if (sizeToRead > 0)
|
||||
{
|
||||
var alignedSize = sizeToRead + ((~sizeToRead + 1) & 0xf);
|
||||
Span<byte> cipherText = stackalloc byte[RarRijndael.CRYPTO_BLOCK_SIZE];
|
||||
Span<byte> cipherText = stackalloc byte[16];
|
||||
for (var i = 0; i < alignedSize / 16; i++)
|
||||
{
|
||||
//long ax = System.currentTimeMillis();
|
||||
|
||||
@@ -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<byte> cipherText)
|
||||
{
|
||||
Span<byte> 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() { }
|
||||
}
|
||||
32
src/SharpCompress/Crypto/BlockTransformer.cs
Normal file
32
src/SharpCompress/Crypto/BlockTransformer.cs
Normal file
@@ -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<byte> cipherText)
|
||||
{
|
||||
var decryptedBytes = new byte[cipherText.Length];
|
||||
var bytes = _transform.TransformBlock(
|
||||
cipherText.ToArray(),
|
||||
0,
|
||||
cipherText.Length,
|
||||
decryptedBytes,
|
||||
0
|
||||
);
|
||||
|
||||
return decryptedBytes;
|
||||
}
|
||||
|
||||
public void Dispose() { }
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -22,11 +22,16 @@ public class RarArchiveTests : ArchiveTests
|
||||
() => 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 class RarArchiveTests : ArchiveTests
|
||||
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)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user