Перш ніж робити щось далі, намагайтеся зрозуміти різницю між шифруванням та автентифікацією , і чому ви, ймовірно, хочете автентифіковане шифрування, а не просто шифрування .
Для реалізації автентифікованого шифрування потрібно зашифрувати, а потім MAC. Порядок шифрування та автентифікації дуже важливий! Одна з існуючих відповідей на це питання допустила цю помилку; як і багато бібліотек криптовалют, написаних на PHP.
Вам слід уникати впровадження власної криптографії , а натомість використовувати захищену бібліотеку, написану та переглянуту експертами з криптографії.
Оновлення: PHP 7.2 тепер забезпечує лібсодіум ! Для кращої безпеки оновіть ваші системи для використання PHP 7.2 або новішої версії та лише дотримуйтесь порад лібсодіуму в цій відповіді.
Використовуйте libsodium, якщо у вас є доступ до PECL (або natry_compat, якщо ви хочете libsodium без PECL); в іншому випадку ...
Використовуйте defuse / php-шифрування ; не котиться власна криптовалюта!
Обидві бібліотеки, зв'язані вище, дозволяють легко та безболісно реалізувати автентифіковане шифрування у власних бібліотеках.
Якщо ви все ще хочете написати та розгорнути власну бібліотеку криптовалют, проти звичайної мудрості кожного експерта з криптографії в Інтернеті, це кроки, які вам доведеться зробити.
Шифрування:
- Шифруйте за допомогою AES в режимі CTR. Ви також можете використовувати GCM (що усуває потребу в окремому MAC). Крім того, ChaCha20 і Salsa20 (надані лібсодіумом ) є потоковими шифрами і не потребують спеціальних режимах.
- Якщо ви не вибрали GCM вище, вам слід автентифікувати шифротекст за допомогою HMAC-SHA-256 (або, для шифрів потоку, Poly1305 - більшість ліцензійних API це робить для вас). MAC повинен охоплювати як IV, так і шифротекст!
Розшифровка:
- Якщо не використовуються Poly1305 або GCM, перерахуйте MAC шифротексту та порівняйте його з MAC, який був надісланий за допомогою
hash_equals()
. Якщо це не вдається, перервіть.
- Розшифруйте повідомлення.
Інші міркування щодо дизайну:
- Ніколи нічого не стискайте. Шифротекст не стискається; стиснення прямого тексту перед шифруванням може призвести до витоку інформації (наприклад, ЗЛОЧИНЕННЯ та НАРУШЕННЯ на TLS).
- Переконайтеся, що ви використовуєте
mb_strlen()
та mb_substr()
, використовуючи '8bit'
режим набору символів, для запобігання mbstring.func_overload
проблемам.
- IV повинні генеруватися за допомогою CSPRNG ; Якщо ви використовуєте
mcrypt_create_iv()
, НЕ ВИКОРИСТОВУЙТЕMCRYPT_RAND
!
- Якщо ви не використовуєте конструкцію AEAD, ЗАВЖДИ шифруйте потім MAC!
bin2hex()
, base64_encode()
і т.д. може просочити інформацію про ваші ключі шифрування через таймінги кешу. Уникайте їх, якщо можливо.
Навіть якщо ви будете дотримуватися наведених тут порад, багато чого може піти не так з криптографією. Завжди попросіть експерта з криптографії переглянути вашу реалізацію. Якщо вам не пощастило бути особистими друзями зі студентом криптографії у вашому місцевому університеті, ви завжди можете спробувати форум обміну криптовалютою для отримання консультації.
Якщо вам потрібен професійний аналіз вашої реалізації, ви завжди можете найняти авторитетну команду консультантів з питань безпеки, щоб переглянути ваш код криптографії PHP (розкриття: мій роботодавець).
Важливо: Коли не використовувати шифрування
Не шифруйте паролі . Натомістьви хочете їх хеш , використовуючи один із цих алгоритмів хешування:
Ніколи не використовуйте хеш-функції загального призначення (MD5, SHA256) для зберігання паролів.
Не шифруйте параметри URL-адрес . Це неправильний інструмент для роботи.
Приклад шифрування рядків PHP з Libsodium
Якщо ви перебуваєте на PHP <7,2 або іншим способом не встановлено лібонатрій, ви можете використовувати натрію_компат для досягнення того ж результату (хоча і повільніше).
<?php
declare(strict_types=1);
/**
* Encrypt a message
*
* @param string $message - message to encrypt
* @param string $key - encryption key
* @return string
* @throws RangeException
*/
function safeEncrypt(string $message, string $key): string
{
if (mb_strlen($key, '8bit') !== SODIUM_CRYPTO_SECRETBOX_KEYBYTES) {
throw new RangeException('Key is not the correct size (must be 32 bytes).');
}
$nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
$cipher = base64_encode(
$nonce.
sodium_crypto_secretbox(
$message,
$nonce,
$key
)
);
sodium_memzero($message);
sodium_memzero($key);
return $cipher;
}
/**
* Decrypt a message
*
* @param string $encrypted - message encrypted with safeEncrypt()
* @param string $key - encryption key
* @return string
* @throws Exception
*/
function safeDecrypt(string $encrypted, string $key): string
{
$decoded = base64_decode($encrypted);
$nonce = mb_substr($decoded, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, '8bit');
$ciphertext = mb_substr($decoded, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, null, '8bit');
$plain = sodium_crypto_secretbox_open(
$ciphertext,
$nonce,
$key
);
if (!is_string($plain)) {
throw new Exception('Invalid MAC');
}
sodium_memzero($ciphertext);
sodium_memzero($key);
return $plain;
}
Потім перевірити це:
<?php
// This refers to the previous code block.
require "safeCrypto.php";
// Do this once then store it somehow:
$key = random_bytes(SODIUM_CRYPTO_SECRETBOX_KEYBYTES);
$message = 'We are all living in a yellow submarine';
$ciphertext = safeEncrypt($message, $key);
$plaintext = safeDecrypt($ciphertext, $key);
var_dump($ciphertext);
var_dump($plaintext);
Halite - лібсодіум виготовляється легше
Один з проектів, над якими я працював, - це бібліотека шифрування під назвою Halite , яка має на меті зробити лібсодіум простішим та інтуїтивнішим.
<?php
use \ParagonIE\Halite\KeyFactory;
use \ParagonIE\Halite\Symmetric\Crypto as SymmetricCrypto;
// Generate a new random symmetric-key encryption key. You're going to want to store this:
$key = new KeyFactory::generateEncryptionKey();
// To save your encryption key:
KeyFactory::save($key, '/path/to/secret.key');
// To load it again:
$loadedkey = KeyFactory::loadEncryptionKey('/path/to/secret.key');
$message = 'We are all living in a yellow submarine';
$ciphertext = SymmetricCrypto::encrypt($message, $key);
$plaintext = SymmetricCrypto::decrypt($ciphertext, $key);
var_dump($ciphertext);
var_dump($plaintext);
Усі основні криптографії обробляються лібсодієм.
Приклад з дефузією / php-шифруванням
<?php
/**
* This requires https://github.com/defuse/php-encryption
* php composer.phar require defuse/php-encryption
*/
use Defuse\Crypto\Crypto;
use Defuse\Crypto\Key;
require "vendor/autoload.php";
// Do this once then store it somehow:
$key = Key::createNewRandomKey();
$message = 'We are all living in a yellow submarine';
$ciphertext = Crypto::encrypt($message, $key);
$plaintext = Crypto::decrypt($ciphertext, $key);
var_dump($ciphertext);
var_dump($plaintext);
Примітка : Crypto::encrypt()
повертає шістнадцятковий вихід.
Керування ключами шифрування
Якщо ви спокусилися скористатися "паролем", зупиніться прямо зараз. Вам потрібен випадковий 128-розрядний ключ шифрування, а не пам'ятний для людини пароль.
Ви можете зберігати ключ шифрування для довготривалого використання так:
$storeMe = bin2hex($key);
І, за запитом, ви можете отримати його так:
$key = hex2bin($storeMe);
Я настійно рекомендую просто зберігати випадково створений ключ для тривалого використання замість будь-якого пароля в якості ключа (або для отримання ключа).
Якщо ви використовуєте бібліотеку Defuse:
"Але я дуже хочу використовувати пароль."
Це погана ідея, але добре, ось як це зробити безпечно.
По-перше, генеруйте випадковий ключ і зберігайте його в константі.
/**
* Replace this with your own salt!
* Use bin2hex() then add \x before every 2 hex characters, like so:
*/
define('MY_PBKDF2_SALT', "\x2d\xb7\x68\x1a\x28\x15\xbe\x06\x33\xa0\x7e\x0e\x8f\x79\xd5\xdf");
Зауважте, що ви додаєте додаткову роботу і можете просто використовувати цю константу як ключ і заощадити собі багато душі!
Потім використовуйте PBKDF2 (як-от так), щоб отримати відповідний ключ шифрування з вашого пароля, а не шифрувати безпосередньо паролем.
/**
* Get an AES key from a static password and a secret salt
*
* @param string $password Your weak password here
* @param int $keysize Number of bytes in encryption key
*/
function getKeyFromPassword($password, $keysize = 16)
{
return hash_pbkdf2(
'sha256',
$password,
MY_PBKDF2_SALT,
100000, // Number of iterations
$keysize,
true
);
}
Не використовуйте лише 16-символьний пароль. Ваш ключ шифрування буде комічно зламаний.