如何加密和解密PHP字符串?

276

我的意思是:

Original String + Salt or Key --> Encrypted String
Encrypted String + Salt or Key --> Decrypted (Original String)

也许类似于以下内容:
"hello world!" + "ABCD1234" --> Encrypt --> "2a2ffa8f13220befbe30819047e23b2c" (may be, for e.g)
"2a2ffa8f13220befbe30819047e23b2c" --> Decrypt with "ABCD1234" --> "hello world!"
  • 在PHP中,您如何做到这一点?

尝试使用Crypt_Blowfish,但它对我没起作用。


44
他不想要哈希,他想要对称加密(比如AES),他只是不知道它叫什么。(现在他知道了 :)) - Patashu
需要多安全? - user557846
3
@夏期劇場,对称加密不需要“盐”,而需要使用密钥。密钥必须保密。盐可以公开而不会损害安全性(只要每个人的盐都不同),这是用于哈希密码的术语。 - Patashu
2
你需要一把Salt(私钥),一个公钥以及像AES-256这样的加密算法:http://wpy.me/blog/15-encrypt-and-decrypt-data-in-php-using-aes-256 - wappy
8
该博客文章的作者在对称加密的背景下使用了一些完全没有意义的术语。AES中没有公钥,也没有盐。只有一个密钥,必须保密。在某些操作模式下可能需要一个IV,但是IV不是盐(根据模式,它可能有非常不同的要求),不需要保密,而实际的加密密钥绝对不能公开。公钥/私钥适用于非对称加密,但与AES无关。 - cpast
10个回答

474
在继续之前,先了解加密和认证的区别,以及为什么你可能需要使用认证加密而不是只有加密
要实现认证加密,你需要先进行加密再进行消息认证(加密和认证的顺序非常重要!)。这个问题的一个现有答案犯了这个错误;许多用PHP编写的加密库也是如此。
你应该避免自己实现加密,而是使用由加密专家编写并审核的安全库。

更新:PHP 7.2现在提供libsodium!为了最佳安全性,请将系统更新到使用PHP 7.2或更高版本,并且只按照本答案中的libsodium建议操作。

如果您有PECL访问权限,请使用libsodium(或sodium_compat,如果您想使用没有PECL的libsodium); 否则...

使用defuse/php-encryption; 不要自己编写加密算法!

上面链接的两个库使得在你自己的库中实现认证加密变得简单和无痛。

如果您仍然想编写和部署自己的加密库,违反了互联网上每位密码学专家的传统智慧,那么您将需要执行以下步骤。

加密:

  1. 使用CTR模式下的AES进行加密。您也可以使用GCM(这样就不需要单独使用MAC了)。此外,ChaCha20和Salsa20(由libsodium提供)是流密码,不需要特殊模式。
  2. 除非您选择了上面的GCM,否则您应该使用HMAC-SHA-256对密文进行身份验证(对于流密码,则使用Poly1305 -- 大多数libsodium API会为您执行此操作)。 MAC应涵盖IV以及密文!

解密:

  1. 除非使用了Poly1305或GCM,否则重新计算密文的MAC,并使用hash_equals()将其与发送的MAC进行比较。如果失败,则中止。
  2. 解密消息。

其他设计考虑事项:

  1. 永远不要压缩任何内容。密文不可压缩;在加密之前压缩明文可能会导致信息泄露(例如TLS上的CRIME和BREACH攻击)。
  2. 确保使用mb_strlen()mb_substr(),使用'8bit'字符集模式以防止mbstring.func_overload问题。
  3. 应使用CSPRNG生成IV;如果您使用的是mcrypt_create_iv()请勿使用MCRYPT_RAND
  4. 除非您使用的是AEAD结构,否则始终先加密再进行MAC!
  5. bin2hex()base64_encode()等可能通过缓存时间泄漏有关加密密钥的信息。如有可能,请避免使用它们。
即使您遵循此处给出的建议,加密仍然可能出现许多问题。一定要让加密专家审核您的实现。如果您没有幸运地与本地大学的加密学生成为个人朋友,您可以尝试Cryptography Stack Exchange论坛寻求建议。

如果您需要对您的实现进行专业分析,您始终可以雇用一支值得信赖的安全顾问团队来审查您的PHP加密代码(披露:我的雇主)。

重要提示:何时不使用加密

不要加密密码。相反,您应该使用以下其中一种密码哈希算法:hash

不要使用通用哈希函数(如MD5、SHA256)来存储密码。

不要加密URL参数 这是错误的工具。

使用Libsodium的PHP字符串加密示例

如果您使用的是PHP < 7.2或没有安装libsodium,则可以使用sodium_compat来实现相同的结果(尽管速度较慢)。

<?php
declare(strict_types=1);

/**
 * Encrypt a message
 * 
 * @param string $message - message to encrypt
 * @param string $key - encryption key
 * @return string
 * @throws RangeException
 */
function safeEncrypt(string $message, string $key): string
{
    if (mb_strlen($key, '8bit') !== SODIUM_CRYPTO_SECRETBOX_KEYBYTES) {
        throw new RangeException('Key is not the correct size (must be 32 bytes).');
    }
    $nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
    
    $cipher = base64_encode(
        $nonce.
        sodium_crypto_secretbox(
            $message,
            $nonce,
            $key
        )
    );
    sodium_memzero($message);
    sodium_memzero($key);
    return $cipher;
}

/**
 * Decrypt a message
 * 
 * @param string $encrypted - message encrypted with safeEncrypt()
 * @param string $key - encryption key
 * @return string
 * @throws Exception
 */
function safeDecrypt(string $encrypted, string $key): string
{   
    $decoded = base64_decode($encrypted);
    $nonce = mb_substr($decoded, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, '8bit');
    $ciphertext = mb_substr($decoded, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, null, '8bit');
    
    $plain = sodium_crypto_secretbox_open(
        $ciphertext,
        $nonce,
        $key
    );
    if (!is_string($plain)) {
        throw new Exception('Invalid MAC');
    }
    sodium_memzero($ciphertext);
    sodium_memzero($key);
    return $plain;
}

然后进行测试:
<?php
// This refers to the previous code block.
require "safeCrypto.php"; 

// Do this once then store it somehow:
$key = random_bytes(SODIUM_CRYPTO_SECRETBOX_KEYBYTES);
$message = 'We are all living in a yellow submarine';

$ciphertext = safeEncrypt($message, $key);
$plaintext = safeDecrypt($ciphertext, $key);

var_dump($ciphertext);
var_dump($plaintext);

Halite - 更易使用的Libsodium

我正在开发一个加密库,名为Halite,旨在使libsodium更易于理解和使用。

<?php
use \ParagonIE\Halite\KeyFactory;
use \ParagonIE\Halite\Symmetric\Crypto as SymmetricCrypto;

// Generate a new random symmetric-key encryption key. You're going to want to store this:
$key = new KeyFactory::generateEncryptionKey();
// To save your encryption key:
KeyFactory::save($key, '/path/to/secret.key');
// To load it again:
$loadedkey = KeyFactory::loadEncryptionKey('/path/to/secret.key');

$message = 'We are all living in a yellow submarine';
$ciphertext = SymmetricCrypto::encrypt($message, $key);
$plaintext = SymmetricCrypto::decrypt($ciphertext, $key);

var_dump($ciphertext);
var_dump($plaintext);

所有底层的加密操作都由libsodium处理。

使用defuse/php-encryption的示例

<?php
/**
 * This requires https://github.com/defuse/php-encryption
 * php composer.phar require defuse/php-encryption
 */

use Defuse\Crypto\Crypto;
use Defuse\Crypto\Key;

require "vendor/autoload.php";

// Do this once then store it somehow:
$key = Key::createNewRandomKey();

$message = 'We are all living in a yellow submarine';

$ciphertext = Crypto::encrypt($message, $key);
$plaintext = Crypto::decrypt($ciphertext, $key);

var_dump($ciphertext);
var_dump($plaintext);

注意: Crypto::encrypt() 返回十六进制编码的输出。

加密密钥管理

如果你想使用一个“密码”,请立即停止。你需要一个随机的128位加密密钥,而不是一个容易被人记住的密码。

你可以这样存储一个长期使用的加密密钥:

$storeMe = bin2hex($key);

并且,按需求,您可以这样检索它:

$key = hex2bin($storeMe);

强烈建议只存储一个随机生成的密钥以供长期使用,而不是使用任何密码作为密钥(或派生密钥)。

如果您正在使用Defuse的库:

"但我真的想使用密码。"

那是个坏主意,但好吧,以下是如何安全地实现。

首先,生成一个随机密钥并将其存储在一个常量中。

/**
 * Replace this with your own salt! 
 * Use bin2hex() then add \x before every 2 hex characters, like so:
 */
define('MY_PBKDF2_SALT', "\x2d\xb7\x68\x1a\x28\x15\xbe\x06\x33\xa0\x7e\x0e\x8f\x79\xd5\xdf");

请注意,您正在增加额外的工作量,而只需使用此常量作为密钥并节省大量痛苦即可!
然后使用PBKDF2(如下所示)从密码中派生适当的加密密钥,而不是直接使用密码进行加密。
/**
 * Get an AES key from a static password and a secret salt
 * 
 * @param string $password Your weak password here
 * @param int $keysize Number of bytes in encryption key
 */
function getKeyFromPassword($password, $keysize = 16)
{
    return hash_pbkdf2(
        'sha256',
        $password,
        MY_PBKDF2_SALT,
        100000, // Number of iterations
        $keysize,
        true
    );
}

不要只使用16个字符的密码。你的加密密钥将会非常容易被破解。


8
不要加密密码,使用password_hash()进行哈希处理,然后用password_verify()检查它们。 - Scott Arciszewski
1
@ScottArciszewski 我喜欢你关于“// Do this once then store it somehow:”的评论..某种程度上,哈哈 :)) 那么,将这个“key”(即对象)作为纯字符串硬编码存储如何?我需要这个键本身,作为一个字符串。我能从这个对象中得到它吗?谢谢。 - Andrew
1
版本1.2.1:通过bin2hex()保存它。版本2.0.0:等待完成后再阅读文档。在发布前不要使用版本2。 - Scott Arciszewski
2
谢谢,我只需要修改一行代码就可以让你的钠示例工作了:function getKeyFromPassword($password, $keysize = \Sodium\CRYPTO_SECRETBOX_KEYBYTES) - Alexey Ozerov
2
哇!因为提到不要加密URL参数,我给你点赞。我真的很喜欢你提供的文章:https://paragonie.com/blog/2015/09/comprehensive-guide-url-parameter-encryption-in-php - Lynnell Neri
显示剩余5条评论

135

我来晚了,但是在寻找正确的方法时,我发现这个页面是谷歌搜索结果中排名靠前的之一。因此,我想分享一下我的观点,我认为这篇文章所讨论的问题截至撰写本文时(2017年初)还是最新的。从PHP 7.1.0开始,mcrypt_decryptmcrypt_encrypt将被弃用,因此构建具有未来可靠性的代码应该使用openssl_encryptopenssl_decrypt

你可以尝试以下代码:

$string_to_encrypt="Test";
$password="password";
$encrypted_string=openssl_encrypt($string_to_encrypt,"AES-128-ECB",$password);
$decrypted_string=openssl_decrypt($encrypted_string,"AES-128-ECB",$password);

重要提示:这里使用的是ECB模式,并不安全。如果您想要一个简单的解决方案而又不想学习密码学工程,那么请不要自己编写代码,可以使用现成的库。

您还可以根据安全需求使用其他的加密方法。要查看可用的加密方法,请参见openssl_get_cipher_methods函数。


30
谢谢,我很惊讶这个简单明了的回答没有得到更多的赞。当我只想加密/解密一个简单的字符串时,我宁愿不看像顶部回答那样长达10页的讨论。请问需要什么进一步的翻译吗? - laurent
7
这个答案并不安全。ECB模式不应该被使用。如果你想要一个“简单明了的答案”,就使用一个库 - Scott Arciszewski
2
@ScottArciszewski,是的,我承认在寻找一些简单代码时说话太快了。自那以后,我已经在自己的代码中添加了一个IV并使用CBC,这对我的使用已经足够好了。 - laurent
13
这完全取决于使用情况!有些情况下这是完全可以接受的。比如说我想从一个页面传递一个GET参数到另一个页面,假设是prod_id=123,但我只是不想让所有人都能读懂123,然而即使他们能够读懂它,也不会有问题。攻击者能够将123替换为自定义值也不会造成任何伤害,他/她只能获得其他产品的详细信息,但Joe普通用户无法知道如何获取例如124产品的详细信息。对于这种情况,这是一个完美的解决方案,但对于安全来说就不行了! - Emil Borconi
1
例如,我想从页面传递一个GET参数到另一个页面,比如prod_id=123,但我不想让所有人都能读取到123,即使他们能够读取到,也不会有问题。- 我可以建议更好的方法吗?https://paragonie.com/blog/2015/09/comprehensive-guide-url-parameter-encryption-in-php - Scott Arciszewski
显示剩余11条评论

45

不要做什么

警告:
这个答案使用了ECB。ECB不是一种加密模式,它只是一个构建块。在本答案中演示的使用ECB并不能安全地加密字符串。请勿在您的代码中使用ECB。请查看Scott的回答以获取一种好的解决方案。

这是我自己的结果。实际上,我在谷歌上找到了一些答案并进行了一些修改。然而,结果是完全不安全的。

<?php
define("ENCRYPTION_KEY", "!@#$%^&*");
$string = "This is the original data string!";

echo $encrypted = encrypt($string, ENCRYPTION_KEY);
echo "<br />";
echo $decrypted = decrypt($encrypted, ENCRYPTION_KEY);

/**
 * Returns an encrypted & utf8-encoded
 */
function encrypt($pure_string, $encryption_key) {
    $iv_size = mcrypt_get_iv_size(MCRYPT_BLOWFISH, MCRYPT_MODE_ECB);
    $iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
    $encrypted_string = mcrypt_encrypt(MCRYPT_BLOWFISH, $encryption_key, utf8_encode($pure_string), MCRYPT_MODE_ECB, $iv);
    return $encrypted_string;
}

/**
 * Returns decrypted original string
 */
function decrypt($encrypted_string, $encryption_key) {
    $iv_size = mcrypt_get_iv_size(MCRYPT_BLOWFISH, MCRYPT_MODE_ECB);
    $iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
    $decrypted_string = mcrypt_decrypt(MCRYPT_BLOWFISH, $encryption_key, $encrypted_string, MCRYPT_MODE_ECB, $iv);
    return $decrypted_string;
}
?>

8
为了更好的安全性,你可以使用"MCRYPT_MODE_CBC"替代"MCRYPT_MODE_ECB"。 - Parixit
10
Ramesh,这是因为你得到的是原始加密数据。你可以使用base64得到更好的加密数据版本,像这样:base64_encode(encrypt($string)) -- 要解密它:decrypt(base64_decode($encrypted)) - mendezcode
83
警告:这不安全。字符串不应该使用ECB模式,ECB不采用IV,这是不具备身份验证的,它使用旧的密码(Blowfish)而不是AES,密钥不是二进制等等。SO社区真的应该停止点赞那些只是“可用”的加密/解密方法,而是开始点赞已知为安全的答案。如果你不确定,请不要投票。 - Maarten Bodewes
3
我已经通过替换mcrypt_encrypt的示例代码(http://php.net/manual/en/function.mcrypt-encrypt.php)来完成了这个操作。请注意,现在重新审查它时,最好在末尾添加一个 rtrim 去掉字符 "\0" 。 - Maarten Bodewes
5
正确的做法是使用类似于 defuse/php-encryption 这样的工具,而不是编写自己的 mcrypt 代码。 - Scott Arciszewski
显示剩余11条评论

25

针对Laravel框架

如果您正在使用Laravel框架,那么使用内部函数进行加密和解密更加容易。

$string = 'Some text to be encrypted';
$encrypted = \Illuminate\Support\Facades\Crypt::encrypt($string);
$decrypted_string = \Illuminate\Support\Facades\Crypt::decrypt($encrypted);

var_dump($string);
var_dump($encrypted);
var_dump($decrypted_string);
注意:务必在 config/app.php 文件的 key 选项中设置一个 16、24 或 32 字符的随机字符串。否则,加密值将不安全。

2
当然,它可能很容易使用。但是它安全吗?它如何解决https://dev59.com/vmQn5IYBdhLWcg3wvpQV#30159120中的问题?它使用认证加密吗?它避免侧信道漏洞并确保恒定时间的相等性检查吗?它使用真正随机的密钥而不是密码/短语吗?它使用适当的操作模式吗?它正确生成随机IV吗? - D.W.

23

已更新

PHP 7兼容版本。它使用PHP OpenSSL库中的openssl_encrypt函数。

class Openssl_EncryptDecrypt {
    function encrypt ($pure_string, $encryption_key) {
        $cipher     = 'AES-256-CBC';
        $options    = OPENSSL_RAW_DATA;
        $hash_algo  = 'sha256';
        $sha2len    = 32;
        $ivlen = openssl_cipher_iv_length($cipher);
        $iv = openssl_random_pseudo_bytes($ivlen);
        $ciphertext_raw = openssl_encrypt($pure_string, $cipher, $encryption_key, $options, $iv);
        $hmac = hash_hmac($hash_algo, $ciphertext_raw, $encryption_key, true);
        return $iv.$hmac.$ciphertext_raw;
    }
    function decrypt ($encrypted_string, $encryption_key) {
        $cipher     = 'AES-256-CBC';
        $options    = OPENSSL_RAW_DATA;
        $hash_algo  = 'sha256';
        $sha2len    = 32;
        $ivlen = openssl_cipher_iv_length($cipher);
        $iv = substr($encrypted_string, 0, $ivlen);
        $hmac = substr($encrypted_string, $ivlen, $sha2len);
        $ciphertext_raw = substr($encrypted_string, $ivlen+$sha2len);
        $original_plaintext = openssl_decrypt($ciphertext_raw, $cipher, $encryption_key, $options, $iv);
        $calcmac = hash_hmac($hash_algo, $ciphertext_raw, $encryption_key, true);
        if(function_exists('hash_equals')) {
            if (hash_equals($hmac, $calcmac)) return $original_plaintext;
        } else {
            if ($this->hash_equals_custom($hmac, $calcmac)) return $original_plaintext;
        }
    }
    /**
     * (Optional)
     * hash_equals() function polyfilling.
     * PHP 5.6+ timing attack safe comparison
     */
    function hash_equals_custom($knownString, $userString) {
        if (function_exists('mb_strlen')) {
            $kLen = mb_strlen($knownString, '8bit');
            $uLen = mb_strlen($userString, '8bit');
        } else {
            $kLen = strlen($knownString);
            $uLen = strlen($userString);
        }
        if ($kLen !== $uLen) {
            return false;
        }
        $result = 0;
        for ($i = 0; $i < $kLen; $i++) {
            $result |= (ord($knownString[$i]) ^ ord($userString[$i]));
        }
        return 0 === $result;
    }
}

define('ENCRYPTION_KEY', '__^%&Q@$&*!@#$%^&*^__');
$string = "This is the original string!";

$OpensslEncryption = new Openssl_EncryptDecrypt;
$encrypted = $OpensslEncryption->encrypt($string, ENCRYPTION_KEY);
$decrypted = $OpensslEncryption->decrypt($encrypted, ENCRYPTION_KEY);

1
这是更好且更安全的版本。谢谢,它也按预期工作。 - user11856197
4
你如何创建和存储加密密钥? - user2800464
但是,加密文本每次都会改变,我该如何将存储的加密字符串与它进行比较? - qɐʇǝɥɐW
1
好的!我明白了,我已经将openssl_random_pseudo_bytes函数更改为静态16个字符的字符串。谢谢! - qɐʇǝɥɐW
1
我在我的项目中使用了这个,做了一些更改以便静态使用。谢谢!!! - Carlos Espinoza
1
$cipher = 'AES-256-CTR'; 更好。 - yes sure

10

如果你不想使用库(虽然你应该使用),那么可以使用类似于以下内容的代码(PHP 7):

function sign($message, $key) {
    return hash_hmac('sha256', $message, $key) . $message;
}

function verify($bundle, $key) {
    return hash_equals(
      hash_hmac('sha256', mb_substr($bundle, 64, null, '8bit'), $key),
      mb_substr($bundle, 0, 64, '8bit')
    );
}

function getKey($password, $keysize = 16) {
    return hash_pbkdf2('sha256',$password,'some_token',100000,$keysize,true);
}

function encrypt($message, $password) {
    $iv = random_bytes(16);
    $key = getKey($password);
    $result = sign(openssl_encrypt($message,'aes-256-ctr',$key,OPENSSL_RAW_DATA,$iv), $key);
    return bin2hex($iv).bin2hex($result);
}

function decrypt($hash, $password) {
    $iv = hex2bin(substr($hash, 0, 32));
    $data = hex2bin(substr($hash, 32));
    $key = getKey($password);
    if (!verify($data, $key)) {
      return null;
    }
    return openssl_decrypt(mb_substr($data, 64, null, '8bit'),'aes-256-ctr',$key,OPENSSL_RAW_DATA,$iv);
}

$string_to_encrypt='John Smith';
$password='password';
$encrypted_string=encrypt($string_to_encrypt, $password);
$decrypted_string=decrypt($encrypted_string, $password);

这个能替代 https://dev59.com/vmQn5IYBdhLWcg3wvpQV#16606352 吗?我一直在使用前者,直到我的主机升级到 PHP 7.2。 - anjanesh
@anjanesh,您将无法使用此方法解密旧数据(因为算法不同且此方法还会检查签名)。 - Ascon
2
在您的情况下,可能应该这样做: define("ENCRYPTION_KEY", "123456*"); $string = "This is the original data string!"; $encrypted = openssl_encrypt($string, 'BF-ECB', ENCRYPTION_KEY); $decrypted = openssl_decrypt($encrypted,'BF-ECB',ENCRYPTION_KEY); - Ascon
这太棒了! - anjanesh

10

这些是使用AES256 CBC在PHP中加密/解密字符串的简洁方法:

function encryptString($plaintext, $password, $encoding = null) {
    $iv = openssl_random_pseudo_bytes(16);
    $ciphertext = openssl_encrypt($plaintext, "AES-256-CBC", hash('sha256', $password, true), OPENSSL_RAW_DATA, $iv);
    $hmac = hash_hmac('sha256', $ciphertext.$iv, hash('sha256', $password, true), true);
    return $encoding == "hex" ? bin2hex($iv.$hmac.$ciphertext) : ($encoding == "base64" ? base64_encode($iv.$hmac.$ciphertext) : $iv.$hmac.$ciphertext);
}

function decryptString($ciphertext, $password, $encoding = null) {
    $ciphertext = $encoding == "hex" ? hex2bin($ciphertext) : ($encoding == "base64" ? base64_decode($ciphertext) : $ciphertext);
    if (!hash_equals(hash_hmac('sha256', substr($ciphertext, 48).substr($ciphertext, 0, 16), hash('sha256', $password, true), true), substr($ciphertext, 16, 32))) return null;
    return openssl_decrypt(substr($ciphertext, 48), "AES-256-CBC", hash('sha256', $password, true), OPENSSL_RAW_DATA, substr($ciphertext, 0, 16));
}

使用方法:

$enc = encryptString("mysecretText", "myPassword");
$dec = decryptString($enc, "myPassword");

编辑:这是一个使用AES256 GCMPBKDF2作为密钥派生的新版本函数,更加安全。

function str_encryptaesgcm($plaintext, $password, $encoding = null) {
    if ($plaintext != null && $password != null) {
        $keysalt = openssl_random_pseudo_bytes(16);
        $key = hash_pbkdf2("sha512", $password, $keysalt, 20000, 32, true);
        $iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length("aes-256-gcm"));
        $tag = "";
        $encryptedstring = openssl_encrypt($plaintext, "aes-256-gcm", $key, OPENSSL_RAW_DATA, $iv, $tag, "", 16);
        return $encoding == "hex" ? bin2hex($keysalt.$iv.$encryptedstring.$tag) : ($encoding == "base64" ? base64_encode($keysalt.$iv.$encryptedstring.$tag) : $keysalt.$iv.$encryptedstring.$tag);
    }
}

function str_decryptaesgcm($encryptedstring, $password, $encoding = null) {
    if ($encryptedstring != null && $password != null) {
        $encryptedstring = $encoding == "hex" ? hex2bin($encryptedstring) : ($encoding == "base64" ? base64_decode($encryptedstring) : $encryptedstring);
        $keysalt = substr($encryptedstring, 0, 16);
        $key = hash_pbkdf2("sha512", $password, $keysalt, 20000, 32, true);
        $ivlength = openssl_cipher_iv_length("aes-256-gcm");
        $iv = substr($encryptedstring, 16, $ivlength);
        $tag = substr($encryptedstring, -16);
        return openssl_decrypt(substr($encryptedstring, 16 + $ivlength, -16), "aes-256-gcm", $key, OPENSSL_RAW_DATA, $iv, $tag);
    }
}

使用方法:

$enc = str_encryptaesgcm("mysecretText", "myPassword", "base64"); // return a base64 encrypted string, you can also choose hex or null as encoding.
$dec = str_decryptaesgcm($enc, "myPassword", "base64");

8

历史注记:此文档是在PHP4时期编写的,现在我们称之为“遗留代码”。

出于历史目的,我保留了这篇答案。但是其中一些方法已经废弃不用,DES加密方法也不是推荐实践等。

我没有更新这段代码的原因有两个:1)我不再使用PHP手动加密方法;2)这段代码仍然达到了它的预期目的:演示PHP中加密如何工作的最简单,最简洁的概念。

如果你找到类似的简化版“PHP加密入门”源代码,可以在评论中告诉我。

除此之外,请欣赏这个早期PHP4最简单加密答案的经典片段。


理想情况下,您拥有或可以获得mcrypt PHP库的访问权限,因为它非常流行且在各种任务中非常有用。以下是不同类型的加密和一些示例代码的详细介绍:PHP中的加密技术

//Listing 3: Encrypting Data Using the mcrypt_ecb Function 

<?php 
echo("<h3> Symmetric Encryption </h3>"); 
$key_value = "KEYVALUE"; 
$plain_text = "PLAINTEXT"; 
$encrypted_text = mcrypt_ecb(MCRYPT_DES, $key_value, $plain_text, MCRYPT_ENCRYPT); 
echo ("<p><b> Text after encryption : </b>"); 
echo ( $encrypted_text ); 
$decrypted_text = mcrypt_ecb(MCRYPT_DES, $key_value, $encrypted_text, MCRYPT_DECRYPT); 
echo ("<p><b> Text after decryption : </b>"); 
echo ( $decrypted_text ); 
?> 

几个警告:

1)当单向哈希能胜任时,永远不要使用可逆或“对称”加密。

2)如果数据真的很敏感,例如信用卡或社会保障号码,请停止;你需要更多的东西才能提供保障,而不仅仅是简单的代码块,而是一个专为此目的设计的加密库和大量研究所需的方法。此外,软件加密可能只有敏感数据安全的<10%。就像重新布线核电站一样,如果情况如此,接受这项任务是危险的、困难的并超出了你的知识范围。罚款可能会非常巨大,所以最好使用服务并将责任交给他们。

3)任何易于实现的加密,如此处所列,都可以合理地保护你想要防止别人偷窥或在意外/故意泄漏的情况下限制暴露的轻度重要信息。但是,由于密钥存储在Web服务器上的明文中,如果他们可以获取数据,则可以获取解密密钥。

即便如此,玩得愉快:)


2
谢谢!但出现了一些问题。我得到的加密结果是"M�������f=�_="这种奇怪的字符。我不能得到简单的字符吗?比如:"2a2ffa8f13220befbe30819047e23b2c"。还有,我不能更改$key_value的长度(固定为8个字符??),和输出的$encrypted_text的长度吗?(难道它不能是32位或64位或其他更长的吗?) - 夏期劇場
3
加密的结果是二进制数据。如果您需要人类可读的结果,请使用基于base64或十六进制的编码方式。"我不能改变密钥值的长度吗?" 不同的对称加密算法对密钥值有不同的要求。 "输出的长度..." 加密文本的长度必须至少与原始文本长度相等,否则就没有足够的信息来重新创建原始文本。(这是鸽巢原理的一个应用) 另外,您应该使用AES而不是DES。DES容易被破解,不再安全。 - Patashu
8
自 PHP 5.5.0 开始,mcrypt_ecb 已经被废弃了,建议不要再使用此函数。使用这个函数是极不推荐的。更多信息请参考:http://php.net/manual/zh/function.mcrypt-ecb.php - Hafez Divandari
1
@BrianDHall 这个帖子仍然会被踩是因为ECB模式不安全(使用CBC,CTR,GCM或Poly1305),DES加密算法弱(你需要AES,即MCRYPT_RIJNDAEL_128),并且密文应该进行身份验证(使用hash_hmac(),并通过hash_equals()进行验证)。 - Scott Arciszewski
嘿@Patashu,如何将base64包含在那段代码中?抱歉我以前从未使用过。 - gumuruh
显示剩余3条评论

1
在PHP中,使用OpenSSL函数进行加密和解密可以实现字符串的加密和解密。
openssl_encrypt()函数:用于加密数据。
语法如下:
string openssl_encrypt( string $data, string $method, string $key, $options = 0, string $iv, string $tag= NULL, string $aad, int $tag_length = 16 )
参数如下: $data:需要加密的字符串或数据。 $method:使用openssl_get_cipher_methods()函数采用的密码方法。 $key:加密密钥。 $options:标志OPENSSL_RAW_DATA和OPENSSL_ZERO_PADDING的按位异或。 $iv:初始化向量,不能为空。 $tag:通过引用传递的身份验证标记,在使用AEAD密码模式(GCM或CCM)时使用。 $aad:附加身份验证数据。

$tag_length: 它保存了认证标签的长度。GCM模式下,认证标签的长度介于4到16之间。

返回值:成功时返回加密字符串,失败时返回FALSE。

openssl_decrypt()函数用于解密数据。

语法如下:

string openssl_decrypt( string $data, string $method, string $key, int $options = 0, string $iv, string $tag, string $aad)

参数如下:

$data: 它保存需要加密的字符串或数据。

$method: 密码方法是使用openssl_get_cipher_methods()函数采用的。

$key: 它保存了加密密钥。

$options: 它保存OPENSSL_RAW_DATA和OPENSSL_ZERO_PADDING标志的按位或运算结果。

$iv: 它保存了初始化向量,不为NULL。

$tag: 它保存了使用AEAD密码模式(GCM或CCM)的认证标签。当认证失败时,openssl_decrypt()返回FALSE。

$aad: 它保存了额外的身份验证数据。

返回值:成功时,它返回解密后的字符串;失败时,返回 FALSE 。

步骤:首先声明一个字符串并将其存储到变量中,使用 openssl_encrypt() 函数加密给定的字符串,并使用 openssl_decrypt() 函数解密给定的字符串。

您可以在以下链接中找到示例:https://www.geeksforgeeks.org/how-to-encrypt-and-decrypt-a-php-string/


0

以下代码适用于带有特殊字符的所有字符串的 PHP 实现

   // Encrypt text --

    $token = "9611222007552";

      $cipher_method = 'aes-128-ctr';
      $enc_key = openssl_digest(php_uname(), 'SHA256', TRUE);  
      $enc_iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length($cipher_method));  
      $crypted_token = openssl_encrypt($token, $cipher_method, $enc_key, 0, $enc_iv) . "::" . bin2hex($enc_iv);
    echo    $crypted_token;
    //unset($token, $cipher_method, $enc_key, $enc_iv);

    // Decrypt text  -- 

    list($crypted_token, $enc_iv) = explode("::", $crypted_token);  
      $cipher_method = 'aes-128-ctr';
      $enc_key = openssl_digest(php_uname(), 'SHA256', TRUE);
      $token = openssl_decrypt($crypted_token, $cipher_method, $enc_key, 0, hex2bin($enc_iv));
    echo   $token;
    //unset($crypted_token, $cipher_method, $enc_key, $enc_iv);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接