-
Notifications
You must be signed in to change notification settings - Fork 484
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #788 from Erior/develop
RAR5 decryption support
- Loading branch information
Showing
16 changed files
with
419 additions
and
2,137 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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(); | ||
} | ||
} |
41 changes: 6 additions & 35 deletions
41
src/SharpCompress/Common/Rar/Headers/ArchiveCryptHeader.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} | ||
} |
Oops, something went wrong.