Страницы

Поиск по вопросам

Показаны сообщения с ярлыком aes. Показать все сообщения
Показаны сообщения с ярлыком aes. Показать все сообщения

пятница, 24 января 2020 г.

Почему AES расшифровывается именно таким образом?

#java #криптография #шифрование #aes


Использую шифрование AES с разными режимами (далее в примере режим "CBC"). Зашифровываю
следующий файл с текстом.

Вношу ошибку в получившийся шифрованный код примерно в середине текста.

При дешифровке получается следующий текст.


Собственно вопрос: почему неправильно расшифровывается именно этот отрезок текста,
ведь как я понял из документации AES (и как мне объяснял преподаватель) неправильно
расшифровываться должен весь текст, содержащийся после внесенной ошибки? Как сделать,
чтобы работало именно так?

Код класса (в "mode" передаю режим):

public class AesCipher {

    private static final String INIT_VECTOR = "ItIsOurBigSecret";

    // зашифрование
    static byte[] encrypt(byte[] secretKey, byte[] plainText, String mode) {

        try {
            if (!isKeyLengthValid(secretKey)) {
                throw new Exception("Длина ключа должна быть 128, 192 или 256 бит!");
            }

            IvParameterSpec ivParameterSpec = new IvParameterSpec(INIT_VECTOR.getBytes());
            SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey, "AES");

            Cipher cipher = Cipher.getInstance("AES/" + mode + "/PKCS5Padding");
            if (mode.equals("ECB"))
                cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
            else
                cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);

            return cipher.doFinal(plainText);
        } catch (Throwable cause) {
            System.out.print(cause.getMessage());
        }

        return null;
    }


    // дешифрование
    static byte[] decrypt(byte[] secretKey, byte[] cipherText, String mode) {

        try {
            if (!isKeyLengthValid(secretKey)) {
                throw new Exception("Длина ключа должна быть 128, 192 или 256 бит!");
            }

            IvParameterSpec ivParameterSpec = new IvParameterSpec(INIT_VECTOR.getBytes());
            SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey, "AES");

            Cipher cipher = Cipher.getInstance("AES/" + mode + "/PKCS5Padding");
            if (mode.equals("ECB"))
                cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
            else
                cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);

            return cipher.doFinal(cipherText);
        } catch (Throwable cause) {
            System.out.print(cause.getMessage());
            Controller.ShowMessage(cause.getMessage());
        }

        return null;
    }


    // проверка длины ключа
    private static boolean isKeyLengthValid(byte[] key) {
        return key.length == 16 || key.length == 24 || key.length == 32;
    }

    


Ответы

Ответ 1



Все правильно. AES обрабатывает данные блоками. В режиме CBC для расшифровки одного блока используется 2 блока шифротекста. Выглядит это приблизительно так: расшифрованый_блок = aes_decrypt(зашифрованый_блок) xor (предыдущий_зашифр_блок) По-этому ошибка в 1 байте шифротекста приведет к неправильной расшифровке только одного блока. Плюс один байт в другом блоке (из-за операции xor). Если вам нужно, что бы ломались все блоки после ошибки, можете использовать другой режим шифрования, например PCBC обладает таким свойством.

воскресенье, 15 декабря 2019 г.

Как реализовать шифрование данных на PHP

#php #javascript #aes #cryptojs


Вот пример на JS:

function decryptRandomNumber (pwdHash, encryptedRandomNumber) {
    var key = CryptoJS.enc.Utf8.parse(pwdHash);

    var encrypted = CryptoJS.enc.Hex.parse(encryptedRandomNumber.toUpperCase());

    var result = CryptoJS.AES.decrypt({
        ciphertext: encrypted
    }, key , {
        iv: iv,
        mode: CryptoJS.mode.CBC,
        padding: CryptoJS.pad.Pkcs7
    }).toString(CryptoJS.enc.Utf8);

    return result;
}

function encryptPwdHash (pwdHash, randomNumber) {

    var randomNumberMd5 = CryptoJS.MD5(randomNumber).toString().toUpperCase();
    var key = CryptoJS.enc.Utf8.parse(randomNumberMd5);
    var result = CryptoJS.AES.encrypt(CryptoJS.enc.Utf8.parse(pwdHash), key, {
        iv: iv,
        mode: CryptoJS.mode.CBC,
        padding: CryptoJS.pad.Pkcs7
    }).ciphertext;

    return result;
}
// пример на js - шифрование
 var password = '123456';
 var pwdHash = CryptoJS.MD5(password).toString().toUpperCase();
 var encriptResult = encryptPwdHash (pwdHash, '3456'); // 


Скриншот результата

     если привести в строку то f46492dc512a6df5cd7c6b9ee50e7cc44fb2337c1605726518d353ce800d5cc05d4d5540dd7674850079e785ab5f3b77

Нужно переписать на PHP.

Расшифровку смог сделать вот этой функцией 

 function aes128_cbc_decrypt($key, $data, $iv) {
     $data = pack("H*", $data);
     $iv = pack("H*", $iv);
     $dec = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $data, MCRYPT_MODE_CBC,   $iv);
     return $dec;
 }


Входящие параметры --> $key = strtoupper(md5($password));
 хеш пароля 
$data = '54BAA6158E81E1069EA2AB1C4F9D1F29';- хеш рандомного числа $iv = '00000000000000000000000000000000';
- вектор в виде 16 - ти ричной константы. 

Вот теперь нужно зашифровать обратно пароль с помощью случайного числа и отправить.
Написал функцию которая делает подобное:

function aes128_cbc_encript($key, $data, $iv) {
    $key = pack("H*", strtoupper(md5($key)));
    //var_dump($data);
    //$data = pack("H*", $data);
    $iv = pack("H*", $iv);
    //$enc = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $data, MCRYPT_MODE_CBC, $iv);
    $enc = openssl_encrypt($data,  'AES-128-CBC', $key, true, $iv);
    return bin2hex($enc);
 }


Пробую запускать 

$decript = '34345'; // расшифрованное рандомное число
$pswdHash = strtoupper(md5($password));

$encript = aes128_cbc_encript($decript, $pswdHash, '00000000000000000000000000000000');


На выходе получаю нечто - 72fc1b49db86787b749c3323421496af00933eb79a9b7d845a51f5ea3fefdf750800554f5fdf0c7d3d765fe7f6653da3

Такой шифр и должен быть по длинне и в 16 - ти разрядном виде, но он не правильный
и при отправке на сервер он не расшифровуется.

Вот пример функции на Oracle, которая на сервере делает расшифровку 

    Fc_Decrypt_Password_Hash(In_Encrypted_Password_Hash In Varchar2,
          In_Random_Number_Md5  In Varchar2) Return Varchar2 Is
          Raw_Random_Number_Md5 Raw(2000);
          Raw_Result            Raw(2000);
          Result                Varchar2(2000);
  Begin
      Raw_Random_Number_Md5 := Utl_I18n.String_To_Raw(Upper(In_Random_Number_Md5),
'AL32UTF8');
      Raw_Result := Dbms_Crypto.Decrypt(Typ => Dbms_Crypto.Aes_Cbc_Pkcs5, Src =>
Upper(In_Encrypted_Password_Hash), Key => Raw_Random_Number_Md5);
     Result := Utl_I18n.Raw_To_Char(Raw_Result, 'AL32UTF8');
   Return(Result);
  End;

    


Ответы

Ответ 1



Во-первых, если вы действительно хотите защитить обмен сообщениями - пользуйтесь готовыми решениями. Функция openssl_encrypt не использует весь $key, а только первые 16 байт, остальные отбрасываются (для AES-128). Хотя это только практический опыт, возможно это верно не для всех версий. В качестве $key у вас используется хэш md5 в 16-ричном формате, а это 32 байта. CryptoJS, использует весь ключ (или по крайней мере больше 16 байт). Ожидать, что результаты шифрований совпадут, не приходится. Функция mcrypt_encrypt не умеет Pkcs7. Она всегда выравнивает блок нулями (и это небезопасно). Хоть в вашем случае выравнивание и не требуется (длина исходного текста всегда делится на 16), по вышеуказанному стандарту все равно нужно добавить выравнивание, это будут 16 байт 0x10. Пример рабочего кода (практически повторяет ваш пример на js): function encrypt(){ $data=strtoupper(md5('123456')); $data.=str_repeat(chr(16), 16);// добавляем выравнивание $key=strtoupper(md5('3456')); $iv=hex2bin(str_repeat('00', 16)); $enc = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $data, MCRYPT_MODE_CBC, $iv); return bin2hex($enc); } var_dump(encrypt()); // -> string(96) "f46492dc512a6df5cd7c6b9ee50e7cc44fb2337c1605726518d353ce800d5cc05d4d5540dd7674850079e785ab5f3b77"

Ответ 2



Так как у вас стоит задача просто реализовать зашифрованный обмен данными между клиентом и сервером, предлагаю просто готовую реализацию. PHP библиотека: /** * Decrypt data from a CryptoJS json encoding string * * @param mixed $passphrase * @param mixed $jsonString * @return mixed */ function cryptoJsAesDecrypt($passphrase, $jsonString){ $jsondata = json_decode($jsonString, true); $salt = hex2bin($jsondata["s"]); $ct = base64_decode($jsondata["ct"]); $iv = hex2bin($jsondata["iv"]); $concatedPassphrase = $passphrase.$salt; $md5 = array(); $md5[0] = md5($concatedPassphrase, true); $result = $md5[0]; for ($i = 1; $i < 3; $i++) { $md5[$i] = md5($md5[$i - 1].$concatedPassphrase, true); $result .= $md5[$i]; } $key = substr($result, 0, 32); $data = openssl_decrypt($ct, 'aes-256-cbc', $key, true, $iv); return json_decode($data, true); } /** * Encrypt value to a cryptojs compatiable json encoding string * * @param mixed $passphrase * @param mixed $value * @return string */ function cryptoJsAesEncrypt($passphrase, $value){ $salt = openssl_random_pseudo_bytes(8); $salted = ''; $dx = ''; while (strlen($salted) < 48) { $dx = md5($dx.$passphrase.$salt, true); $salted .= $dx; } $key = substr($salted, 0, 32); $iv = substr($salted, 32,16); $encrypted_data = openssl_encrypt(json_encode($value), 'aes-256-cbc', $key, true, $iv); $data = array("ct" => base64_encode($encrypted_data), "iv" => bin2hex($iv), "s" => bin2hex($salt)); return json_encode($data); } Javascript библиотека: var CryptoJSAesJson = { stringify: function (cipherParams) { var j = {ct: cipherParams.ciphertext.toString(CryptoJS.enc.Base64)}; if (cipherParams.iv) j.iv = cipherParams.iv.toString(); if (cipherParams.salt) j.s = cipherParams.salt.toString(); return JSON.stringify(j); }, parse: function (jsonStr) { var j = JSON.parse(jsonStr); var cipherParams = CryptoJS.lib.CipherParams.create({ciphertext: CryptoJS.enc.Base64.parse(j.ct)}); if (j.iv) cipherParams.iv = CryptoJS.enc.Hex.parse(j.iv) if (j.s) cipherParams.salt = CryptoJS.enc.Hex.parse(j.s) return cipherParams; } } Пример на Javascript: var encrypted = CryptoJS.AES.encrypt(JSON.stringify("value to encrypt"), "my passphrase", {format: CryptoJSAesJson}).toString(); var decrypted = JSON.parse(CryptoJS.AES.decrypt(encrypted, "my passphrase", {format: CryptoJSAesJson}).toString(CryptoJS.enc.Utf8)); Пример на PHP $encrypted = cryptoJsAesEncrypt("my passphrase", "value to encrypt"); $decrypted = cryptoJsAesDecrypt("my passphrase", $encrypted); Взято остюда

Ответ 3



Переписал все полностью, если это оно, то прокомментирую после вашего подтверждения.
'; # вы зашифровали: 1152 # моя функция для шифрования в режиме CBC function dataEncrypt($plaintext, $key, $iv) { $ciphertext = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $plaintext, MCRYPT_MODE_CBC, pack('H*', $iv)); $ciphertext = strtoupper(bin2hex($ciphertext)); return $ciphertext; } # шифрую то, что вы шифровали ранне (число 1152) своей функцией echo dataEncrypt('1152', strtoupper(md5('123456')), '00000000000000000000000000000000') . '

'; # результат: C3364787440D1A610FB3B99C8C6D483C # расшифровываю результат работы своей функции: C3364787440D1A610FB3B99C8C6D483C с помощью вашей функцией aes128_cbc_decrypt echo aes128_cbc_decrypt(strtoupper(md5('123456')), 'C3364787440D1A610FB3B99C8C6D483C', '00000000000000000000000000000000') . '

'; # я зашифровал: 1152

вторник, 10 декабря 2019 г.

Обеспечит ли двойная шифровка AES-256 стойкость к взлому в 2 раза выше?

#криптография #aes


Хочу выяснить у людей разбирающихся в криптографии вот какой вопрос, ибо я не очень
силён в таких делах. 

Предположим у нас есть какие-то исходные данные размером 128 бит. Я их шифрую AES-256
с ключом key1, а затем, полученный шифр заново шифрую тем же AES-256, но для большей
безопасности уже другим ключом key2.

Значит ли это что стойкость зашифрованной информации к взлом стала выше в 2 раза?
    


Ответы

Ответ 1



В сущности - да, атакующему придётся подбирать оба ключа, чтобы расшифровать исходное сообщение. Т.к. изначально это было 2^256, и ещё добавили 2^256, в результате стало 2^512. Только не понятно, зачем Вам это нужно. Ключ длиной в 256 бит прямым перебором не берётся, нет смысла его увеличивать.

Ответ 2



Оба ключа отдельно перебирать не придётся, поскольку к двойному шифрованию можно применить атаку "метод встречи посередине". Краткое описание такое. У нас есть два текста - незашифрованный (PT) и зашифрованный (CT). Начинаем подбирать ключи методом полного перебора. Каждым ключом шифруем незашифрованный текст (получаем PTX) и расшифровываем зашифрованный текст (получаем CTX). Результаты запоминаем. Пробуем следующий ключ и так далее. На каждой итерации проверяем, совпадает ли у нас получившийся PTX с одним из ранее запомненных CTX или получившийся CTX с одним из ранее запомненных PTX. Если совпадение найдено, мы нашли сразу оба ключа. Таким образом, для нахождения двух 256-битных ключей методом полного перебора нам потребуется максимум 2^257 операций шифрования/дешифрования, а вовсе не 2^512.

четверг, 25 октября 2018 г.

Как реализовать шифрование данных на PHP

Вот пример на JS:
function decryptRandomNumber (pwdHash, encryptedRandomNumber) { var key = CryptoJS.enc.Utf8.parse(pwdHash);
var encrypted = CryptoJS.enc.Hex.parse(encryptedRandomNumber.toUpperCase());
var result = CryptoJS.AES.decrypt({ ciphertext: encrypted }, key , { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 }).toString(CryptoJS.enc.Utf8);
return result; }
function encryptPwdHash (pwdHash, randomNumber) {
var randomNumberMd5 = CryptoJS.MD5(randomNumber).toString().toUpperCase(); var key = CryptoJS.enc.Utf8.parse(randomNumberMd5); var result = CryptoJS.AES.encrypt(CryptoJS.enc.Utf8.parse(pwdHash), key, { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 }).ciphertext;
return result; } // пример на js - шифрование var password = '123456'; var pwdHash = CryptoJS.MD5(password).toString().toUpperCase(); var encriptResult = encryptPwdHash (pwdHash, '3456'); //
Скриншот результата если привести в строку то f46492dc512a6df5cd7c6b9ee50e7cc44fb2337c1605726518d353ce800d5cc05d4d5540dd7674850079e785ab5f3b77
Нужно переписать на PHP.
Расшифровку смог сделать вот этой функцией
function aes128_cbc_decrypt($key, $data, $iv) { $data = pack("H*", $data); $iv = pack("H*", $iv); $dec = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $data, MCRYPT_MODE_CBC, $iv); return $dec; }
Входящие параметры --> $key = strtoupper(md5($password)); хеш пароля $data = '54BAA6158E81E1069EA2AB1C4F9D1F29';- хеш рандомного числа $iv = '00000000000000000000000000000000'; - вектор в виде 16 - ти ричной константы.
Вот теперь нужно зашифровать обратно пароль с помощью случайного числа и отправить. Написал функцию которая делает подобное:
function aes128_cbc_encript($key, $data, $iv) { $key = pack("H*", strtoupper(md5($key))); //var_dump($data); //$data = pack("H*", $data); $iv = pack("H*", $iv); //$enc = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $data, MCRYPT_MODE_CBC, $iv); $enc = openssl_encrypt($data, 'AES-128-CBC', $key, true, $iv); return bin2hex($enc); }
Пробую запускать
$decript = '34345'; // расшифрованное рандомное число $pswdHash = strtoupper(md5($password));
$encript = aes128_cbc_encript($decript, $pswdHash, '00000000000000000000000000000000');
На выходе получаю нечто - 72fc1b49db86787b749c3323421496af00933eb79a9b7d845a51f5ea3fefdf750800554f5fdf0c7d3d765fe7f6653da3
Такой шифр и должен быть по длинне и в 16 - ти разрядном виде, но он не правильный и при отправке на сервер он не расшифровуется.
Вот пример функции на Oracle, которая на сервере делает расшифровку
Fc_Decrypt_Password_Hash(In_Encrypted_Password_Hash In Varchar2, In_Random_Number_Md5 In Varchar2) Return Varchar2 Is Raw_Random_Number_Md5 Raw(2000); Raw_Result Raw(2000); Result Varchar2(2000); Begin Raw_Random_Number_Md5 := Utl_I18n.String_To_Raw(Upper(In_Random_Number_Md5), 'AL32UTF8'); Raw_Result := Dbms_Crypto.Decrypt(Typ => Dbms_Crypto.Aes_Cbc_Pkcs5, Src => Upper(In_Encrypted_Password_Hash), Key => Raw_Random_Number_Md5); Result := Utl_I18n.Raw_To_Char(Raw_Result, 'AL32UTF8'); Return(Result); End;


Ответ

Во-первых, если вы действительно хотите защитить обмен сообщениями - пользуйтесь готовыми решениями.
Функция openssl_encrypt не использует весь $key, а только первые 16 байт, остальные отбрасываются (для AES-128). Хотя это только практический опыт, возможно это верно не для всех версий. В качестве $key у вас используется хэш md5 в 16-ричном формате, а это 32 байта. CryptoJS, использует весь ключ (или по крайней мере больше 16 байт).
Ожидать, что результаты шифрований совпадут, не приходится.
Функция mcrypt_encrypt не умеет Pkcs7. Она всегда выравнивает блок нулями (и это небезопасно). Хоть в вашем случае выравнивание и не требуется (длина исходного текста всегда делится на 16), по вышеуказанному стандарту все равно нужно добавить выравнивание, это будут 16 байт 0x10
Пример рабочего кода (практически повторяет ваш пример на js):
function encrypt(){ $data=strtoupper(md5('123456')); $data.=str_repeat(chr(16), 16);// добавляем выравнивание $key=strtoupper(md5('3456')); $iv=hex2bin(str_repeat('00', 16)); $enc = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $data, MCRYPT_MODE_CBC, $iv); return bin2hex($enc); } var_dump(encrypt()); // -> string(96) "f46492dc512a6df5cd7c6b9ee50e7cc44fb2337c1605726518d353ce800d5cc05d4d5540dd7674850079e785ab5f3b77"

понедельник, 15 октября 2018 г.

Обеспечит ли двойная шифровка AES-256 стойкость к взлому в 2 раза выше?

Хочу выяснить у людей разбирающихся в криптографии вот какой вопрос, ибо я не очень силён в таких делах.
Предположим у нас есть какие-то исходные данные размером 128 бит. Я их шифрую AES-256 с ключом key1, а затем, полученный шифр заново шифрую тем же AES-256, но для большей безопасности уже другим ключом key2.
Значит ли это что стойкость зашифрованной информации к взлом стала выше в 2 раза?


Ответ

В сущности - да, атакующему придётся подбирать оба ключа, чтобы расшифровать исходное сообщение. Т.к. изначально это было 2^256, и ещё добавили 2^256, в результате стало 2^512. Только не понятно, зачем Вам это нужно. Ключ длиной в 256 бит прямым перебором не берётся, нет смысла его увеличивать.