PHPでデータを暗号化する方法について

採用はこちら

目次

PHPでデータを暗号化する方法

PHPでデータを安全に扱う場合は、「暗号化」と「ハッシュ化」を正しく使い分けることが重要です。

元のデータを後から復元する必要がある場合は暗号化を使用し、ログインパスワードのように元へ戻す必要がない情報にはハッシュ化を使用します。

PHPでは、主にSodiumやOpenSSLを使って暗号化できます。

PHPで使われる主な暗号化・ハッシュ化方法

PHPで利用できる代表的な方法は次のとおりです。

方法主な用途復号
Sodium機密データの暗号化可能
OpenSSLAESなどを使った暗号化可能
password_hash()パスワード保存不可
hash()ハッシュ値の生成不可

新しくPHPで暗号化処理を実装する場合は、認証付き暗号を扱いやすいSodiumが有力な選択肢です。

一方、既存システムとの互換性やAES-GCMなど特定の暗号方式が必要な場合は、OpenSSLが利用されます。

PHPのSodiumでデータを暗号化する方法

PHPではSodium拡張を利用することで、安全性を考慮した暗号化処理を比較的シンプルに実装できます。

共有鍵による認証付き暗号化には、sodium_crypto_secretbox()を利用できます。

sodium_crypto_secretbox()で暗号化する

基本的な暗号化処理は次のようになります。

<?php

$data = '暗号化したいデータ';

// 暗号鍵を生成
$key = sodium_crypto_secretbox_keygen();

// nonceを生成
$nonce = random_bytes(
    SODIUM_CRYPTO_SECRETBOX_NONCEBYTES
);

// 暗号化
$ciphertext = sodium_crypto_secretbox(
    $data,
    $nonce,
    $key
);

// 保存しやすい形式へ変換
$encrypted = base64_encode(
    $nonce . $ciphertext
);

echo $encrypted;

sodium_crypto_secretbox()では、32バイトの暗号鍵と24バイトのnonceを使用します。

nonceは秘密情報ではないため、暗号文と一緒に保存して問題ありません。

ただし、同じ暗号鍵で同じnonceを再利用しないことが重要です。

nonceとは何か

nonceは、暗号化処理ごとに使用する一度限りの値です。

同じデータを同じ鍵で暗号化した場合でも、nonceが異なれば異なる暗号文を生成できます。

PHPでは次のように生成できます。

$nonce = random_bytes(
    SODIUM_CRYPTO_SECRETBOX_NONCEBYTES
);

Sodiumのsecretboxではnonceのサイズが十分大きいため、random_bytes()によるランダム生成が一般的です。

Sodiumで暗号化したデータを復号する方法

暗号化したデータを元に戻す場合は、sodium_crypto_secretbox_open()を使用します。

sodium_crypto_secretbox_open()で復号する

例えば、次のように復号できます。

<?php

$decoded = base64_decode(
    $encrypted,
    true
);

if ($decoded === false) {
    throw new RuntimeException(
        '暗号化データが不正です'
    );
}

$nonceLength =
    SODIUM_CRYPTO_SECRETBOX_NONCEBYTES;

$nonce = substr(
    $decoded,
    0,
    $nonceLength
);

$ciphertext = substr(
    $decoded,
    $nonceLength
);

$plaintext = sodium_crypto_secretbox_open(
    $ciphertext,
    $nonce,
    $key
);

if ($plaintext === false) {
    throw new RuntimeException(
        '復号に失敗しました'
    );
}

echo $plaintext;

sodium_crypto_secretbox_open()は、正しく復号できた場合に元の平文を返します。

暗号文が改ざんされていた場合や、鍵が異なる場合などはfalseを返すため、必ず結果を確認することが重要です。

PHPで使う暗号鍵を安全に管理する方法

暗号化の安全性は、アルゴリズムだけではなく暗号鍵の管理方法にも大きく左右されます。

暗号鍵をPHPファイルへ直接書かない

次のように、暗号鍵をソースコードへ直接記述する方法は避けた方がよいでしょう。

$key = 'my-secret-key';

ソースコードが流出した場合、鍵も同時に知られてしまい、保存している暗号化データを復号される危険があります。

また、暗号鍵をGitなどのリポジトリへコミットしないことも重要です。

環境変数から暗号鍵を取得する

比較的小規模なWebアプリケーションでは、暗号鍵を環境変数などで管理する方法があります。

例えば、Base64形式にした鍵を環境変数へ保存します。

APP_ENCRYPTION_KEY=xxxxxxxxxxxxxxxxxxxxxxxx

PHP側では次のように取得できます。

$keyBase64 = getenv(
    'APP_ENCRYPTION_KEY'
);

$key = base64_decode(
    $keyBase64,
    true
);

if (
    $key === false ||
    strlen($key) !==
    SODIUM_CRYPTO_SECRETBOX_KEYBYTES
) {
    throw new RuntimeException(
        '暗号鍵が不正です'
    );
}

ただし、環境変数に保存すれば必ず安全というわけではありません。

重要度の高いシステムでは、KMSやSecrets管理サービスなどを利用することも検討します。

KMSやVaultを利用する方法

本格的なシステムでは、次のようなサービスを利用して暗号鍵や秘密情報を管理する方法があります。

  • AWS KMS
  • Google Cloud KMS
  • Azure Key Vault
  • HashiCorp Vault

暗号化対象の重要度やシステム規模に応じて、適切な鍵管理方法を選ぶことが大切です。

PHPで暗号鍵を生成する方法

Sodiumでは、専用関数を使って安全な暗号鍵を生成できます。

sodium_crypto_secretbox_keygen()を使う

暗号鍵は次のように生成できます。

$key = sodium_crypto_secretbox_keygen();

環境変数などへ保存しやすくする場合は、Base64へ変換します。

echo base64_encode($key);

生成した鍵は、安全な場所に保存しておきます。

暗号化のたびに新しい鍵を生成すると、以前に暗号化したデータを復号できなくなるため注意が必要です。

OpenSSLでデータを暗号化する方法

PHPでは、OpenSSLを使ってAESなどの暗号方式を利用することもできます。

特にAES-GCMは、暗号化と改ざん検知を組み合わせた認証付き暗号として利用できます。

AES-256-GCMで暗号化する

基本的な例は次のとおりです。

<?php

$data = '暗号化したいデータ';

$key = random_bytes(32);

$cipher = 'aes-256-gcm';

$ivLength = openssl_cipher_iv_length(
    $cipher
);

$iv = random_bytes($ivLength);

$tag = '';

$ciphertext = openssl_encrypt(
    $data,
    $cipher,
    $key,
    OPENSSL_RAW_DATA,
    $iv,
    $tag
);

if ($ciphertext === false) {
    throw new RuntimeException(
        '暗号化に失敗しました'
    );
}

AES-256-GCMでは、主に次の情報が必要です。

  • 暗号鍵
  • IV
  • 認証タグ
  • 暗号文

復号時にはこれらを正しく利用する必要があります。

IV・認証タグ・暗号文を保存する

例えば、JSON形式にするとデータ構造が分かりやすくなります。

$encryptedData = json_encode([
    'iv' => base64_encode($iv),
    'tag' => base64_encode($tag),
    'ciphertext' => base64_encode(
        $ciphertext
    ),
]);

単純に、

$iv . $tag . $ciphertext

のように連結することもできます。

ただし、その場合は復号時にIV・認証タグ・暗号文を正確に分離できるよう、各データの長さを把握しておく必要があります。

PHPでパスワードを安全に保存する方法

ログインパスワードは、SodiumやOpenSSLで暗号化して保存するのではなく、パスワード専用のハッシュ関数を使用するのが基本です。

password_hash()でパスワードを保存する

PHPではpassword_hash()を使用できます。

$password = 'example-password';

$hash = password_hash(
    $password,
    PASSWORD_DEFAULT
);

echo $hash;

生成したハッシュ値をデータベースへ保存します。

password_verify()でパスワードを確認する

ログイン時は、入力されたパスワードと保存済みのハッシュを比較します。

if (
    password_verify(
        $password,
        $hash
    )
) {
    echo 'ログイン成功';
}

パスワードを復号して比較する必要はありません。

パスワードを暗号化しない理由

暗号化は、鍵があれば元のデータを復元できます。

そのため、暗号鍵が流出すると保存しているパスワードも復号される可能性があります。

一方、パスワードハッシュは元のパスワードを復元することを目的としていません。

そのため、パスワード保存では次のように使い分けます。

住所・電話番号・APIトークン
↓
暗号化
↓
必要なときに復号

パスワード
↓
password_hash()
↓
元には戻さない

PHPにおける暗号化とハッシュ化の違い

暗号化とハッシュ化は似ているように見えますが、目的が異なります。

暗号化とは

暗号化は、データを第三者が読めない形へ変換し、必要に応じて元のデータへ復元する仕組みです。

例えば、

山田太郎
↓
暗号化
↓
8Gx9kF0...
↓
復号
↓
山田太郎

のようなイメージです。

氏名、住所、電話番号、APIトークンなど、後で元の値が必要になる情報で利用できます。

ハッシュ化とは

ハッシュ化は、元の値から一定の規則でハッシュ値を生成する処理です。

一般的なパスワード保存では、元の値へ戻すことを前提としていません。

password123
↓
ハッシュ化
↓
$2y$...

ログイン時は元のパスワードへ復元せず、入力された値が保存済みハッシュと一致するかを確認します。

Base64は暗号化ではない

PHPではbase64_encode()がよく利用されますが、Base64は暗号化ではありません。

base64_encode()はエンコード処理

例えば、

$data = '秘密のデータ';

echo base64_encode($data);

とすると、元の文字列とは異なる文字列になります。

しかし、

base64_decode($data);

を使えば簡単に元へ戻せます。

そのため、Base64だけではデータの機密性を守ることはできません。

暗号化とBase64の役割は異なる

Sodiumの例では、

$encrypted = base64_encode(
    $nonce . $ciphertext
);

としています。

この場合の役割は次のように異なります。

sodium_crypto_secretbox()
↓
実際の暗号化

base64_encode()
↓
暗号化結果を文字列として
保存・転送しやすくする

Base64は暗号化したデータを扱いやすくするために利用しているだけです。

SHA-256だけでパスワードを保存しない

次のように単純なSHA-256でパスワードを保存する方法は推奨されません。

$password = hash(
    'sha256',
    $password
);

SHA-256自体が危険なわけではない

SHA-256そのものが使えないという意味ではありません。

SHA-256はさまざまな用途で利用されるハッシュ関数です。

ただし、非常に高速に計算できるため、パスワード保存に単独で使用すると総当たり攻撃への耐性を持たせにくくなります。

パスワードには、専用に設計されたpassword_hash()を利用するのが基本です。

データベースの個人情報を暗号化する方法

例えば、電話番号をSodiumで暗号化してデータベースへ保存することができます。

暗号化関数を作成する

function encryptData(
    string $data,
    string $key
): string {
    $nonce = random_bytes(
        SODIUM_CRYPTO_SECRETBOX_NONCEBYTES
    );

    $ciphertext =
        sodium_crypto_secretbox(
            $data,
            $nonce,
            $key
        );

    return base64_encode(
        $nonce . $ciphertext
    );
}

暗号化した電話番号を保存する

$phone = '09012345678';

$encryptedPhone = encryptData(
    $phone,
    $key
);

$stmt = $pdo->prepare(
    'INSERT INTO users (phone)
     VALUES (:phone)'
);

$stmt->execute([
    ':phone' => $encryptedPhone
]);

データベースには平文の電話番号ではなく、暗号化された文字列が保存されます。

データベースの暗号化データを復号する方法

暗号化したデータを取得したら、同じ暗号鍵を使って復号します。

復号関数を作成する

function decryptData(
    string $encrypted,
    string $key
): string {
    $decoded = base64_decode(
        $encrypted,
        true
    );

    if ($decoded === false) {
        throw new RuntimeException(
            '暗号化データが不正です'
        );
    }

    $nonceLength =
        SODIUM_CRYPTO_SECRETBOX_NONCEBYTES;

    if (
        strlen($decoded) <
        $nonceLength +
        SODIUM_CRYPTO_SECRETBOX_MACBYTES
    ) {
        throw new RuntimeException(
            '暗号化データが短すぎます'
        );
    }

    $nonce = substr(
        $decoded,
        0,
        $nonceLength
    );

    $ciphertext = substr(
        $decoded,
        $nonceLength
    );

    $plaintext =
        sodium_crypto_secretbox_open(
            $ciphertext,
            $nonce,
            $key
        );

    if ($plaintext === false) {
        throw new RuntimeException(
            '復号に失敗しました'
        );
    }

    return $plaintext;
}

使用するときは次のようにします。

$phone = decryptData(
    $row['phone'],
    $key
);

echo $phone;

暗号化時と復号時には、必ず同じ鍵を使用します。

個人情報を暗号化するときの注意点

氏名や電話番号などを暗号化することは有効ですが、すべてのデータを単純に暗号化すればよいわけではありません。

暗号化すると検索しにくくなる場合がある

Sodiumのsecretboxのようにランダムなnonceを利用すると、同じ電話番号を暗号化しても毎回異なる暗号文になります。

そのため、暗号化した電話番号に対して、

WHERE phone = ?

のような単純な完全一致検索を行うことは難しくなります。

個人情報を暗号化するときは、次の点も考慮する必要があります。

  • 検索が必要か
  • 暗号鍵をどこで管理するか
  • データベース自体が暗号化されているか
  • バックアップが暗号化されているか
  • 誰が復号できるか
  • 鍵をどのようにローテーションするか

システム全体のセキュリティ設計として考えることが重要です。

PHPで公開鍵暗号を利用する方法

Sodiumでは共有鍵暗号だけでなく、公開鍵を利用した暗号化も可能です。

sodium_crypto_box()を利用する

sodium_crypto_box()では、送信者と受信者の鍵を組み合わせて暗号化します。

イメージとしては次のようになります。

送信者

送信者の秘密鍵
+
受信者の公開鍵
↓
暗号化

復号するときは、

受信者

送信者の公開鍵
+
受信者の秘密鍵
↓
復号

という形になります。

単純に「公開鍵だけで暗号化し、秘密鍵だけで復号する」という仕組みではない点に注意が必要です。

公開鍵暗号が向いているケース

公開鍵暗号は、送信側と受信側で秘密鍵を共有したくない場合に有効です。

例えば、

  • システム間で安全にデータを送信する
  • 相手へ共通の秘密鍵を渡したくない
  • 送信者と受信者を区別して鍵を管理したい

といったケースで利用できます。

PHPでデータを暗号化するときの注意点

暗号化処理を安全に運用するためには、暗号化関数だけでなく周辺の設計にも注意が必要です。

独自の暗号方式を作らない

独自に考えた暗号方式は、安全性を十分に検証できない可能性があります。

PHPではSodiumやOpenSSLなど、実績のある暗号ライブラリを利用するのが基本です。

鍵をソースコードへ書かない

暗号鍵はPHPファイルへ直接記述せず、環境変数やSecrets管理サービスなどを利用します。

安全な乱数を使用する

nonceやIVの生成には、暗号学的に安全な乱数生成関数を使用します。

PHPではrandom_bytes()が利用できます。

nonceやIVを適切に管理する

暗号方式によって、nonceやIVのサイズや再利用に関する条件が異なります。

利用する暗号方式の仕様に従って生成・保存する必要があります。

認証付き暗号を利用する

単にデータを暗号化するだけでなく、データが改ざんされていないことも確認できる認証付き暗号を利用すると安全性を高められます。

SodiumのsecretboxやAES-GCMなどが代表例です。

復号エラーを必ず確認する

暗号化されたデータが破損している場合や、鍵が異なる場合には復号できません。

復号関数の戻り値や例外を適切に処理することが重要です。

PHPの暗号化方法は目的に応じて使い分ける

PHPには複数の暗号化・ハッシュ化方法があるため、目的に合わせて選ぶ必要があります。

パスワードを保存したい場合

password_hash()
+
password_verify()

を利用します。

個人情報やAPIトークンを暗号化したい場合

Sodium

などの認証付き暗号が有力な選択肢です。

AESとの互換性が必要な場合

OpenSSL
+
AES-GCM

などを利用できます。

公開鍵暗号が必要な場合

sodium_crypto_box()

などを検討します。

データを文字列として扱いやすくしたい場合

base64_encode()

を利用できます。

ただし、Base64自体には暗号化機能はありません。

PHPで安全に暗号化するには鍵管理まで考えることが重要

PHPでデータを暗号化する場合、単純に文字列を読めない状態へ変換するだけでは十分ではありません。

SodiumやAES-GCMなどの信頼できる認証付き暗号を使用し、暗号鍵、nonce、IV、認証タグを適切に管理する必要があります。

また、パスワードには暗号化ではなくpassword_hash()を使用するなど、データの種類によって方式を使い分けることも重要です。

特に実際のWebアプリケーションでは、暗号アルゴリズムだけではなく、鍵の保存場所、アクセス権限、バックアップ、鍵のローテーション、検索要件まで含めて設計することで、より安全なデータ管理につながります。

以上、PHPでデータを暗号化する方法についてでした。

最後までお読みいただき、ありがとうございました。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次