S3に暗号化して画像を保存する
暗号化の実装案については
①画像を文字列に変換(画像を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
公式ドキュメントによると
クライアント側の暗号化の使用を開始するには、次が必要です。
- アンAWS KMS暗号化キー
- S3 バケット
との事です。
こちらの方法でも実装可能のようですが、今回はlaravelで実装しようとおもってましたので、
flysystem-aws-s3-v3
と組み合わせて使う場合はパッケージのカスタマイズが必要そうです。
独自にカスタマイズした場合にパッケージがアップデートした場合に影響がでるのを、きにする場合は暗号化はphpの暗号化でopenssl_encrypt()を使うことで暗号化できそうです。
参考下記
ですので、上記で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}\""