如何在php中加密/解密数据?


110

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

这是我想要做的:

我有一个包含这些字段(用户ID,Fname,Lname,Email,Password)的表

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

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




9
SHA是哈希,不是加密。关键是哈希不能逆转为原始数据(无论如何都不容易)。您可能想要mcrypt,或者如果它不可用,我会推荐phpseclib-尽管要特别注意的是,任何涉及大量低级数学的纯PHP实现都会是sloooooowww ...这就是为什么我喜欢phpseclib,因为如果可用,它将首先使用mcrypt,并且最后只能使用PHP实现。
DaveRandom 2012年

7
您通常不希望能够解密密码!
千斤顶

1
基本上,您不应在此级别上考虑加密,而应考虑访问控制,机密性,完整性和身份验证。之后,检查可能如何使用加密或安全哈希来实现此目标。您可能需要阅读PBKDF2和bcrypt / scrypt来了解密码等的安全哈希。
Maarten Bodewes 2012年

Answers:


289

前言

从表定义开始:

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

更改如下:

  1. 的字段FnameLnameEmail将使用对称密码,通过提供待加密的OpenSSL
  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

可以执行一次或多次(如果您希望创建一串加密密钥)。尽可能将它们保密。

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中,请考虑使用BINARYVARBINARY列。如果这不是一个选项,则还可以使用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之类的哈希算法,以针对已知哈希值验证文件内容。它们经过了极大的优化,可以使验证速度尽可能快,同时仍保持准确。由于它们的输出空间相对有限,因此很容易使用已知的密码及其各自的哈希输出(即Rainbow表)来构建数据库。

在对哈希值进行哈希运算之前,先在密码中添加盐,这将使彩虹表变得毫无用处,但是最近的硬件改进使蛮力查找成为一种可行的方法。这就是为什么您需要一种故意慢且根本无法优化的哈希算法。它还应该能够增加更快硬件的负载,而又不影响验证现有密码哈希值以使其成为未来的能力。

当前有两个流行的选择:

  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.40s)和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()再次调用,但将先前计算的哈希值作为salt值传递。如果给定的密码与哈希匹配,则返回值将产生相同的哈希。为了验证哈希,通常建议使用恒定时间比较功能以避免定时攻击。

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提供经过身份验证的对称密钥加密,并且不允许开发人员在不编辑其代码的情况下削弱它的原因。
Scott Arciszewski 2015年

2
@Scott很好,我添加了一个经过身份验证的加密示例;感谢推:)
杰克

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”来简化帖子。伟大的职位@杰克
帕特里克Allaert

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 2012年

7
密钥应为随机字节,或者应使用安全密钥派生功能。
Maarten Bodewes 2012年

4
MCRYPT_RIJNDAEL_256不是标准化函数,您应该使用AES(MCRYPT_RIJNDAEL_128)
Maarten Bodewes 2012年

14

回答背景和说明

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

您想要使用的是双向函数,更具体地说,是Block Cipher。允许对数据进行加密和解密的功能。功能mcrypt_encryptmcrypt_decrypt默认使用Blowfish算法。在本手册中可以找到PHP对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);

您也不应使用ECB。
Maarten Bodewes 2012年

密钥应为随机字节,或者应使用安全密钥派生功能。
Maarten Bodewes 2012年

4
永远不要使用ECB模式。它是不安全的,并且大多数时候实际上并没有真正帮助实际加密数据(而不仅仅是对数据进行编码)。有关更多信息,请参见有关该主题出色的Wikipedia文章
Holger Just

1
最好不要使用mcrypt,它是一种废弃软件,已经多年未更新,并且不支持标准PKCS#7(néePKCS#5)填充,仅支持甚至不能用于二进制数据的非标准null填充。mcrypt的许多错误可以追溯到2003年。.而是考虑使用defuse,它得到了维护并且是正确的。
扎夫

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扩展。
Patrick Allaert '16

上述返回代码falseopenssl_decrypt()。请参阅stackoverflow.com/q/41952509/1066234, 因为诸如AES之类的块密码要求输入数据必须是块大小(对于AES为16字节)的精确倍数。
Kai Noack

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传递加密的字符串,则需要对字符串进行urlencode:

    $encrypted = urlencode($encrypted);

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

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

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

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

希望有帮助。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.