【问题标题】:Proper way to generate key from password to openssl_encrypt in PHP在 PHP 中从密码生成密钥到 openssl_encrypt 的正确方法
【发布时间】:2020-10-13 13:39:10
【问题描述】:

我不是安全专家,但我必须在 PHP 中使用 openssl 加密和解密文件,用户可以定义加密密码。我使用 aes-256-gcm 作为密码方法。

我生成安全密钥的实际代码是:

$password = '12345'; //Entered by the end user

$salt = openssl_random_pseudo_bytes(16);
$key = openssl_pbkdf2($password, $salt, 32, 10000, 'sha256');

我将生成的盐存储在加密文件的前缀中。密码不存储,只有用户知道。

解决方案有效。 我的问题是这个解决方案在今天是否足够好和足够安全?

【问题讨论】:

  • 当您最终将盐与加密输出一起存储在那里时,您总是想知道盐的安全性或意义。
  • pbkdf2 不是最先进的,但它仍然相当不错,被认为是 Good Enough™。使用不可预测的盐。我不确定openssl_random_pseudo_bytes 的调用是做什么的。一个重要的参数是迭代次数。 10,000 有点低。一般来说,这个数字应该尽可能大,以保证良好的用户体验和可接受的性能。
  • 什么会比 pbkdf2 更好?
  • @IncredibleHat 请参阅security.stackexchange.com/questions/17421/how-to-store-salt,了解加密密码哈希的安全优势。多项式的答案很好地解释了为什么这种方法仍然可以防止某些类型的攻击(例如彩虹表),即使攻击者知道盐。
  • @Topaco:感谢您的链接。但是,根据 PHP 手册,我不能将自己的盐与 Argon2 和 password_hash 一起使用。关于如何在 PHP 中实现链接站点的功能,这值得另一个问题。

标签: php security encryption openssl aes


【解决方案1】:

正如@Topaco 所说,“设置盐”选项在 PHP/openssl 中被禁用。幸运的是,在许多现代 PHP 版本中还有另一个可用的加密库 - libsodium(钠)。在这里,我们找到了必要的方法来以可重复的方式散列密码,因为您需要它来进行加密。

此处描述了“sodium_crypto_pwhash”方法:https://www.php.net/manual/de/function.sodium-crypto-pwhash.php。 您应该检查是否启用了 libsodium(最简单的方法是 phpinfo()):

sodium
sodium support => enabled
libsodium headers version => 1.0.17
libsodium library version => 1.0.17

现在您可以对密码进行哈希处理了。请注意,我使用 固定盐(完全不安全) 仅用于演示目的。与“salt”相同,需要存储“opslimit”和“memlimit”变量以获得可重现(解密)的密码。

<?php
echo 'php version: ' . PHP_VERSION . ' openssl version: '
    . OPENSSL_VERSION_TEXT . ' sodium: '
    . SODIUM_LIBRARY_VERSION . '<br>';
echo 'key derivation with PHP-libsodium' . '<br>';
echo 'https://stackoverflow.com/questions/62535675/proper-way-to-generate-key-from-password-to-openssl-encrypt-in-php' . '<br>';
$password = '12345'; //Entered by the end user
//$salt = openssl_random_pseudo_bytes(16); // openssl-randombytes
//$salt = random_bytes(SODIUM_CRYPTO_PWHASH_SALTBYTES); // libsodium randombytes
// ### just for demonstration purpose I'm using a static salt
$salt = "1234567890123456";
$key = sodium_crypto_pwhash(
    32,
    $password,
    $salt,
    SODIUM_CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE,
    // or SODIUM_CRYPTO_PWHASH_OPSLIMIT_MODERATE  or SODIUM_CRYPTO_PWHASH_OPSLIMIT_SENSITIVE
    SODIUM_CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE,
    // or SODIUM_CRYPTO_PWHASH_MEMLIMIT_MODERATE or SODIUM_CRYPTO_PWHASH_MEMLIMIT_SENSITIVE
    SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13
);
echo PHP_EOL . '<br>' . 'key: ' . bin2hex($key) . PHP_EOL . '<br>';
?>

可重现的结果:

php version: 7.4.6 openssl version: OpenSSL 1.1.1g 21 Apr 2020 sodium: 1.0.17
key derivation with PHP-libsodium
https://stackoverflow.com/questions/62535675/proper-way-to-generate-key-from-password-to-openssl-encrypt-in-php

key: 8a2a973c147a18fe2a60f7a2c4513e75141a12be0bdc793243fa12b067a3acbe

编辑:使用 PHP/libsodium、Argon2ID 作为 KDF 和 XCHACHA20POLY1305 身份验证加密的小文件的完整文件加密

关于钠/libsodium 的简短示例的良好来源是 https://www.zend.com/blog/libsodium-and-php-encrypt。 libsodium 的“介绍页面”确实有一些关于如何使用它的示例 - 以下代码取自这一面 (https://www.php.net/manual/en/intro.sodium.php)。没有检查或错误处理,您只需要一个文件“plaintext.txt”。

加密.php:

<?php
// https://www.php.net/manual/de/intro.sodium.php
$password = 'password';
$input_file = 'plaintext.txt';
$encrypted_file = 'encryption1.enc';
$chunk_size = 4096;

$alg = SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13;
$opslimit = SODIUM_CRYPTO_PWHASH_OPSLIMIT_MODERATE;
$memlimit = SODIUM_CRYPTO_PWHASH_MEMLIMIT_MODERATE;
$salt = random_bytes(SODIUM_CRYPTO_PWHASH_SALTBYTES);

$secret_key = sodium_crypto_pwhash(SODIUM_CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_KEYBYTES,
    $password, $salt, $opslimit, $memlimit, $alg);

$fd_in = fopen($input_file, 'rb');
$fd_out = fopen($encrypted_file, 'wb');

fwrite($fd_out, pack('C', $alg));
fwrite($fd_out, pack('P', $opslimit));
fwrite($fd_out, pack('P', $memlimit));
fwrite($fd_out, $salt);

list($stream, $header) = sodium_crypto_secretstream_xchacha20poly1305_init_push($secret_key);

fwrite($fd_out, $header);

$tag = SODIUM_CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_MESSAGE;
do {
    $chunk = fread($fd_in, $chunk_size);
    if (stream_get_meta_data($fd_in)['unread_bytes'] <= 0) {
        $tag = SODIUM_CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_FINAL;
    }
    $encrypted_chunk = sodium_crypto_secretstream_xchacha20poly1305_push($stream, $chunk, '', $tag);
    fwrite($fd_out, $encrypted_chunk);
} while ($tag !== SODIUM_CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_FINAL);

fclose($fd_out);
fclose($fd_in);
?>

解密.php

<?php
// https://www.php.net/manual/de/intro.sodium.php
$encrypted_file = 'encryption1.enc';
$decrypted_file = 'decrypt1.txt';
$password = 'password';
$fd_in = fopen($encrypted_file, 'rb');
$fd_out = fopen($decrypted_file, 'wb');
$chunk_size = 4096;
$alg = unpack('C', fread($fd_in, 1))[1];
$opslimit = unpack('P', fread($fd_in, 8))[1];
$memlimit = unpack('P', fread($fd_in, 8))[1];
$salt = fread($fd_in, SODIUM_CRYPTO_PWHASH_SALTBYTES);

$header = fread($fd_in, SODIUM_CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_HEADERBYTES);

$secret_key = sodium_crypto_pwhash(SODIUM_CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_KEYBYTES,
    $password, $salt, $opslimit, $memlimit, $alg);

$stream = sodium_crypto_secretstream_xchacha20poly1305_init_pull($header, $secret_key);

$tag = SODIUM_CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_MESSAGE;
while (stream_get_meta_data($fd_in)['unread_bytes'] > 0 &&
    $tag !== SODIUM_CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_TAG_FINAL) {
    $chunk = fread($fd_in, $chunk_size + SODIUM_CRYPTO_SECRETSTREAM_XCHACHA20POLY1305_ABYTES);
    $res = sodium_crypto_secretstream_xchacha20poly1305_pull($stream, $chunk);
    if ($res === FALSE) {
        break;
    }
    list($decrypted_chunk, $tag) = $res;
    fwrite($fd_out, $decrypted_chunk);
}
$ok = stream_get_meta_data($fd_in)['unread_bytes'] <= 0;

fclose($fd_out);
fclose($fd_in);

if (!$ok) {
    die('Invalid/corrupted input');
}
?>

【讨论】:

  • 谢谢!也许整个加密过程应该从 openSSL 切换到 Sodium。太糟糕了,文档还很不完整。
【解决方案2】:

我找到了以下代码,其中包含一个使用 libsodium 和密码安全加密/解密的类

https://gist.github.com/bcremer/858e4a3c279b276751335dc38fc162c5

【讨论】:

    猜你喜欢
    • 2011-02-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-04-10
    • 2018-05-31
    • 2010-12-02
    • 2021-12-24
    • 2015-05-13
    相关资源
    最近更新 更多