118

我目前是一名学生,正在学习 PHP,我正在尝试对 PHP 中的数据进行简单的加密/解密。我做了一些在线研究,其中一些非常令人困惑(至少对我而言)。

这是我正在尝试做的事情:

我有一个由这些字段组成的表(用户 ID、Fname、Lname、Email、Password)

我想要的是对所有字段进行加密然后解密(是否可以sha256用于加密/解密,如果没有任何加密算法)

我想学习的另一件事是如何创建一种hash(sha256)结合良好“盐”的单一方式。(基本上我只想简单地实现加密/解密,hash(sha256)+salt) 先生/女士,您的回答会很有帮助,非常感谢。谢谢++

4

6 回答 6

298

前言

从您的表定义开始:

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

以下是更改:

  1. 字段Fname,Lname和将使用OpenSSLEmail提供的对称密码进行加密,
  2. IV字段将存储用于加密的初始化向量。存储要求取决于所使用的密码和模式;稍后再详细介绍。
  3. Password字段将使用单向密码散列进行散列,

加密

密码和模式

选择最佳加密密码和模式超出了此答案的范围,但最终选择会影响加密密钥和初始化向量的大小;对于这篇文章,我们将使用 AES-256-CBC,它具有 16 字节的固定块大小和 16、24 或 32 字节的密钥大小。

加密密钥

一个好的加密密钥是从可靠的随机数生成器生成的二进制 blob。建议使用以下示例 (>= 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

这可以执行一次或多次(如果您希望创建一个加密密钥链)。尽可能保持这些隐私。

初始化向量为加密增加了随机性,并且是 CBC 模式所必需的。理想情况下,这些值应该只使用一次(技术上每个加密密钥一次),因此对行的任何部分的更新都应该重新生成它。

提供了一个函数来帮助您生成 IV:

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

例子

让我们加密名称字段,使用前面的$encryption_keyand $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 中,请考虑使用BINARYorVARBINARY列。如果这不是一个选项,您还可以使用base64_encode()or将二进制数据转换为文本表示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(又名河豚)

此答案将使用 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)、成本因子(在 3GHz 机器上 13 的因子大约需要 0.40 秒)和 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()

于 2012-06-08T08:00:35.483 回答
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);
于 2012-06-06T14:37:22.390 回答
14

答案背景和解释

要理解这个问题,首先要了解什么是SHA256。SHA256 是一种加密散列函数。加密哈希函数是一种单向函数,其输出是加密安全的。这意味着计算哈希很容易(相当于加密数据),但很难使用哈希得到原始输入(相当于解密数据)。由于使用加密哈希函数意味着解密在计算上是不可行的,因此您无法使用 SHA256 执行解密。

您要使用的是双向功能,但更具体地说,是Block Cipher。允许对数据进行加密和解密的功能。这些函数默认使用 Blowfish 算法mcrypt_encryptmcrypt_decryptPHP 对 mcrypt 的使用可以在本手册中找到。还存在用于选择密码 mcrypt 使用的密码定义列表。可以在Wikipedia上找到有关 Blowfish 的 wiki 。分组密码使用已知密钥对已知大小和位置的块中的输入进行加密,以便以后可以使用该密钥对数据进行解密。这是 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);
于 2012-06-07T05:16:42.867 回答
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;
于 2016-05-19T20:28:31.750 回答
9
     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;
    }
于 2018-04-20T07:12:05.707 回答
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 传递加密字符串,则需要对字符串进行 urlencode:

    $encrypted = urlencode($encrypted);

为了更好地理解发生了什么,请阅读:

要生成 16 字节长的密钥,您可以使用:

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

要查看 openssl 的错误消息,您可以使用:echo openssl_error_string();

希望有帮助。

于 2017-09-08T21:26:30.203 回答