あなたはさらに何かをする前に、理解しようとの違いの暗号化と認証を、あなたはおそらくしたい理由は、認証済みの暗号化だけではなく、暗号化を。
認証された暗号化を実装するには、暗号化してからMACを使用します。暗号化と認証の順序は非常に重要です。この質問に対する既存の回答の1つがこの間違いを犯しました。PHPで書かれた多くの暗号化ライブラリと同様に。
独自の暗号化を実装することは避け、代わりに暗号化の専門家によって作成およびレビューされた安全なライブラリを使用してください。
更新:PHP 7.2はlibsodiumを提供します!最高のセキュリティを確保するには、PHP 7.2以降を使用するようにシステムを更新し、この回答のlibsodiumのアドバイスのみに従ってください。
PECLアクセス権がある場合はlibsodiumを使用します(PECLなしでlibsodiumが必要な場合はodium_compat)。それ以外の場合...
defuse / php-encryptionを使用します。独自の暗号化を行わないでください。
上記のリンクされたライブラリはどちらも、認証された暗号化を独自のライブラリに実装することを簡単かつ容易にします。
それでも独自の暗号化ライブラリを作成して展開したい場合は、インターネット上のすべての暗号化の専門家の常識に反して、これらの手順を実行する必要があります。
暗号化:
- CTRモードでAESを使用して暗号化します。GCMを使用することもできます(これにより、個別のMACが不要になります)。さらに、ChaCha20およびSalsa20(libsodiumによって提供)はストリーム暗号であり、特別なモードを必要としません。
- 上記のGCMを選択しない限り、HMAC-SHA-256で暗号文を認証する必要があります(または、ストリーム暗号の場合は、Poly1305-ほとんどのlibsodium APIがこれを行います)。MACはIVと暗号文をカバーする必要があります!
解読:
- Poly1305またはGCMが使用されていない限り、暗号文のMACを再計算し、それをを使用して送信されたMACと比較し
hash_equals()
ます。失敗した場合は中止します。
- メッセージを復号化します。
その他の設計上の考慮事項:
- これまで何も圧縮しないでください。暗号文は圧縮できません。暗号化の前に平文を圧縮すると、情報漏えいが発生する可能性があります(TLSでのCRIMEやBREACHなど)。
- 問題の発生を防ぐために、文字セットモードを使用して
mb_strlen()
およびを使用していることを確認してください。mb_substr()
'8bit'
mbstring.func_overload
- IVはCSPRNGを使用して生成する必要があります。あなたが使用している場合は
mcrypt_create_iv()
、使用をしないでくださいMCRYPT_RAND
!
- AEADコンストラクトを使用している場合を除き、常にMACを暗号化します。
bin2hex()
、base64_encode()
などは、キャッシュタイミングを介して暗号化キーに関する情報を漏洩する可能性があります。可能であれば避けてください。
ここで与えられたアドバイスに従ったとしても、暗号化で多くのことが失敗する可能性があります。暗号化の専門家に常に実装をレビューしてもらいます。あなたの地元の大学の暗号学の学生と個人的な友達になるほど幸運ではない場合は、いつでも暗号化スタック交換フォーラムを試してアドバイスを受けることができます。
実装の専門的な分析が必要な場合は、常に信頼できるセキュリティコンサルタントのチームを雇って、PHP暗号化コードを確認することができます(開示:私の雇用主)。
重要:暗号化を使用しない場合
パスワードを暗号化しないでください。あなたはしたいハッシュこれらのパスワードハッシュアルゴリズムのいずれかを使用して、代わりにそれら:
パスワードの保存には、汎用ハッシュ関数(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がより簡単に
私が取り組んできたプロジェクトの1つは、libsodiumをより簡単かつ直感的にすることを目的としたHaliteと呼ばれる暗号化ライブラリです。
<?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()
16進エンコードされた出力を返します。
暗号化キー管理
「パスワード」を使いたくなったら、今すぐやめてください。人間が覚えやすいパスワードではなく、ランダムな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文字のパスワードを使用しないでください。暗号化キーは破壊されます。