如何在php中加密/解密数据?
Posted
技术标签:
【中文标题】如何在php中加密/解密数据?【英文标题】:How to encrypt/decrypt data in php? 【发布时间】:2012-06-10 14:10:56 【问题描述】:我目前是一名学生,正在学习 php,我正在尝试用 PHP 对数据进行简单的加密/解密。我做了一些在线研究,其中一些非常令人困惑(至少对我而言)。
这是我想要做的:
我有一个由这些字段组成的表 (UserID,Fname,Lname,Email,Password)
我想要的是将所有字段加密然后解密(是否可以使用sha256
进行加密/解密,如果没有任何加密算法)
我想学习的另一件事是如何创建一个单一的方式hash(sha256)
结合良好的“盐”。
(基本上我只是想要一个简单的加密/解密实现,hash(sha256)+salt)
先生/女士,您的回答将有很大帮助,我们将不胜感激。谢谢++
【问题讨论】:
The horrors of Sha-1 encryption! php.net/manual/en/faq.passwords.php SHA 是哈希,不是加密。关键是哈希不能反转为原始数据(无论如何都不容易)。你可能想要mcrypt,或者如果它不可用,我会推荐phpseclib - 尽管重要的是要注意任何涉及大量低级数学的纯PHP实现都会很糟糕......这就是为什么我像 phpseclib,因为如果 mcrypt 可用,它首先使用 mcrypt,并且仅在最后的手段时才回退到 PHP 实现。 您通常不希望能够解密密码! 基本上你不应该考虑这个级别的加密,你应该考虑访问控制、机密性、完整性和身份验证。然后检查如何实现这一点,可能使用加密或安全散列。您可能需要阅读 PBKDF2 和 bcrypt/scrypt 以了解密码的安全散列等。 【参考方案1】: 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 段加密解密。谢谢【参考方案2】:我花了很长时间才弄清楚,如何在使用 openssl_decrypt()
时不获取 false
并让加密和解密工作。
// 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);
为了更好地理解发生了什么,请阅读:
http://blog.turret.io/the-missing-php-aes-encryption-example/ http://thefsb.tumblr.com/post/110749271235/using-opensslendecrypt-in-php-要生成 16 字节长的密钥,您可以使用:
$bytes = openssl_random_pseudo_bytes(16);
$hex = bin2hex($bytes);
要查看 openssl 的错误消息,您可以使用:echo openssl_error_string();
希望对您有所帮助。
【讨论】:
【参考方案3】:这里是一个使用 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;
【讨论】:
上面的代码为openssl_decrypt()
返回false
。请参阅***.com/q/41952509/1066234 由于 AES 等分组密码要求输入数据是块大小的精确倍数(AES 为 16 字节),因此需要填充。【参考方案4】:
前言
从你的表定义开始:
- UserID
- Fname
- Lname
- Email
- Password
- IV
以下是更改:
Fname
、Lname
和Email
字段将使用OpenSSL 提供的对称密码进行加密,
IV
字段将存储用于加密的initialisation vector。存储要求取决于所使用的密码和模式;稍后再详细介绍。
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 中,请考虑使用 BINARY
or 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 等哈希算法用于根据已知哈希值验证文件内容。它们经过了极大的优化,以尽可能快地进行此验证,同时仍保持准确。鉴于它们相对有限的输出空间,很容易构建一个具有已知密码及其各自哈希输出的数据库,即彩虹表。
在散列密码之前给密码添加盐会使彩虹表变得无用,但最近的硬件进步使蛮力查找成为一种可行的方法。这就是为什么您需要一种故意缓慢且根本无法优化的散列算法。它还应该能够为更快的硬件增加负载,而不会影响验证现有密码哈希以使其成为未来证明的能力。
目前有两种流行的选择:
-
PBKDF2(基于密码的密钥派生函数 v2)
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()
函数基于算法(Blowfish 为 $2y$
)、成本因子(在 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 引入了password hashing functions,您可以使用它来简化上述哈希方法:
$hash = password_hash($password, PASSWORD_BCRYPT, ['cost' => 13]);
并验证:
if (password_verify($given_password, $db_hash))
// password valid
另请参阅:password_hash()
、password_verify()
【讨论】:
我应该使用多长的长度来存储姓名、姓氏、电子邮件等以确保最安全? varbinary(???) 当然可以,但这取决于它的使用方式。如果你发布一个加密库,你不知道开发者将如何实现它。这就是为什么github.com/defuse/php-encryption 提供经过身份验证的对称密钥加密,并且不允许开发人员在不编辑其代码的情况下削弱它。 @Scott 很好,我添加了一个经过身份验证的加密示例;感谢您的推动:) +1 用于经过身份验证的加密。问题中没有足够的信息来说明这里不需要 AE。当然,SQL 流量经常通过具有未知安全属性的网络,从数据库到存储的流量也是如此。备份和复制也是如此。威胁模型是什么?问题没有说,做假设可能很危险。 我会使用:$iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length("AES-256-CBC"))
,而不是硬编码$iv_size = 16;
,以指示 iv 的大小与使用的密码之间的联系。您还可以稍微扩展一下pkcs7_pad()
/pkcs7_unpad()
的需要(或不需要),或者通过删除它们并使用“aes-256-ctr”来简化帖子。很棒的帖子@Ja͢ck【参考方案5】:
答题背景及说明
要理解这个问题,首先要了解什么是SHA256。 SHA256 是一种加密散列函数。加密哈希函数是一种单向函数,其输出是加密安全的。这意味着计算哈希很容易(相当于加密数据),但很难使用哈希获得原始输入(相当于解密数据)。由于使用加密哈希函数意味着解密在计算上是不可行的,因此您无法使用 SHA256 执行解密。
您要使用的是双向函数,但更具体地说,是块密码。允许对数据进行加密和解密的功能。 mcrypt_encrypt
和 mcrypt_decrypt
函数默认使用 Blowfish 算法。 PHP 对 mcrypt 的使用可以在这个manual 中找到。还存在用于选择 mcrypt 使用的密码的 cipher definitions 列表。可以在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 模式。这是不安全的,而且大多数时候实际上并不能真正帮助加密数据(而不仅仅是对其进行编码)。请参阅excellent Wikipedia article on the subject 了解更多信息。 最好不要使用mcrypt,它是废弃软件,多年未更新,不支持标准PKCS#7(née PKCS#5)填充,只有非标准空填充可以'甚至不能与二进制数据一起使用。 mcrypt 有许多出色的 bugs 可以追溯到 2003 年。请考虑使用 defuse,它正在维护并且是正确的。【参考方案6】:我想这个问题之前已经回答过了……但无论如何,如果你想加密/解密数据,你不能使用 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);
【讨论】:
就此而言,您也不应该使用欧洲央行。 密钥应该是随机字节,或者您应该使用安全密钥派生函数。 MCRYPT_RIJNDAEL_256 不是标准化函数,您应该使用 AES (MCRYPT_RIJNDAEL_128)以上是关于如何在php中加密/解密数据?的主要内容,如果未能解决你的问题,请参考以下文章