使用PHP最简单的双向加密

在普通PHP安装中进行双向加密的最简单方法是什么?

我需要能够使用字符串密钥加密数据,并使用相同的密钥解密另一端。

安全性不像代码的可移植性那么重要,所以我希望能够尽可能地简化事情。 目前,我正在使用RC4实现,但是如果我能够找到本机支持的东西,我想我可以节省大量不必要的代码。

编辑:

你应该真的使用openssl_encrypt()和openssl_decrypt()

正如斯科特所说,Mcrypt不是一个好主意,因为它自2007年以来一直没有更新。

甚至还有一个RFC来从PHP中删除Mcrypt – https://wiki.php.net/rfc/mcrypt-viking-funeral

重要说明 :除非您有非常特殊的用例, 否则不要加密密码 ,而应使用密码散列算法。 当有人说他们在服务器端应用程序中加密密码时,他们要么是不知情的,要么是描述危险的系统设计。 安全地存储密码是与加密完全分开的问题。

被告知。 设计安全的系统。

PHP中的便携式数据加密

如果您使用PHP 5.4或更新的版本,并且不想自己编写加密模块,则建议使用提供经过身份验证的加密的现有库 。 我链接的图书馆只依赖PHP提供的内容,并由少数安全研究人员定期审查。 (包括我自己)

如果您的可移植性目标不能防止需要PECL扩展, 那么 强烈建议libsodium胜过您可以用PHP编写的任何内容。

更新(2016-06-12):您现在可以使用sodium_compat并使用相同的crypto libsodium产品,而无需安装PECL扩展。

如果你想尝试一下密码学工程,请继续阅读。


首先,您应该花时间学习未经身份验证的加密和密码破坏原则 的危险 。

  • 加密的数据仍然可能被恶意用户篡改。
  • 验证加密数据可防止篡改。
  • 验证未加密的数据不会防止篡改。

加密和解密

PHP中的加密实际上很简单(一旦你做出了关于如何加密信息的决定,我们将使用openssl_encrypt()openssl_decrypt() 。请openssl_get_cipher_methods()获取系统支持的方法列表。 在CTR模式下选择AES :

  • aes-128-ctr
  • aes-192-ctr
  • aes-256-ctr

目前没有理由相信AES密钥大小是一个值得担心的重要问题(由于256位模式下的密钥调度不好,所以可能不会更好)。

注意: 我们没有使用mcrypt因为它是放弃的,并且有未修补的错误 ,可能会影响安全性。 由于这些原因,我鼓励其他PHP开发人员避免这种情况。

简单的加密/解密包装使用OpenSSL

 class UnsafeCrypto { const METHOD = 'aes-256-ctr'; /** * Encrypts (but does not authenticate) a message * * @param string $message - plaintext message * @param string $key - encryption key (raw binary expected) * @param boolean $encode - set to TRUE to return a base64-encoded * @return string (raw binary) */ public static function encrypt($message, $key, $encode = false) { $nonceSize = openssl_cipher_iv_length(self::METHOD); $nonce = openssl_random_pseudo_bytes($nonceSize); $ciphertext = openssl_encrypt( $message, self::METHOD, $key, OPENSSL_RAW_DATA, $nonce ); // Now let's pack the IV and the ciphertext together // Naively, we can just concatenate if ($encode) { return base64_encode($nonce.$ciphertext); } return $nonce.$ciphertext; } /** * Decrypts (but does not verify) a message * * @param string $message - ciphertext message * @param string $key - encryption key (raw binary expected) * @param boolean $encoded - are we expecting an encoded string? * @return string */ public static function decrypt($message, $key, $encoded = false) { if ($encoded) { $message = base64_decode($message, true); if ($message === false) { throw new Exception('Encryption failure'); } } $nonceSize = openssl_cipher_iv_length(self::METHOD); $nonce = mb_substr($message, 0, $nonceSize, '8bit'); $ciphertext = mb_substr($message, $nonceSize, null, '8bit'); $plaintext = openssl_decrypt( $ciphertext, self::METHOD, $key, OPENSSL_RAW_DATA, $nonce ); return $plaintext; } } 

用法示例

 $message = 'Ready your ammunition; we attack at dawn.'; $key = hex2bin('000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f'); $encrypted = UnsafeCrypto::encrypt($message, $key); $decrypted = UnsafeCrypto::decrypt($encrypted, $key); var_dump($encrypted, $decrypted); 

演示 : https : //3v4l.org/jl7qR


上面这个简单的加密库还是不安全的。 我们需要验证密文,并在解密之前对其进行验证 。

注意 :默认情况下, UnsafeCrypto::encrypt()将返回一个原始的二进制字符串。 如果您需要以二进制安全格式(base64编码)存储,请调用它:

 $message = 'Ready your ammunition; we attack at dawn.'; $key = hex2bin('000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f'); $encrypted = UnsafeCrypto::encrypt($message, $key, true); $decrypted = UnsafeCrypto::decrypt($encrypted, $key, true); var_dump($encrypted, $decrypted); 

演示 :http: //3v4l.org/f5K93

简单的身份验证包装

 class SaferCrypto extends UnsafeCrypto { const HASH_ALGO = 'sha256'; /** * Encrypts then MACs a message * * @param string $message - plaintext message * @param string $key - encryption key (raw binary expected) * @param boolean $encode - set to TRUE to return a base64-encoded string * @return string (raw binary) */ public static function encrypt($message, $key, $encode = false) { list($encKey, $authKey) = self::splitKeys($key); // Pass to UnsafeCrypto::encrypt $ciphertext = parent::encrypt($message, $encKey); // Calculate a MAC of the IV and ciphertext $mac = hash_hmac(self::HASH_ALGO, $ciphertext, $authKey, true); if ($encode) { return base64_encode($mac.$ciphertext); } // Prepend MAC to the ciphertext and return to caller return $mac.$ciphertext; } /** * Decrypts a message (after verifying integrity) * * @param string $message - ciphertext message * @param string $key - encryption key (raw binary expected) * @param boolean $encoded - are we expecting an encoded string? * @return string (raw binary) */ public static function decrypt($message, $key, $encoded = false) { list($encKey, $authKey) = self::splitKeys($key); if ($encoded) { $message = base64_decode($message, true); if ($message === false) { throw new Exception('Encryption failure'); } } // Hash Size -- in case HASH_ALGO is changed $hs = mb_strlen(hash(self::HASH_ALGO, '', true), '8bit'); $mac = mb_substr($message, 0, $hs, '8bit'); $ciphertext = mb_substr($message, $hs, null, '8bit'); $calculated = hash_hmac( self::HASH_ALGO, $ciphertext, $authKey, true ); if (!self::hashEquals($mac, $calculated)) { throw new Exception('Encryption failure'); } // Pass to UnsafeCrypto::decrypt $plaintext = parent::decrypt($ciphertext, $encKey); return $plaintext; } /** * Splits a key into two separate keys; one for encryption * and the other for authenticaiton * * @param string $masterKey (raw binary) * @return array (two raw binary strings) */ protected static function splitKeys($masterKey) { // You really want to implement HKDF here instead! return [ hash_hmac(self::HASH_ALGO, 'ENCRYPTION', $masterKey, true), hash_hmac(self::HASH_ALGO, 'AUTHENTICATION', $masterKey, true) ]; } /** * Compare two strings without leaking timing information * * @param string $a * @param string $b * @ref https://paragonie.com/b/WS1DLx6BnpsdaVQW * @return boolean */ protected static function hashEquals($a, $b) { if (function_exists('hash_equals')) { return hash_equals($a, $b); } $nonce = openssl_random_pseudo_bytes(32); return hash_hmac(self::HASH_ALGO, $a, $nonce) === hash_hmac(self::HASH_ALGO, $b, $nonce); } } 

用法示例

 $message = 'Ready your ammunition; we attack at dawn.'; $key = hex2bin('000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f'); $encrypted = SaferCrypto::encrypt($message, $key); $decrypted = SaferCrypto::decrypt($encrypted, $key); var_dump($encrypted, $decrypted); 

演示 : 原始二进制 , base64编码


如果有人希望在生产环境中使用这个SaferCrypto库,或者你自己实现了相同的概念,我强烈建议在你做之前,向你的常驻密码员提出第二个意见。 他们会告诉你我可能没有意识到的错误。

使用信誉良好的密码术库会更好。

使用mcrypt_encrypt()mcrypt_decrypt()以及相应的参数。 真的很简单直接,你使用一个经过测试的加密包。

编辑

在回答这个问题之后的5年零4个月, mcrypt扩展现在正处于弃用和最终从PHP中删除的过程中。

这是简单但足够安全的实现:

  • CBC模式下的AES-256加密
  • PBKDF2从纯文本密码中创建加密密钥
  • HMAC来验证加密的消息。

代码和示例在这里: https : //stackoverflow.com/a/19445173/1387163