web-dev-qa-db-ja.com

PHPでデータを暗号化/復号化する方法は?

私は現在学生で、PHPを勉強しています。PHPでデータの単純な暗号化/復号化を試みています。私はいくつかのオンライン調査を行いましたが、そのうちのいくつかは非常にわかりにくいものでした(少なくとも私にとって)。

これが私がやろうとしていることです:

これらのフィールドで構成されるテーブルがあります(UserID、Fname、Lname、Email、Password)

私が持ちたいのは、すべてのフィールドを暗号化してから復号化することです(暗号化アルゴリズムではない場合、暗号化/復号化にsha256を使用することは可能ですか)

私が学びたいもう一つのことは、良い「塩」と組み合わせた一方向のhash(sha256)を作成する方法です。 (基本的に、暗号化/復号化の簡単な実装、hash(sha256)+salt) Sir/Ma'amが欲しいだけです。あなたの答えは大いに役立ち、とても感謝しています。ありがとうございました++

106
Randel Ramirez

序文

テーブル定義から始めます。

- UserID
- Fname
- Lname
- Email
- Password
- IV

変更点は次のとおりです。

  1. FnameLname、およびEmailフィールドは、 OpenSSL で提供される対称暗号を使用して暗号化されます。
  2. IVフィールドには、暗号化に使用される 初期化ベクトル が格納されます。ストレージ要件は、使用する暗号とモードによって異なります。これについては後で詳しく説明します。
  3. Passwordフィールドは、one-wayパスワードハッシュを使用してハッシュされます。

暗号化

暗号とモード

最適な暗号化方式とモードを選択することはこの回答の範囲外ですが、最終的な選択は暗号化キーと初期化ベクトルの両方のサイズに影響します。この投稿では、16バイトの固定ブロックサイズと16、24、または32バイトのキーサイズを持つAES-256-CBCを使用します。

暗号化キー

優れた暗号化キーは、信頼性の高い乱数ジェネレーターから生成されるバイナリ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

これは1回または複数回実行できます(暗号化キーのチェーンを作成する場合)。これらはできるだけ非公開にしてください。

IV

初期化ベクトルは、暗号化にランダム性を追加し、CBCモードに必要です。これらの値は、理想的には1回のみ(技術的には暗号化キーごとに1回)使用する必要があるため、行の任意の部分を更新すると再生成されます。

IVの生成に役立つ機能が提供されています。

$iv_size = 16; // 128 bits
$iv = openssl_random_pseudo_bytes($iv_size, $strong);

以前の$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
);

ストレージ要件

IVのような暗号化された出力はバイナリです。これらの値をデータベースに格納するには、BINARYVARBINARYなどの指定された列タイプを使用します。

IVと同様に、出力値はバイナリです。これらの値をMySQLに保存するには、 BINARYまたはVARBINARY 列の使用を検討してください。これがオプションではない場合、 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
));

認証された暗号化

秘密キー(暗号化キーとは異なる)と暗号テキストから生成された署名を追加することにより、生成された暗号テキストの整合性をさらに向上させることができます。暗号文が復号化される前に、署名が最初に検証されます(一定時間の比較方法が望ましい)。

// 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
}

参照: hash_equals()

ハッシング

リバーシブルパスワードをデータベースに保存することはできる限り避ける必要があります。その内容を知るのではなく、パスワードのみを検証したい場合。ユーザーがパスワードを紛失した場合、元のパスワードを送信するのではなく、パスワードをリセットできるようにすることをお勧めします(パスワードのリセットは、限られた時間のみ実行できるようにしてください)。

ハッシュ関数の適用は一方向の操作です。その後、元のデータを公開せずに検証に安全に使用できます。パスワードについては、長さが比較的短く、多くの人がパスワードを選択できないため、ブルートフォース方式はそれを発見するための実行可能なアプローチです。

MD5やSHA1などのハッシュアルゴリズムは、既知のハッシュ値に対してファイルの内容を検証するために作成されました。これらは、この検証をできる限り高速に、しかも正確に行えるように最適化されています。出力スペースが比較的限られているため、既知のパスワードとそれぞれのハッシュ出力であるRainbowテーブルを使用してデータベースを簡単に構築できました。

ハッシュする前にパスワードにソルトを追加すると、レインボーテーブルが使用できなくなりますが、最近のハードウェアの進歩により、ブルートフォースルックアップが実行可能なアプローチになりました。そのため、意図的に遅く、最適化が不可能なハッシュアルゴリズムが必要です。また、既存のパスワードハッシュを検証して将来の使用を保証する機能に影響を与えることなく、より高速なハードウェアの負荷を増やすことができるはずです。

現在、2つの一般的な選択肢があります。

  1. PBKDF2(パスワードベースのキー派生関数v2)
  2. bcrypt(別名フグ)

この回答では、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);

ソルトは openssl_random_pseudo_bytes() で生成され、ランダムなデータのblobを形成し、base64_encode()およびstrtr()を介して実行され、必要なアルファベット[A-Za-z0-9/.]に一致します。

crypt() 関数は、アルゴリズム(Blowfishの場合は$2y$)、コストファクター(3 GHzマシンではファクター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;
}

パスワードを確認するには、もう一度crypt()を呼び出しますが、以前に計算したハッシュをソルト値として渡します。指定されたパスワードがハッシュと一致する場合、戻り値は同じハッシュを生成します。ハッシュを検証するには、多くの場合、タイミング攻撃を回避するために、一定時間の比較関数を使用することをお勧めします。

PHP 5.5を使用したパスワードハッシュ

PHP 5.5では パスワードハッシュ関数 を導入しました。これを使用して、上記のハッシュ方法を簡素化できます。

$hash = password_hash($password, PASSWORD_BCRYPT, ['cost' => 13]);

そして検証:

if (password_verify($given_password, $db_hash)) {
    // password valid
}

参照: password_hash()password_verify()

282
Ja͢ck

これは以前に答えられたと思います...しかし、とにかく、データを暗号化/復号化する場合は、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);
20
romo

回答の背景と説明

この質問を理解するには、最初にSHA256とは何かを理解する必要があります。 SHA256は暗号化ハッシュ関数です。暗号化ハッシュ関数は一方向関数であり、その出力は暗号的に安全です。つまり、ハッシュの計算は簡単ですが(データの暗号化に相当)、ハッシュを使用して元の入力を取得するのは困難です(データの復号化に相当)。暗号化ハッシュ関数を使用すると、復号化が計算上実行不可能になるため、SHA256で復号化を実行できません。

使用するのは双方向関数ですが、より具体的には、Block Cipherです。データの暗号化と復号化の両方を可能にする機能。関数mcrypt_encryptおよびmcrypt_decryptは、デフォルトでBlowfishアルゴリズムを使用します。 PHPでのmcryptの使用は、この manual にあります。 mcryptが使用する暗号を選択するための 暗号定義 のリストも存在します。 Blowfishのwikiは Wikipedia にあります。ブロック暗号は、既知のキーを使用して既知のサイズと位置のブロックで入力を暗号化し、そのキーを使用して後でデータを復号化できるようにします。これは、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);
14
cytinus

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;
8
Vivek
     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;
    }
1

openssl_decrypt()を使用しているときにfalseを取得せず、暗号化と復号化が機能するようにする方法を見つけるのにかなり時間がかかりました。

    // cryptographic key of a binary string 16 bytes long (because AES-128 has a key size of 16 bytes)
    $encryption_key = '58adf8c78efef9570c447295008e2e6e'; // example
    $iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length('aes-256-cbc'));
    $encrypted = openssl_encrypt($plaintext, 'aes-256-cbc', $encryption_key, OPENSSL_RAW_DATA, $iv);
    $encrypted = $encrypted . ':' . base64_encode($iv);

    // decrypt to get again $plaintext
    $parts = explode(':', $encrypted);
    $decrypted = openssl_decrypt($parts[0], 'aes-256-cbc', $encryption_key, OPENSSL_RAW_DATA, base64_decode($parts[1])); 

暗号化された文字列をURL経由で渡す場合、文字列をurlencodeする必要があります。

    $encrypted = urlencode($encrypted);

何が起こっているかをよりよく理解するには、以下をお読みください:

16バイト長のキーを生成するには、次を使用できます。

    $bytes = openssl_random_pseudo_bytes(16);
    $hex = bin2hex($bytes);

Opensslのエラーメッセージを表示するには、echo openssl_error_string();を使用できます。

お役に立てば幸いです。

0
Kai Noack