Как зашифровать / расшифровать данные в php?

110

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

Вот что я пытаюсь сделать:

У меня есть таблица, состоящая из этих полей (UserID, Fname, Lname, Email, Password)

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

Еще одна вещь, которую я хочу узнать, - это как создать одностороннюю работу в hash(sha256)сочетании с хорошей «солью». (По сути, я просто хочу иметь простую реализацию шифрования / дешифрования, hash(sha256)+salt) сэр / мэм, ваши ответы будут очень полезны и будут очень признательны. Спасибо ++

Рэндел Рамирес
источник
3
Ужасы шифрования Sha-1!
Нафтали, он же Нил,
9
SHA - это хеш, а не шифрование. Ключевым моментом является то, что хэш нельзя вернуть к исходным данным (в любом случае, это непросто). Вероятно, вам нужен mcrypt, или, если он недоступен, я бы порекомендовал phpseclib - хотя важно отметить, что любая реализация на чистом PHP всего, что связано с большим количеством низкоуровневой математики, будет sloooooowww ... Вот почему мне нравится phpseclib, потому что он сначала использует mcrypt, если он доступен, и возвращается к реализациям PHP только в крайнем случае.
DaveRandom 06
7
Обычно вы не хотите иметь возможность расшифровывать пароль!
Ja͢ck 06
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
В этом отношении вам также не следует использовать ECB.
Maarten Bodewes
7
Ключи должны быть случайными байтами, или вы должны использовать безопасную функцию получения ключа.
Maarten Bodewes
4
MCRYPT_RIJNDAEL_256 не является стандартизированной функцией, вам следует использовать AES (MCRYPT_RIJNDAEL_128)
Маартен Бодевес,
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);
подладанник
источник
В этом отношении вам также не следует использовать ECB.
Maarten Bodewes
Ключи должны быть случайными байтами, или вы должны использовать безопасную функцию получения ключа.
Maarten Bodewes
4
Никогда не используйте режим ECB. Это небезопасно, и большую часть времени на самом деле не помогает фактическое шифрование данных (а не просто их кодирование). См. Отличную статью в Википедии по этому поводу для получения дополнительной информации.
Holger Just
1
Лучше не использовать mcrypt, это заброшенное программное обеспечение, не обновлялось годами и не поддерживает стандартное заполнение PKCS # 7 (урожденное PKCS # 5), только нестандартное заполнение нулями, которое невозможно использовать даже с двоичными данными . В mcrypt было много неурегулированных ошибок, относящихся к 2003 году. Вместо этого рассмотрите возможность использования defuse , он поддерживается и является правильным.
zaph
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-адрес, вам необходимо указать URL-адрес строки:

    $encrypted = urlencode($encrypted);

Чтобы лучше понять, что происходит, прочтите:

Чтобы сгенерировать ключи длиной 16 байт, вы можете использовать:

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

Чтобы увидеть сообщения об ошибках openssl, вы можете использовать: echo openssl_error_string();

Надеюсь, это поможет.

Кай Ноак
источник