2021-06-14

RAR3如何產生 AES Key、IV

RAR3如何產生Key、IV,以及遇到的困難(1)
困難(1)的解法(慢)
困難(1)的解法(快)
遇到的困難(2):密碼字數超過28個字
困難(1)和困難(2)的解法

RAR3如何產生Key、IV,以及遇到的困難(1)

材料:
    [string]$password   (使用者輸入)
    [byte[]]$salt       (位於RAR檔頭)

$seed = [System.Text.Encoding]::Unicode.GetBytes($password) + $salt
[byte[]]$iv = New-Object byte[] 16 $sha1 = [System.Security.Cryptography.SHA1]::Create()
$n = 0 for($i = 0; $i -lt 16; $i++){ for($j = 0; $j -lt 0x4000; $j++){ $n4bytes = [System.BitConverter]::GetBytes($n++)
# 每次會傳送 $seed 和 $n4bytes 進行處理 # 總共會傳送 (16 * 0x4000) 次 $sha1.TransformBlock($seed, 0, $seed.Length, $null, 0) > $null # 只傳送 $n4bytes 的前 3 bytes $sha1.TransformBlock($n4bytes, 0, 3, $null, 0) > $null if ($j -eq 0){ # 計算 Hash $sha1.TransformFinalBlock($seed, 0, 0) > $null # 得到 IV 的一部份 $iv[$i] = $sha1.Hash[19]
# 遇到的困難(1): # 算出Hash之後,必須以 「還沒 TransformFinalBlock 的狀態」 # 繼續 TransformBlock # 然而,現在已經 TransformFinalBlock 了,無法改變 } } } # 計算 Hash $sha1.TransformFinalBlock($seed, 0, 0) > $null $hash = $sha1.Hash
# 得到 Key [byte[]]$key = $hash[3..0] + $hash[7..4] + $hash[11..8] + $hash[15..12]

困難(1)的解法(慢)

$seed = [System.Text.Encoding]::Unicode.GetBytes($password) + $salt

[byte[]]$iv = New-Object byte[] 16
$sha1 = [System.Security.Cryptography.SHA1]::Create()
for($i = 0; $i -lt 16; $i++){ # 算1次,得到$iv[0] # 算 1*0x4000 + 1 次,得到 $iv[1] # 算 15*0x4000 + 1 次,得到 $iv[15] $n = 0 for($j = 0; $j -lt $i*0x4000 + 1; $j++){ $n4bytes = [System.BitConverter]::GetBytes($n++) $sha1.TransformBlock($seed, 0, $seed.Length, $null, 0) > $null $sha1.TransformBlock($n4bytes, 0, 3, $null, 0) > $null } $sha1.TransformFinalBlock($seed, 0, 0) > $null $iv[$i] = $sha1.Hash[19] $sha1.Initialize() }
# 算 16*0x4000 次,得到 $key $n = 0 for ($i = 0; $i -lt 16*0x4000; $i++){ $n4bytes = [System.BitConverter]::GetBytes($n++) $sha1.TransformBlock($seed, 0, $seed.Length, $null, 0) > $null $sha1.TransformBlock($n4bytes, 0, 3, $null, 0) > $null }
# 計算 Hash $sha1.TransformFinalBlock($seed, 0, 0) > $null $hash = $sha1.Hash
# 得到 Key [byte[]]$key = $hash[3..0] + $hash[7..4] + $hash[11..8] + $hash[15..12]

困難(1)的解法(快)

$seed = [System.Text.Encoding]::Unicode.GetBytes($password) + $salt

[byte[]]$iv = New-Object byte[] 16
# 只有SHA1Managed可以
$sha1 = [System.Security.Cryptography.SHA1Managed]::new()
$sha1Type = $sha1.gettype()
$Field_count = $sha1Type.GetField('_count', 36) $Field_stateSHA1 = $sha1Type.GetField('_stateSHA1', 36) $Field_buffer = $sha1Type.GetField('_buffer', 36)
[Int64]$_count_copy = 0 [UInt32[]]$_stateSHA1_copy = New-Object UInt32[] 5 [byte[]]$_buffer_copy = New-Object byte[] 64

$n = 0 for($i = 0; $i -lt 16; $i++){ for($j = 0; $j -lt 0x4000; $j++){ $n4bytes = [System.BitConverter]::GetBytes($n++) $sha1.TransformBlock($seed, 0, $seed.length, $null, 0) > $null $sha1.TransformBlock($n4bytes, 0, 3, $null, 0) > $null if ($j -eq 0){ # 取得 $sha1 狀態 $_count = $Field_count.GetValue($sha1) $_stateSHA1 = $Field_stateSHA1.GetValue($sha1) $_buffer = $Field_buffer.GetValue($sha1)
# 備份 $sha1 狀態 $_count_copy = $_count [array]::Copy($_stateSHA1, $_stateSHA1_copy, 5) [array]::Copy($_buffer, $_buffer_copy, 64) # 計算Hash $sha1.TransformFinalBlock($seed, 0, 0) > $null $iv[$i] = $sha1.Hash[19] # 還原 $sha1 狀態 $sha1.Initialize() $Field_count.setValue($sha1, $_count_copy) [array]::Copy($_stateSHA1_copy, $_stateSHA1, 5) [array]::Copy($_buffer_copy, $_buffer, 64) } } } $sha1.TransformFinalBlock($seed, 0, 0) > $null $hash = $sha1.Hash [byte[]]$key = $hash[3..0] + $hash[7..4] + $hash[11..8] + $hash[15..12]

遇到的困難(2):密碼字數超過28個字

# 當密碼字數超過28個字,會使$seed長度大於64
$seed = [System.Text.Encoding]::Unicode.GetBytes($password) + $salt


$sha1.TransformBlock($seed, 0, $seed.length, $null, 0) > $null

<#
$sha1 在TransformBlock的時候,每次處理 64 bytes的資料
剩下來不足 64 bytes 的資料,會放到 buffer 暫時存放

當$seed送去TransformBlock時
會把部份資料複製到buffer、湊足 64 bytes以進行處理
然後,再從 $seed的某起點、讀取64bytes進行處理


RAR3 SHA1特別之處:
    從$seed某起點、讀取 64bytes 進行處理之後,
    從$seed某起點、寫入 64bytes 到$seed
    造成$seed內容的改變

    當$seed長度66:
        當buffer資料長度為0:
            從$seed起點0、讀取 64bytes 進行處理,不寫入資料到$seed
            剩餘 2bytes 存入buffer

        當buffer資料長度為62:
            從$seed起點0、讀取 2 bytes、湊足 64 bytes以進行處理
            從$seed起點2、讀取 64bytes 進行處理
            從$seed起點2、寫入 64bytes 到$seed

    當$seed長度130:
        當buffer資料長度為0:
            從$seed起點0、讀取 64bytes 進行處理,不寫入資料到$seed
            從$seed起點64、讀取 64bytes 進行處理
            從$seed起點64、寫入 64bytes 到$seed
            剩餘 2bytes 存入buffer

        當buffer資料長度為62:
            從$seed起點0、讀取 2 bytes、湊足 64 bytes以進行處理
            從$seed起點2、讀取 64bytes 進行處理
            從$seed起點2、寫入 64bytes 到$seed
            從$seed起點66、讀取 64bytes 進行處理
            從$seed起點66、寫入 64bytes 到$seed

#>



困難(1)和困難(2)的解法

使用RAR3_SHA1.cs
PS R:\> Add-Type -Path .\RAR3_SHA1.cs
PS R:\> $sha1 = new-object SHA1SP.SHA1SP
PS R:\>
PS R:\> [byte[]]$data = 11, 12, 13
PS R:\> $sha1.update($data)
PS R:\> $r = $sha1.digest()
PS R:\> [System.BitConverter]::ToString($r)
0D-52-37-C1-4C-75-22-CD-24-6A-D7-67-38-26-25-6F-A5-62-0A-80
PS R:\>
PS R:\> [byte[]]$data2 = 21, 22, 23
PS R:\> $sha1.update($data2)
PS R:\> $r = $sha1.digest()
PS R:\> [System.BitConverter]::ToString($r)
60-92-DE-71-DB-96-D8-F9-C6-BC-57-14-BB-D9-FA-54-D3-42-7F-A4
PS R:\>
PS R:\> $sha1.Reset()
PS R:\> $sha1.update($data + $data2)
PS R:\> $r = $sha1.digest()
PS R:\> [System.BitConverter]::ToString($r)
60-92-DE-71-DB-96-D8-F9-C6-BC-57-14-BB-D9-FA-54-D3-42-7F-A4
PS R:\>
ps1新增程式碼
Add-Type -Path .\RAR3_SHA1.cs
$sha1 = new-object SHA1SP.SHA1SP

// 當密碼超過28字時,可能會寫資料回來
// 要用New-Object另外準備空間,不能使用原本的$seed
$seedNew = New-Object byte[] $seed.length
[array]::Copy($seed, 0, $seedNew, 0, $seed.length)

ps1修改程式碼
$sha1.update($seedNew)
$sha1.update($n4bytes[0..2])
$hash = $sha1.digest()

// RAR3_SHA1.cs

// https://zh.wikipedia.org/wiki/SHA-1
// https://github.com/mono/mono/blob/master/mcs/class/corlib/System.Security.Cryptography/SHA1CryptoServiceProvider.cs
// For caculating RAR 3 key IV // It's different than other SHA1 algorithm when input data length greater than 64 namespace SHA1SP {
public class SHA1SP { private uint[] h = new uint[5]; private uint[] w = new uint[80]; private byte[] buffer = new byte[64]; private long count = 0;
public SHA1SP (){ h[0] = 0x67452301; h[1] = 0xEFCDAB89; h[2] = 0x98BADCFE; h[3] = 0x10325476; h[4] = 0xC3D2E1F0; }
public void Reset (){ h[0] = 0x67452301; h[1] = 0xEFCDAB89; h[2] = 0x98BADCFE; h[3] = 0x10325476; h[4] = 0xC3D2E1F0;
count = 0; }
public void update(byte[] data){ if (null == data){return;}
// 取得buffer資料長度 int bufferCount = (int)count & 0x3f; long start = 0; long Len = data.Length; long end = start + Len;
// 更新:已輸入了多少資料 count += Len; bool afterFirstBlock = false;
// 當buffer有資料 if (bufferCount != 0){ // n = buffer剩餘空間 int n = 64 - bufferCount;
if (Len < n){// buffer剩餘空間足夠、可以放全部資料 System.Array.Copy(data, 0, buffer, bufferCount, Len); return;
}else{// 把buffer剩餘空間全部用掉、用來放一部份資料 System.Array.Copy(data, 0, buffer, bufferCount, n); process64(buffer, 0);
start += n; afterFirstBlock = true; } }
while (start + 64 <= end) { process64(data, start);
if (afterFirstBlock){// RAR3 SHA1 特別的部份:會寫資料回data for (int i=0, j=64; j < 80; i+=4, j++){ data[start + i] = (byte)(w[j]); data[start + i + 1] = (byte)(w[j] >> 8); data[start + i + 2] = (byte)(w[j] >> 16); data[start + i + 3] = (byte)(w[j] >> 24); } }
start += 64; afterFirstBlock = true; }
if (start < end){// 未處理的資料,存到buffer System.Array.Copy(data, start, buffer, 0, end - start); }
return; }
public byte[] digest(){ if (count == 0){return null;} // 取得buffer資料長度 int bufferCount = (int)count & 0x3f;
int finalBlockLen = 64; // 如果「buffer資料長度」+「至少 1 byte 補 0」+ 「8 bytes 長度資訊」大於 64 if (bufferCount + 1 + 8 > 64){finalBlockLen = 128;}
byte[] finalBlock = new byte [finalBlockLen];
int i = 0; // 複製 buffer資料 到 finalBlock for (; i < bufferCount; i++){ finalBlock[i] = buffer[i]; }
// 至少 1 byte 補 0 // 靠近buffer資料的那 1 bit 為 1 // 1000 0000 finalBlock[i++] = 0x80;
// 計算補 0 要補到哪個位置才停止 int zeroEnd = finalBlockLen - 8; // 補 0 for (; i < zeroEnd; i++){ finalBlock[i] = 0; }
// 計算資料長度為多少 bits ulong TotalCount = (ulong)(count * 8); finalBlock[i++] = (byte)(TotalCount >> 56); finalBlock[i++] = (byte)(TotalCount >> 48); finalBlock[i++] = (byte)(TotalCount >> 40); finalBlock[i++] = (byte)(TotalCount >> 32); finalBlock[i++] = (byte)(TotalCount >> 24); finalBlock[i++] = (byte)(TotalCount >> 16); finalBlock[i++] = (byte)(TotalCount >> 8); finalBlock[i] = (byte)(TotalCount);
// 備份狀態 uint[] hCopy = new uint[5]; for (i = 0; i < 5; i++){ hCopy[i] = h[i]; }
process64(finalBlock, 0);
if (finalBlockLen == 128){ process64(finalBlock, 64); } byte[] result = new byte[20]; // 得到 Hash for (i = 0; i < 5; i++){ result[i*4] = (byte)(h[i] >> 24); result[i*4+1] = (byte)(h[i] >> 16); result[i*4+2] = (byte)(h[i] >> 8); result[i*4+3] = (byte)(h[i]); }
// 還原狀態 for (i = 0; i < 5; i++){ h[i] = hCopy[i]; }
return result; }
private void process64(byte[] data, long start){ int i; long j;
// 把 64 bytes 資料轉成 16個 uint for(i = 0; i < 16; i++){ j = start + i*4; w[i] = (uint)(data[j] << 24 | data[j+1] << 16 | data[j+2] << 8 | data[j+3]); }
// 用 16個 uint 產生 64個 uint // 結果,總共有 80個 uint uint temp; for(i = 16; i < 80; i++){ temp = w[i-3] ^ w[i-8] ^ w[i-14] ^ w[i-16]; w[i] = (temp << 1) | (temp >> 31); } // 複製一份資料,以供運算 uint a = h[0]; uint b = h[1]; uint c = h[2]; uint d = h[3]; uint e = h[4];
uint f = 0; uint k = 0; for(i = 0; i < 80; i++){ if (i < 20){ f = (b & c) | ((~ b) & d); k = 0x5A827999; }else if (i < 40){ f = b ^ c ^ d; k = 0x6ED9EBA1; }else if (i < 60){ f = (b & c) | (b & d) | (c & d); k = 0x8F1BBCDC; }else{ f = b ^ c ^ d; k = 0xCA62C1D6; } temp = (a << 5 | a >> 27) + f + e + k + w[i]; e = d; d = c; c = (b << 30) | (b >> 2); b = a; a = temp; }
// 更新SHA1狀態。可能會溢位 h[0] += a; h[1] += b; h[2] += c; h[3] += d; h[4] += e;
return; } } }

沒有留言:

張貼留言