在进一步做任何事情之前,寻求理解
the difference between encryption and authentication,以及为什么你可能想要验证加密而不仅仅是加密。
上面链接的两个库使得在你自己的库中实现认证加密变得容易和轻松。
如果你仍然想要编写和部署你自己的加密库,对照互联网上的每个加密专家的传统智慧,这些都是你必须采取的步骤。
加密:
>在CTR模式下使用AES加密。您还可以使用GCM(这消除了对单独的MAC的需要)。此外,ChaCha20和Salsa20(由libsodium提供)是流密码,并且不需要特殊模式。
>除非你选择GCM以上,你应该使用HMAC-SHA-256验证密文(或者,对于流密码,Poly1305 – 大多数libsodium API为你这样做)。 MAC应该覆盖IV以及密文!
解密:
>除非使用Poly1305或GCM,重新计算密文的MAC,并将其与使用hash_equals()发送的MAC进行比较。如果失败,请中止。
>解密消息。
其他设计注意事项:
>不要压缩任何东西。密文不可压缩;在加密之前压缩明文可能导致信息泄漏(例如TLS上的CRIME和BREACH)。
>确保使用mb_strlen()和mb_substr(),使用’8bit’字符集模式来防止mbstring.func_overload问题。
> IVs应该使用CSPRNG生成;如果您使用mcrypt_create_iv(),请不要使用MCRYPT_RAND!
>除非你使用AEAD结构,ALWAYS encrypt then MAC!
> bin2hex(),base64_encode()等可能通过缓存时间泄漏有关您的加密密钥的信息。如果可能,避免他们。
即使你遵循这里提供的建议,很多可能会出错的加密。始终让密码专家审查您的实施。如果你不幸运地不够个人的朋友与密码学生在你当地的大学,你可以随时尝试Cryptography Stack Exchange论坛的建议。
重要:何时不使用加密
切勿使用通用哈希函数(MD5,SHA256)进行密码存储。
这是这个工作的错误工具。
PHP字符串加密示例与Libsodium
// This requires the libsodium PECL extension
/**
* Encrypt a message
*
* @param string $message - message to encrypt
* @param string $key - encryption key
* @return string
*/
function safeEncrypt($message, $key)
{
$nonce = \Sodium\randombytes_buf(
\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
*/
function safeDecrypt($encrypted, $key)
{
$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
);
\Sodium\memzero($ciphertext);
\Sodium\memzero($key);
return $plain;
}
然后测试一下:
// This refers to the previous code block.
require "safeCrypto.php";
// Do this once then store it somehow:
$key = \Sodium\randombytes_buf(
\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更容易和更直观。
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处理。显然,这意味着你需要PECL扩展来使用Halite。
使用defuse / php加密的示例
/**
* This requires https://github.com/defuse/php-encryption
* php composer.phar require defuse/php-encryption
*
* Note that v2.0.0 is around the corner and this might need
* to be edited when it's finally released.
*/
use \Defuse\Crypto\Crypto;
require "vendor/autoload.php";
// Do this once then store it somehow:
$key = Crypto::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()返回原始二进制,所以你可能想使用base64_encode()和base64_decode()来存储/发送密文,以防止编码错误。在版本2中,它将默认为十六进制编码输出。
加密密钥管理
如果你想使用“密码”,立即停止。您需要一个随机的128位加密密钥,而不是人类难忘的密码。
您可以存储长期使用的加密密钥,如下所示:
$storeMe = bin2hex($key);
而且,根据需要,你可以这样检索:
$key = hex2bin($storeMe);
我强烈建议只是存储一个随机生成的密钥长期使用,而不是任何类型的密码作为密钥(或导出密钥)。
“但我真的想使用密码。
这是一个坏主意,但好吧,这里是如何做到安全。
首先,生成一个随机密钥并将其存储在常量中。
/**
* 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个字符的密码。您的加密密钥将会被破坏。