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


110

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

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

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

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

私が学びたいもう1つのことはhash(sha256)、良い「塩」と組み合わせた一方向を作成する方法です。(基本的に、暗号化/復号化、hash(sha256)+salt) サー/奥様の単純な実装が欲しいだけです。あなたの答えは非常に役に立ち、非常に感謝されます。ありがとう++




9
SHAはハッシュであり、暗号化ではありません。重要な点は、ハッシュを元のデータに戻すことはできない(とにかく簡単にはできない)ことです。おそらくmcryptが必要か、それが利用できない場合はphpseclibをお勧めします。ただし、多くの低レベルの数学を含むあらゆるものの純粋なPHPの実装は非常に重要ですが、phpseclibが好きです。使用可能な場合は最初にmcryptを使用し、最後の手段としてPHP実装のみにフォールバックします。
DaveRandom

7
通常、パスワードを解読したくありません!
ジャック・

1
基本的に、このレベルでの暗号化について考えるべきではありません。アクセス制御、機密性、完全性、認証について考える必要があります。あなたは、これを達成することができますどのようにチェックした後、おそらく暗号化またはセキュアハッシュを使用。パスワードの安全なハッシュなどを理解するために、PBKDF2とbcrypt / scryptを読み込むことができます。
Maarten Bodewes

回答:


289

序文

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

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

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

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

暗号化

暗号とモード

最適な暗号化方式とモードを選択することはこの回答の範囲を超えていますが、最後の選択は暗号化キーと初期化ベクトルの両方のサイズに影響します。この投稿では、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と同様に、暗号化された出力はバイナリです。これらの値をデータベースに保存するには、BINARYまたはなどの指定された列タイプを使用しますVARBINARY

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などのハッシュアルゴリズムは、既知のハッシュ値に対してファイルの内容を検証するために作成されました。これらは非常に最適化されており、この検証を可能な限り高速にしながら、正確性を保ちます。出力スペースが比較的限られているため、既知のパスワードとそれぞれのハッシュ出力であるレインボーテーブルでデータベースを構築するのは簡単でした。

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

現在利用可能な2つの人気のある選択肢があります。

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

この回答では、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()、次いで介して実行されるデータのランダムブロブを形成するbase64_encode()strtr()、必要なアルファベットと一致します[A-Za-z0-9/.]

このcrypt()関数は、アルゴリズム($2y$Blowfishの場合)、コスト係数(13の係数は3GHzマシンで約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()


安全な賭けのために、名前、姓、電子メールなどを保存するのにどのくらいの長さを使用すればよいですか?varbinary(???)
BentCoder 2013

2
もちろんですが、使い方によって異なります。暗号化ライブラリを公開する場合、開発者がそれを実装する方法がわかりません。これがgithub.com/defuse/php-encryptionが認証された対称鍵暗号化を提供し、開発者がコードを編集せずにそれを弱めることができない理由です。
Scott Arciszewski、2015年

2
@スコットまあ、私は認証された暗号化の例を追加しました。プッシュありがとう:)
ジャック

1
認証された暗号化の場合は+1。質問には、AEは必要ないという十分な情報がありません。確かに、SQLトラフィックは、データベースからストレージへのトラフィックと同様に、セキュリティプロパティが不明なネットワークを経由することがよくあります。バックアップとレプリケーションも。脅威モデルとは何ですか?質問は言っていません、そして仮定をすることは危険かもしれません。
Jason Orendorff

1
ハードコーディングの代わりに$iv_size = 16;、私は以下を使用します:$iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length("AES-256-CBC"))使用する暗号で使用するivのサイズ間のリンクを示すため。pkcs7_pad()/の必要性(またはそうでない場合)を少し拡張するかpkcs7_unpad()、それらを削除して「aes-256-ctr」を使用することで投稿を単純化することもできます。素晴らしい投稿@Ja Greatck
Patrick Allaert、

24

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

7
そのため、ECBも使用しないでください。
Maarten Bodewes

7
鍵はランダムなバイトにするか、安全な鍵導出関数を使用する必要があります。
Maarten Bodewes

4
MCRYPT_RIJNDAEL_256は標準化された関数ではないため、AES(MCRYPT_RIJNDAEL_128)を使用する必要があります
Maarten Bodewes

14

回答の背景と説明

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

使用したいのは双方向関数ですが、より具体的には、Block Cipherです。データの暗号化と復号化の両方を可能にする機能。関数mcrypt_encryptmcrypt_decryptデフォルトではBlowfishアルゴリズムを使用します。PHPでのmcryptの使用については、このマニュアルを参照してください。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);

そのため、ECBも使用しないでください。
Maarten Bodewes

鍵はランダムなバイトにするか、安全な鍵導出関数を使用する必要があります。
Maarten Bodewes

4
ECBモードを使用しないでください。これは安全ではなく、ほとんどの場合、実際にデータを暗号化するのに役立ちません(単にエンコードするだけではありません)。詳細については、主題に関する優れたWikipediaの記事を参照してください。
Holger Just

1
mcryptを使用しないことをお勧めします。これは放棄され、何年も更新されておらず、標準のPKCS#7(néePKCS#5)パディングをサポートしていません。バイナリデータでも使用できない非標準のnullパディングのみです。 。mcryptには、2003年までさかのぼる多くの未解決のバグがありました。代わりに、defuseの使用を検討してください。保守されており、正しいです。
zaph 2016年

9

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;

2
の代わりにmcrypt_create_iv()、私は次のものを使用します:openssl_random_pseudo_bytes(openssl_cipher_iv_length($encryptionMethod))この方法では、$ encryptionMethodの任意の値に対してこの方法が機能し、openssl拡張のみを使用します。
Patrick Allaert、2016

上記のコードはfalseを返しますopenssl_decrypt()stackoverflow.com/q/41952509/1066234を 参照してください。AESなどのブロック暗号では、入力データをブロックサイズの正確な倍数にする必要があるため(AESの場合は16バイト)、パディングが必要です。
カイノアック2017

6
     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;
    }

とてもシンプル!URLセグメントの暗号化と復号化に使用します。ありがとう
Mahbub Tito

0

false使用するときにaを取得しない方法openssl_decrypt()と、暗号化と復号化を機能させる方法を理解するにはかなり時間がかかりました。

    // 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();

お役に立てば幸いです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.