S3に暗号化して画像を保存する

AWS インフラ構築


暗号化の実装案については
①画像を文字列に変換(画像をbase64で文字列化)→②変換した文字列をOpenSSLで暗号化 して文字列をDBで保存

と言う方法もありますが、
今回は文字列変換をDBにする方法ではなくS3に暗号化して保存します。

暗号化の種類

暗号化には2種類あります

  • サーバーサイド暗号化(Server Side Encryption: SSE)
  • クライアントサイド暗号化(Client Side Encryption: CSE)

超重要なファイルを保存する時はSSEとCSEを同時に実装する方法がよさそうです。
アップロード前にCSEで暗号化して、サーバー保存時にSSEで保存する方法です。

S3のSSEについて

S3のSSEは3種類あります。

  • SSE-S3
  • SSE-KMS
  • SSE-C

SSEの実装

SSEの実装ですが、今回はSSE-KMSで実装します。
SSEの設定はS3のバケット作成時に設定します。

サーバーの暗号化有効にするを選択し、
AWS KMSキーに関してはAWS KMS キーから選択するでキーの作成をします。

すすめていくとKMSのアクセスをIAMユーザーにアタッチする事を確認されます。
ここでは、S3にfull acessするIAMユーザーを作成していたので、これにアタッチします。

laravelでアクセスする場合はこのアタッチしたIAMユーザーを使い、アクセスればSSE暗号化をしてアップロードできます。

または、アタッチしなくてもKMSキーをオプション設定すればいけるはずです。(未確認)

CSEの実装

今回はphp+laravelで実装するケースで考えます。その場合は三択

  • laravel+flysystem-aws-s3-v3+AWSSDK for PHPを使いCSE
  • laravel+flysystem-aws-s3-v3+phpのロジックで画像を暗号化( AWSSDK for PHPを使わない)
  • laravel+AWSSDK for PHPを使いCSE(flysystem-aws-s3-v3を使わない)

AWSSDK for PHP

公式ドキュメントによると

https://docs.aws.amazon.com/ja_jp/sdk-for-php/v3/developer-guide/s3-encryption-client.html


クライアント側の暗号化の使用を開始するには、次が必要です。

  • アンAWS KMS暗号化キー
  • S3 バケット

との事です。

こちらの方法でも実装可能のようですが、今回はlaravelで実装しようとおもってましたので、
flysystem-aws-s3-v3
と組み合わせて使う場合はパッケージのカスタマイズが必要そうです。
独自にカスタマイズした場合にパッケージがアップデートした場合に影響がでるのを、きにする場合は暗号化はphpの暗号化でopenssl_encrypt()を使うことで暗号化できそうです。
参考下記

https://qiita.com/zaburo/items/93afa1dd19ad255eab41

ですので、上記でphpで暗号化すればよさそうです。

flysystem-aws-s3-v3を使いカスタマイズする場合は
\vendor\laravel\framework\src\illuminate\Filesystem\FilesystemManager
のcreateS3Driverあたりを改造すればいけそうです。

laravelで暗号化されたファイルをダウンロード

SSE-KMSで暗号化されたファイルをダウンロードするには、一時ダウンロードURLを発行する必要があります。

下記のような関数で、keyのpathを渡して一時URLに変換するのが有効です。

$filepathには保存したファイルがpath付きで入っている想定です。

    public function getFileSecurity($filePath)
    {
        $s3 = Storage::disk('s3');
        $client = $s3->getDriver()->getAdapter()->getClient();
        $filename = basename($filePath); //ファイル名を抜き出し
        $new_filename = urlencode($filename); // ファイル名を指定
        $command = $client->getCommand('GetObject', [
            'Bucket'                        => env('AWS_BUCKET') ,
            'Key'                           => "$filePath" ,
            'ResponseContentDisposition'    => "attachment; filename=\"{$new_filename}\"" , // ファイル名を指定
        ]);
        $expiry = "+10 minutes";
        $request = $client->createPresignedRequest($command, $expiry);
        $signed_url = (string) $request->getUri();
        return $signed_url;
    }

ダウンロードではなく、ブラウザに表示させたい場合は、下記の個所を変更すれば大丈夫です。

 'ResponseContentDisposition'    => "application/json; filename=\"{$new_filename}\""