Расшифровка строки, зашифрованной на C#, с помощью класса RijndaelManaged с использованием PHP

Вот некоторый код C# (я немного изменил его, чтобы изменить некоторые жестко закодированные в нем значения):

public static string Decrypt(string InputFile)
{
    string outstr = null;

    if ((InputFile != null))
    {
        if (File.Exists(InputFile))
        {

            FileStream fsIn = null;
            CryptoStream cstream = null;

            try
            {
                byte[] _b = { 94, 120, 102, 204, 199, 246, 243, 104, 185, 115, 76, 48, 220, 182, 112, 101 };
                fsIn = File.Open(InputFile, FileMode.Open, System.IO.FileAccess.Read);

                SymmetricAlgorithm symm = new RijndaelManaged();
                PasswordDeriveBytes Key = new PasswordDeriveBytes(System.Environment.MachineName, System.Text.Encoding.Default.GetBytes("G:MFX62rlABW:IUYAX(i"));
                ICryptoTransform transform = symm.CreateDecryptor(Key.GetBytes(24), _b);
                cstream = new CryptoStream(fsIn, transform, CryptoStreamMode.Read);

                StreamReader sr = new StreamReader(cstream);

                char[] buff = new char[1000];
                sr.Read(buff, 0, 1000);

                outstr = new string(buff);
            }
            finally
            {
                if (cstream != null)
                {
                    cstream.Close();
                }
                if (fsIn != null)
                {
                    fsIn.Close();
                }
            }
        }
    }
    return outstr;
}

Мне нужно придумать функцию, чтобы сделать то же самое в PHP. Имейте в виду, что я не писал код C# и не могу его модифицировать, поэтому, даже если он плохой, я застрял с ним. Я искал повсюду и нашел кусочки вокруг, но пока ничего не работает. Во всех примерах, которые я нашел, используется mcrypt, который в наши дни не одобряется, но я, вероятно, застрял на нем. Затем я нашел следующий пост с полезной информацией: Rewrite Код шифрования Rijndael 256 C# в PHP

Похоже, что класс PasswordDeriveBytes является ключом к этому. Я создал следующий код PHP, чтобы попытаться расшифровать:

function PBKDF1($pass,$salt,$count,$dklen) { 
    $t = $pass.$salt;
    //echo 'S||P: '.bin2hex($t).'<br/>';
    $t = sha1($t, true); 
    //echo 'T1:' . bin2hex($t) . '<br/>';
    for($i=2; $i <= $count; $i++) { 
        $t = sha1($t, true); 
        //echo 'T'.$i.':' . bin2hex($t) . '<br/>';
    } 
    $t = substr($t,0,$dklen); 
    return $t;      
}

$input = 'Ry5WdjGS8rpA9eA+iQ3aPw==';
$key   = "win7x64";
$salt  = implode(unpack('C*', "G:MFX62rlABW:IUYAX(i"));

$salt   = pack("H*", $salt); 
$it     = 1000; 
$keyLen = 16; 
$key    = PBKDF1($key, $salt, $it, $keyLen);
$key    = bin2hex(substr($key, 0, 8));
$iv     = bin2hex(substr($key, 8, 8));

echo trim(mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $key, base64_decode($input), MCRYPT_MODE_CBC, $iv));

Вы заметите, что для того, что я считаю System.Environment.MachineName, я ввел фиксированное значение на данный момент, которое является именем компьютера, на котором я работаю, поэтому должно быть эквивалентно тому, что делает код C #. Кроме этого, я заметил, что использование MCRYPT_RIJNDAEL_256 не работает, оно выдает ошибку «Параметр IV должен быть таким же длинным, как размер блока». Если я использую MCRYPT_RIJNDAEL_128, я не получаю эту ошибку, но расшифровка все равно не выполняется. Я предполагаю, что мне не хватает части для массива байтов _b, который используется функцией CreateDecryptor, я понятия не имею, где это должно вписываться. Любая помощь приветствуется.

ОБНОВЛЕНИЕ

Это решение, которое стало возможным благодаря ответу, отмеченному как правильный. Обратите внимание, что код функции PBKDF1 не мой, ссылка на него была указана в ответе.

function PBKDF1($pass, $salt, $count, $cb) {
  static $base;
  static $extra;
  static $extracount= 0;
  static $hashno;
  static $state = 0;

  if ($state == 0)
  {
    $hashno = 0;
    $state = 1;

    $key = $pass . $salt;
    $base = sha1($key, true);
    for($i = 2; $i < $count; $i++)
    {
      $base = sha1($base, true);
    }
  }

  $result = "";

  if ($extracount > 0)
  {
    $rlen = strlen($extra) - $extracount;
    if ($rlen >= $cb)
    {
      $result = substr($extra, $extracount, $cb);
      if ($rlen > $cb)
      {
        $extracount += $cb;
      }
      else
      {
        $extra = null;
        $extracount = 0;
      }
      return $result;
    }
    $result = substr($extra, $rlen, $rlen);
  }

  $current = "";
  $clen = 0;
  $remain = $cb - strlen($result);
  while ($remain > $clen)
  {
    if ($hashno == 0)
    {
      $current = sha1($base, true);
    }
    else if ($hashno < 1000)
    {
      $n = sprintf("%d", $hashno);
      $tmp = $n . $base;
      $current .= sha1($tmp, true);
    }
    $hashno++;
    $clen = strlen($current);     
  }

  // $current now holds at least as many bytes as we need
  $result .= substr($current, 0, $remain);

  // Save any left over bytes for any future requests
  if ($clen > $remain)
  {
    $extra = $current;
    $extracount = $remain;
  }

  return $result; 
}

$input  = 'base 64 encoded string to decrypt here';
$key    = strtoupper(gethostname());
$salt   = 'G:MFX62rlABW:IUYAX(i';
$it     = 100; 
$keyLen = 24;
$key    = PBKDF1($key, $salt, $it, $keyLen);
$iv     = implode(array_map('chr', [94, 120, 102, 204, 199, 246, 243, 104, 185, 115, 76, 48, 220, 182, 112, 101]));

person Rocket04    schedule 09.04.2016    source источник


Ответы (1)


_b — это статическое значение, которое используется в качестве IV (CreateDecryptor принимает ключ и параметр IV). Поскольку он имеет длину 16 байт, это означает, что вы используете Rijndael-128 или более известный AES.

Key.GetBytes(24) предполагает, что получен 24-байтовый ключ, а не 16-байтовый.

Убедись в том, что

  • System.Text.Encoding.Default эквивалентно implode(unpack('C*', ...,
  • Значение по умолчанию для итераций PasswordDeriveBytes равно 1000,
  • Значение по умолчанию для хэша PasswordDeriveBytes — SHA-1.

Проблемы с безопасностью:

  • PBKDF1 устарел, а PBKDF2 ненамного лучше. Используйте современные алгоритмы получения ключей, такие как Argon2 или scrypt.
  • IV должен быть выбран случайным образом для достижения семантической безопасности. Он не обязательно должен быть секретным, поэтому его можно отправить вместе с зашифрованным текстом.
  • Растягивание ключа путем кодирования его в шестнадцатеричный формат не обеспечивает никакой безопасности (не используйте bin2hex).
  • Зашифрованный текст не аутентифицируется, что означает, что вы не можете обнаружить (злонамеренное) манипулирование зашифрованными сообщениями. Используйте шифрование, а затем MAC.
person Artjom B.    schedule 09.04.2016
comment
Итак, я думаю, что у меня есть $iv прямо сейчас. Моя проблема в ключе. Я думаю, что тот факт, что ключ составляет 24 байта, является проблемой, исходя из того, что здесь сказано: stackoverflow.com/questions/3505453/ PasswordDeriveBytes совместим с PBKDF1 только тогда и только тогда, когда запрашиваемая длина составляет 20 или менее байтов, максимальный вывод PBKDF1 с SHA1. PasswordDeriveBytes использует проприетарную схему, которая является небезопасной и даже непоследовательной (например, запрос 32, а затем 16 байт дает другой результат, чем запрос 48 байт). - person Rocket04; 09.04.2016
comment
Я бы только подумал, что вы облажались, что вам придется реализовать PasswordDeriveBytes на PHP, что не займет много времени, поскольку исходный код находится в свободном доступе. Или вы можете копнуть немного глубже в своей любимой поисковой системе. - person Artjom B.; 09.04.2016
comment
Не смотрите дальше. Этот фрагмент работает должным образом. Код C#: ideone.com/h5lMj1 и код PHP: ideone.com/o0YYm0 Имейте в виду, что по умолчанию IterationCount равен 100, а не 1000. - person Artjom B.; 09.04.2016
comment
Вы единственный величайший человек на земле. Оды и сонеты надо писать, восхваляя твое великолепие. Большое спасибо, я отмечу этот ответ. - person Rocket04; 09.04.2016
comment
Не используйте PBKDF1. - person Scott Arciszewski; 12.04.2016