Як зашифрувати / розшифрувати дані в php?


110

Зараз я студент і вивчаю PHP, я намагаюся зробити простий шифр / розшифрування даних у PHP. Я зробив деякі інтернет-дослідження, і деякі з них були досить заплутаними (принаймні для мене).

Ось що я намагаюся зробити:

У мене є таблиця, що складається з цих полів (UserID, Ім'я, Ім'я, Електронна пошта, Пароль)

Що я хочу мати - це зашифрувати всі поля та потім розшифрувати (чи можна використовувати sha256для шифрування / дешифрування, якщо не будь-який алгоритм шифрування)

Ще одне, що я хочу навчитися - це створити єдиний спосіб у hash(sha256)поєднанні з хорошою «сіллю». (В основному я просто хочу просту реалізацію шифрування / дешифрування, hash(sha256)+salt) сер / пані, ваші відповіді будуть дуже корисними і дуже вдячні. Дякую ++




9
SHA - хеш, а не шифрування. Ключовим моментом є те, що хеш не можна повернути до вихідних даних (не так легко, у будь-якому випадку). Ви, мабуть, хочете mcrypt або якщо він недоступний, я б рекомендував phpseclib - хоча важливо зазначити, що будь-яка реалізація чистого PHP будь-чого, що включає багато математики низького рівня, буде sloooooowww ... Ось чому мені подобається phpseclib, тому що він спочатку використовує mcrypt, якщо він доступний, і лише в крайньому випадку відновлюється до реалізації PHP.
DaveRandom

7
Зазвичай ви не хочете мати можливість розшифрувати пароль!
Ja͢ck

1
В основному ви не повинні думати про шифрування на цьому рівні, ви повинні думати про контроль доступу, конфіденційність, цілісність та автентифікацію. Після цього перевірте, як ви можете цього досягти, можливо, використовуючи шифрування або захищене хешування. Ви можете прочитати в PBKDF2 та bcrypt / scrypt, щоб зрозуміти безпечне хешування паролів тощо.
Maarten Bodewes

Відповіді:


289

Передмова

Починаючи з визначення таблиці:

- UserID
- Fname
- Lname
- Email
- Password
- IV

Ось такі зміни:

  1. Поля Fname, Lnameі Emailбудуть зашифровані з допомогою симетричного шифру, що надається OpenSSL ,
  2. IVПоле буде зберігати вектор ініціалізації , використовуваний для шифрування. Вимоги до зберігання залежать від шифру та використовуваного режиму; докладніше про це пізніше.
  3. PasswordПоле буде хешіруются з використанням односторонню хеш пароля,

Шифрування

Шифр і режим

Вибір найкращого шифрувального шифру та режиму виходить за межі цієї відповіді, але остаточний вибір впливає на розмір як ключа шифрування, так і вектора ініціалізації; для цієї посади ми будемо використовувати AES-256-CBC, який має фіксований розмір блоку 16 байт і розмір ключа або 16, 24 або 32 байти.

Ключ шифрування

Хороший ключ шифрування - це двійковий крап, який генерується з надійного генератора випадкових чисел. Наступний приклад рекомендується (> = 5.3):

$key_size = 32; // 256 bits
$encryption_key = openssl_random_pseudo_bytes($key_size, $strong);
// $strong will be true if the key is crypto safe

Це можна зробити один або кілька разів (якщо ви хочете створити ланцюжок ключів шифрування). Зберігайте їх як можна більш приватними.

IV

Вектор ініціалізації додає випадковість шифруванню і необхідний для режиму CBC. Ці значення слід в ідеалі використовувати лише один раз (технічно один раз за ключ шифрування), тому оновлення будь-якої частини рядка має відновити її.

Надається функція, яка допоможе вам генерувати IV:

$iv_size = 16; // 128 bits
$iv = openssl_random_pseudo_bytes($iv_size, $strong);

Приклад

Давайте зашифруємо поле імен, використовуючи раніше $encryption_keyі $iv; для цього нам доведеться перекласти наші дані до розміру блоку:

function pkcs7_pad($data, $size)
{
    $length = $size - strlen($data) % $size;
    return $data . str_repeat(chr($length), $length);
}

$name = 'Jack';
$enc_name = openssl_encrypt(
    pkcs7_pad($name, 16), // padded data
    'AES-256-CBC',        // cipher and mode
    $encryption_key,      // secret key
    0,                    // options (not used)
    $iv                   // initialisation vector
);

Вимоги до зберігання

Зашифрований вихід, як і IV, є двійковим; зберігання цих значень у базі даних може бути здійснено за допомогою визначених типів стовпців, таких як BINARYабо VARBINARY.

Вихідне значення, як і IV, є двійковим; щоб зберегти ці значення в MySQL, розглянемо використання BINARYабоVARBINARY стовпці. Якщо це не варіант, ви також можете перетворити бінарні дані в текстове подання, використовуючи base64_encode()або bin2hex(), для цього потрібно від 33% до 100% більше місця для зберігання.

Розшифрування

Розшифровка збережених значень схожа:

function pkcs7_unpad($data)
{
    return substr($data, 0, -ord($data[strlen($data) - 1]));
}

$row = $result->fetch(PDO::FETCH_ASSOC); // read from database result
// $enc_name = base64_decode($row['Name']);
// $enc_name = hex2bin($row['Name']);
$enc_name = $row['Name'];
// $iv = base64_decode($row['IV']);
// $iv = hex2bin($row['IV']);
$iv = $row['IV'];

$name = pkcs7_unpad(openssl_decrypt(
    $enc_name,
    'AES-256-CBC',
    $encryption_key,
    0,
    $iv
));

Підтверджене шифрування

Ви можете додатково поліпшити цілісність згенерованого тексту шифру, додавши підпис, створений секретним ключем (відмінним від ключа шифрування) та текстом шифру. Перш ніж розшифрувати текст шифру, підпис спочатку перевіряється (бажано методом порівняння з постійним часом).

Приклад

// generate once, keep safe
$auth_key = openssl_random_pseudo_bytes(32, $strong);

// authentication
$auth = hash_hmac('sha256', $enc_name, $auth_key, true);
$auth_enc_name = $auth . $enc_name;

// verification
$auth = substr($auth_enc_name, 0, 32);
$enc_name = substr($auth_enc_name, 32);
$actual_auth = hash_hmac('sha256', $enc_name, $auth_key, true);

if (hash_equals($auth, $actual_auth)) {
    // perform decryption
}

Дивитися також: hash_equals()

Хешинг

Зберігання реверсивного пароля у вашій базі даних слід уникати якомога більше; ви хочете лише перевірити пароль, а не знати його вміст. Якщо користувач втрачає свій пароль, краще дозволити його скинути, а не надсилати їм оригінальний (переконайтеся, що скидання пароля може бути здійснено лише протягом обмеженого часу).

Застосування хеш-функції - це одностороння операція; після цього його можна безпечно використовувати для перевірки, не розкриваючи вихідні дані; що стосується паролів, метод грубої сили - це здійсненний підхід до їх розкриття через його порівняно невелику довжину та неправильний вибір пароля багатьох людей.

Алгоритми хешування, такі як MD5 або SHA1, були створені для перевірки вмісту файлу на рівні відомого хеш-значення. Вони значно оптимізовані для того, щоб зробити цю перевірку якомога швидшою, але при цьому залишаючись точною. Зважаючи на їх відносно обмежений вихідний простір, було легко створити базу даних з відомими паролями та їх відповідними хеш-виводами, таблицями веселок.

Додавання солі до пароля до хешування, це зробить райдужну таблицю марною, але останні вдосконалення обладнання зробили грубу силу пошуку життєздатним підходом. Ось чому вам потрібен алгоритм хешування, який навмисно повільний і просто неможливий для оптимізації. Він також повинен мати можливість збільшити навантаження на більш швидке обладнання, не впливаючи на можливість перевірки існуючих хешей паролів, щоб зробити його подальшим доказом.

Наразі доступні два популярні варіанти:

  1. PBKDF2 (Функція виведення ключа на основі пароля v2)
  2. bcrypt (він же Blowfish)

У цій відповіді буде використаний приклад з bcrypt.

Покоління

Хеш паролів може генеруватися так:

$password = 'my password';
$random = openssl_random_pseudo_bytes(18);
$salt = sprintf('$2y$%02d$%s',
    13, // 2^n cost factor
    substr(strtr(base64_encode($random), '+', '.'), 0, 22)
);

$hash = crypt($password, $salt);

Сіль генерується з, openssl_random_pseudo_bytes()щоб утворити випадкову крапку даних, яка потім проходить через base64_encode()і strtr()відповідає необхідному алфавіту [A-Za-z0-9/.].

У crypt()функції виконує хешування на основі алгоритму ( $2y$для Blowfish), фактор вартості (коефіцієнт 13 займає приблизно 0.40s на машині 3GHz) і солі 22 символів.

Перевірка

Після вибору рядка, що містить інформацію про користувача, ви перевіряєте пароль таким чином:

$given_password = $_POST['password']; // the submitted password
$db_hash = $row['Password']; // field with the password hash

$given_hash = crypt($given_password, $db_hash);

if (isEqual($given_hash, $db_hash)) {
    // user password verified
}

// constant time string compare
function isEqual($str1, $str2)
{
    $n1 = strlen($str1);
    if (strlen($str2) != $n1) {
        return false;
    }
    for ($i = 0, $diff = 0; $i != $n1; ++$i) {
        $diff |= ord($str1[$i]) ^ ord($str2[$i]);
    }
    return !$diff;
}

Щоб підтвердити пароль, ви дзвоните crypt()ще раз, але передаєте раніше обчислений хеш як значення солі. Повернене значення дає той самий хеш, якщо даний пароль відповідає хешу. Щоб перевірити хеш, часто рекомендується використовувати функцію порівняння в постійному часі, щоб уникнути атак часу.

Хешування паролів із PHP 5.5

PHP 5.5 представив функції хешування паролів, які можна використовувати для спрощення описаного вище способу хешування:

$hash = password_hash($password, PASSWORD_BCRYPT, ['cost' => 13]);

І перевірка:

if (password_verify($given_password, $db_hash)) {
    // password valid
}

Дивіться також: password_hash(),password_verify()


Яку довжину я повинен використовувати для збереження імені, прізвища, електронної пошти тощо для найбільш безпечної пари? varbinary (???)
BentCoder

2
Звичайно, але це залежить від того, яким чином він використовується. Якщо ви публікуєте бібліотеку шифрування, ви не знаєте, як розробники будуть її реалізовувати. Ось чому github.com/defuse/php-encryption забезпечує автентифіковане шифрування симетричного ключа і не дозволяє розробникам послаблювати його, не редагуючи його код.
Скотт Арчішевський

2
@Scott Дуже добре, я додав приклад автентифікованого шифрування; дякую за поштовх :)
Ja͢ck

1
+1 для автентифікованого шифрування. У запитанні недостатньо інформації, щоб сказати, що AE тут не потрібен. Безумовно, трафік SQL часто проходить по мережі з невідомими властивостями безпеки, як і трафік від бази даних до сховища. Резервні копії та реплікація теж. Яка модель загрози? Питання не говорить, і це може бути небезпечно робити припущення.
Джейсон Орендорф

1
Замість жорсткого кодування $iv_size = 16;я б використав: $iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length("AES-256-CBC"))для того, щоб вказати зв’язок між розміром iv для використання з шифром, що використовується. Ви також можете трохи розширити необхідність (або ні) в pkcs7_pad()/ pkcs7_unpad()або просто спростити публікацію, позбувшись їх та скориставшись "aes-256-ctr". Відмінний пост @ Ja͢ck
Патрік Аллаерт

24

Я думаю, на це вже було відповіді ... але все одно, якщо ви хочете зашифрувати / розшифрувати дані, ви не можете використовувати SHA256

//Key
$key = 'SuperSecretKey';

//To Encrypt:
$encrypted = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $key, 'I want to encrypt this', MCRYPT_MODE_ECB);

//To Decrypt:
$decrypted = mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $key, $encrypted, MCRYPT_MODE_ECB);

7
З цього приводу не слід використовувати і ЄЦБ.
Maarten Bodewes

7
Ключі повинні бути випадковими байтами, або ви повинні використовувати функцію виведення захищеного ключа.
Maarten Bodewes

4
MCRYPT_RIJNDAEL_256 не є стандартизованою функцією, вам слід скористатися AES (MCRYPT_RIJNDAEL_128)
Maarten Bodewes

14

Фон відповіді та пояснення

Щоб зрозуміти це питання, спершу потрібно зрозуміти, що таке SHA256. SHA256 - це функція криптографічного хешу . Функція криптографічного хешу - це одностороння функція, вихід якої криптографічно захищений. Це означає, що легко обчислити хеш (еквівалентно шифруванню даних), але важко отримати вихідний вхід за допомогою хеша (еквівалентно дешифруванню даних). Оскільки використання криптографічної хеш-функції означає, що дешифрування є обчислювально нездійсненним, тому ви не можете виконувати дешифрування за допомогою SHA256.

Те, що ви хочете використовувати, - це двостороння функція, але конкретніше, блок-шифр . Функція, яка дозволяє здійснювати як шифрування, так і дешифрування даних. Функції mcrypt_encryptта mcrypt_decryptза замовчуванням використовують алгоритм Blowfish. Використання mcrypt PHP можна знайти в цьому посібнику . Перелік визначень шифрів для вибору використання шифру mcrypt також існує. Вікі про Blowfish можна знайти у Вікіпедії . Блоковий шифр шифрує вхід в блоки відомого розміру і положення відомим ключем, щоб згодом дані були розшифровані за допомогою ключа. Це те, що SHA256 не може вам надати.

Код

$key = 'ThisIsTheCipherKey';

$ciphertext = mcrypt_encrypt(MCRYPT_BLOWFISH, $key, 'This is plaintext.', MCRYPT_MODE_CFB);

$plaintext = mcrypt_decrypt(MCRYPT_BLOWFISH, $key, $encrypted, MCRYPT_MODE_CFB);

З цього приводу не слід використовувати і ЄЦБ.
Maarten Bodewes

Ключі повинні бути випадковими байтами, або ви повинні використовувати функцію виведення захищеного ключа.
Maarten Bodewes

4
Ніколи не використовуйте режим ECB. Це небезпечно, і більшість часу насправді не допомагає фактично шифрувати дані (а не просто кодувати їх). Додаткову інформацію див. У чудовій статті Вікіпедії на цю тему .
Холгер Просто

1
Найкраще не використовувати mcrypt, це занедбане програмне забезпечення, не оновлювалося роками і не підтримує стандартну PKCS № 7 (née PKCS № 5) прокладку, лише нестандартні нульові прокладки, які неможливо використовувати навіть з двійковими даними . У mcrypt було багато виправлених помилок, починаючи з 2003 року. Замість цього розглядайте питання про використання обезжирення , воно підтримується і є правильним.
заф

9

Ось приклад з використанням openssl_encrypt

//Encryption:
$textToEncrypt = "My Text to Encrypt";
$encryptionMethod = "AES-256-CBC";
$secretHash = "encryptionhash";
$iv = mcrypt_create_iv(16, MCRYPT_RAND);
$encryptedText = openssl_encrypt($textToEncrypt,$encryptionMethod,$secretHash, 0, $iv);

//Decryption:
$decryptedText = openssl_decrypt($encryptedText, $encryptionMethod, $secretHash, 0, $iv);
print "My Decrypted Text: ". $decryptedText;

2
Замість цього mcrypt_create_iv()я б використав:, openssl_random_pseudo_bytes(openssl_cipher_iv_length($encryptionMethod))таким чином методологія працює для будь-якого значення $ encryptionMethod і використовує лише розширення openssl.
Патрік Аллаерт

Код вище повертається falseдля openssl_decrypt(). Див. Stackoverflow.com/q/41952509/1066234 Оскільки блокові шифри, такі як AES, вимагають, щоб вхідні дані були точними кратними розмірами блоку (16-байт для AES).
Кай Ноак

6
     function my_simple_crypt( $string, $action = 'e' ) {
        // you may change these values to your own
        $secret_key = 'my_simple_secret_key';
        $secret_iv = 'my_simple_secret_iv';

        $output = false;
        $encrypt_method = "AES-256-CBC";
        $key = hash( 'sha256', $secret_key );
        $iv = substr( hash( 'sha256', $secret_iv ), 0, 16 );

        if( $action == 'e' ) {
            $output = base64_encode( openssl_encrypt( $string, $encrypt_method, $key, 0, $iv ) );
        }
        else if( $action == 'd' ){
            $output = openssl_decrypt( base64_decode( $string ), $encrypt_method, $key, 0, $iv );
        }

        return $output;
    }

дуже просто ! Я використовую його для шифрування-розшифрування сегмента URL-адреси. Спасибі
Махбуб Тіто

0

Мені знадобилося досить багато часу, щоб зрозуміти, як не отримати falseпри користуванніopenssl_decrypt() та шифрувати та розшифровувати.

    // cryptographic key of a binary string 16 bytes long (because AES-128 has a key size of 16 bytes)
    $encryption_key = '58adf8c78efef9570c447295008e2e6e'; // example
    $iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length('aes-256-cbc'));
    $encrypted = openssl_encrypt($plaintext, 'aes-256-cbc', $encryption_key, OPENSSL_RAW_DATA, $iv);
    $encrypted = $encrypted . ':' . base64_encode($iv);

    // decrypt to get again $plaintext
    $parts = explode(':', $encrypted);
    $decrypted = openssl_decrypt($parts[0], 'aes-256-cbc', $encryption_key, OPENSSL_RAW_DATA, base64_decode($parts[1])); 

Якщо ви хочете передати зашифрований рядок через URL-адресу, вам потрібно перекодувати рядок:

    $encrypted = urlencode($encrypted);

Щоб краще зрозуміти, що відбувається, читайте:

Для генерування довгих клавіш 16 байт можна використовувати:

    $bytes = openssl_random_pseudo_bytes(16);
    $hex = bin2hex($bytes);

Щоб побачити повідомлення про помилки openssl, ви можете скористатися: echo openssl_error_string();

Сподіваюся, що це допомагає.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.