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

Ran*_*rez 108 php security encryption cryptography encryption-symmetric

我现在是一名学生,我正在学习PHP,我正在尝试在PHP中对数据进行简单的加密/解密.我做了一些在线研究,其中一些很混乱(至少对我而言).

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

我有一个由这些字段组成的表(UserID,Fname,Lname,Email,Password)

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

我想要学习的另一件事是如何创造一种hash(sha256)与良好"盐"相结合的方式.(基本上我只是希望有一个简单的加密/解密实现,hash(sha256)+salt) 先生/女士,你的答案会有很大的帮助,非常感谢.谢谢++

Ja͢*_*͢ck 285

前言

从您的表定义开始:

- UserID
- Fname
- Lname
- Email
- Password
- IV
Run Code Online (Sandbox Code Playgroud)

以下是更改:

  1. 的字段Fname,LnameEmail将使用对称密码,通过提供待加密的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
Run Code Online (Sandbox Code Playgroud)

这可以进行一次或多次(如果您希望创建一系列加密密钥).保持这些尽可能私密.

IV

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

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

$iv_size = 16; // 128 bits
$iv = openssl_random_pseudo_bytes($iv_size, $strong);
Run Code Online (Sandbox Code Playgroud)

让我们来加密name字段中,使用的是早期$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
);
Run Code Online (Sandbox Code Playgroud)

存储要求

加密输出,如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
));
Run Code Online (Sandbox Code Playgroud)

认证加密

您可以通过附加从密钥(不同于加密密钥)和密文生成的签名来进一步提高生成的密文的完整性.在密码文本被解密之前,首先验证签名(优选地使用恒定时间比较方法).

// 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
}
Run Code Online (Sandbox Code Playgroud)

也可以看看: 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);
Run Code Online (Sandbox Code Playgroud)

生成盐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;
}
Run Code Online (Sandbox Code Playgroud)

要验证密码,请crypt()再次调用,但是将先前计算的散列作为salt值传递.如果给定的密码与哈希匹配,则返回值产生相同的哈希值.为了验证散列,通常建议使用恒定时间比较功能来避免定时攻击.

使用PHP 5.5进行密码散列

PHP 5.5引入了密码散列函数,可用于简化上述散列方法:

$hash = password_hash($password, PASSWORD_BCRYPT, ['cost' => 13]);
Run Code Online (Sandbox Code Playgroud)

并验证:

if (password_verify($given_password, $db_hash)) {
    // password valid
}
Run Code Online (Sandbox Code Playgroud)

另见:password_hash(),password_verify()

  • 当然,但这取决于它的使用方式.如果您发布加密库,则不知道开发人员将如何实现它.这就是为什么https://github.com/defuse/php-encryption提供经过身份验证的对称密钥加密,并且不允许开发人员在不编辑代码的情况下削弱它. (2认同)
  • @Scott很好,我添加了一个经过身份验证的加密示例; 谢谢你的推动:) (2认同)

rom*_*omo 22

我认为之前已经回答了......但无论如何,如果你想加密/解密数据,你不能使用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);
Run Code Online (Sandbox Code Playgroud)

  • 就此而言,您也不应该使用ECB. (7认同)
  • 密钥应该是随机字节,或者您应该使用安全密钥派生函数. (7认同)
  • MCRYPT_RIJNDAEL_256不是标准化函数,您应该使用AES(MCRYPT_RIJNDAEL_128) (4认同)

cyt*_*nus 14

回答背景和解释

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

你想要使用的是一个双向函数,但更具体地说,是一个分组密码.一种允许加密和解密数据的功能.功能mcrypt_encryptmcrypt_decrypt默认使用Blowfish算法.PHP的mcrypt用法可以在本手册中找到.还存在用于选择密码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);
Run Code Online (Sandbox Code Playgroud)

  • **永远不要**使用ECB模式.它是不安全的,大多数时候实际上并没有真正帮助加密数据(而不仅仅是编码).有关详细信息,请参阅[关于该主题的优秀维基百科文章](http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Electronic_codebook_.28ECB.29). (4认同)

小智 8

以下是使用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;
Run Code Online (Sandbox Code Playgroud)

  • 而不是`mcrypt_create_iv()`,我会使用:`openssl_random_pseudo_bytes(openssl_cipher_iv_length($ encryptionMethod))`,这种方法适用于$ encryptionMethod的任何值,并且只使用openssl扩展. (2认同)

gau*_*ini 8

     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;
    }
Run Code Online (Sandbox Code Playgroud)