全体像
SPIRALのAPIから取得したデータを暗号化し、ダウンロード可能な形式に変換します。
ダウンロードしたファイルは、別ページにて復号化し、CSVとしてダウンロードできます。
データを暗号化しダウンロードボタンに引き渡す設定
PHP
<?php //------------------------------ // 設定値 //------------------------------ define("API_URL", "https://api.spiral-platform.com/v1"); define("API_KEY", ""); define("APP_ROLE", ""); define("DB_ID", ""); define("APP_ID", ""); define("ENCRYPT_KEY", "This_Is_A_32-Character_Key_Ex@mp");// 暗号化キー(文字列は32文字で設定、AES-256なので256 bits) //------------------------------ // API実行 //------------------------------ $commonBase = CommonBase::getInstance(); $query = "?limit=200"; //最大200件まで(それ以上取得したい場合はレスポンスのnextOffsetがnullになるまで取得し続けるループ処理を加えてください) // レコードの取得 $resultRecordListSelect = $commonBase->apiCurlAction("GET", "/apps/". APP_ID. "/dbs/". DB_ID. "/records". $query); // JSONデータの取得 $jsonData = json_encode($resultRecordListSelect['items'], JSON_PRETTY_PRINT); // 初期化ベクトル (IV) と暗号化タグを生成 $iv = random_bytes(16); // データを暗号化 $encryptedData = openssl_encrypt($jsonData, 'aes-256-gcm', ENCRYPT_KEY, OPENSSL_RAW_DATA, $iv, $tag); // IVとタグを結合してJSON形式で保存 $encryptedDataWithIvAndTag = base64_encode(json_encode([ 'iv' => base64_encode($iv), 'tag' => base64_encode($tag), 'data' => base64_encode($encryptedData) ])); // 結果を保存 $SPIRAL->setTHValue("response", $encryptedDataWithIvAndTag); //------------------------------ // 共通モジュール //------------------------------ class CommonBase { /** * シングルトンインスタンス * @var UserManager */ protected static $singleton; public function __construct() { if (self::$singleton) { throw new Exception('must be singleton'); } self::$singleton = $this; } /** * シングルトンインスタンスを返す * @return UserManager */ public static function getInstance() { if (!self::$singleton) { return new CommonBase(); } else { return self::$singleton; } } /** * V2用 API送信ロジック * @return Result */ function apiCurlAction($method, $addUrlPass, $data = null, $multiPart = null, $jsonDecode = null) { $header = array( "Authorization:Bearer ". API_KEY, "X-Spiral-Api-Version: 1.1", ); if($multiPart) { $header = array_merge($header, array($multiPart)); } else { $header = array_merge($header, array("Content-Type:application/json")); } if(APP_ROLE){ $header = array_merge($header, array("X-Spiral-App-Role: ".APP_ROLE)); } // curl $curl = curl_init(); curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); curl_setopt($curl, CURLOPT_URL, API_URL. $addUrlPass); curl_setopt($curl, CURLOPT_HTTPHEADER, $header); if ($method == "POST") { if ($multiPart) { curl_setopt($curl, CURLOPT_POSTFIELDS, $data); } else { curl_setopt($curl, CURLOPT_POSTFIELDS , json_encode($data)); } curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $method); } if ($method == "PATCH") { curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($data)); curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $method); } if ($method == "DELETE") { curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($data)); curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $method); } $response = curl_exec($curl); if (curl_errno($curl)) echo curl_error($curl); curl_close($curl); if($jsonDecode){ return $response; }else{ return json_decode($response, true); } } } ?>
- API_KEY、APP_ID、DB_ID、ENCRYPT_KEYを適切な値に置き換えてください。
- ENCRYPT_KEYは英字(大文字、小文字区別有)+数字+記号の32文字の文字列で設定してください。
認証エリア内に作成いただくことで、ユーザごとにランダムを設定することができます。
ユーザごとの暗号化キーを生成する必要があるので、DBトリガのレコードアクションを利用して設定を行ってください。
テキストフィールドに関数設定で RANDOM_NUM_ALP_SYM(32)を記載いただくことで、英数字記号の32文字のランダム値が生成されます。
PHP
$key = $SPIRAL->getAuthRecordByFieldId("3"); //暗号化キーの格納フィールドのフィールドID define("ENCRYPT_KEY", $key);// 暗号化キー(文字列は32文字で設定、AES-256なので256 bits)
暗号化されたデータのダウンロード
上記のPHPコードでは、
暗号化されたデータをThymeleafの変数responseに引き渡しています。
以下のようなJavaScriptコードを記述することで、暗号化されたデータ(txt形式)のダウンロードボタン設置とダウンロード処理を作成できます。
HTMLとJavaScriptをそれぞれのタブへ貼り付けてください。
$SPIRAL->setTHValue("response", $encryptedDataWithIvAndTag);の箇所で、
暗号化されたデータをThymeleafの変数responseに引き渡しています。
以下のようなJavaScriptコードを記述することで、暗号化されたデータ(txt形式)のダウンロードボタン設置とダウンロード処理を作成できます。
HTMLとJavaScriptをそれぞれのタブへ貼り付けてください。
HTML
<button id="download-btn">ダウンロード</button> <th:block th:if="${cp.result.isSuccess}"> <!-- PHP正常完了した場合に表示 --> <p id="response" style="display:none;" th:text="${cp.result.value['response']}"></p> </th:block> <th:block th:if="${!cp.result.isSuccess}"> <!-- PHPにエラー起こった場合に表示 --> <p th:text="${cp.result.errorMessage}">error message</p> </th:block>
JavaScript
document.addEventListener('DOMContentLoaded', () => { // 設定値 const textFileName = 'data.txt'; // ダウンロードされる暗号化されたテキスト名 const responseElement = document.getElementById('response'); const encryptedDataWithIvAndTag = responseElement.textContent; document.getElementById('download-btn').addEventListener('click', () => { try { const blob = new Blob([encryptedDataWithIvAndTag], { type: 'text/plain' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = textFileName; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); } catch (error) { console.error('ファイルダウンロードエラー:', error); alert('ファイルのダウンロード中にエラーが発生しました。'); } }); });
- ダウンロードボタンを押下すると、暗号化されたデータがdata.txtという名前でダウンロードされます。
- ダウンロードが確認出来れば暗号化ファイルダウンロードの設定が完了になります。
復号化
ダウンロードしたファイルを復号化し、CSV形式に変換してダウンロードさせます。
HTML
<label for="key">復号化キー (32文字):</label> <input type="text" id="key" name="key" required><br><br> <label for="file">暗号化されたファイルを選択:</label> <input type="file" id="file" name="file" accept=".txt" required><br><br> <button id="decrypt-btn">CSVとしてダウンロード</button> /*認証エリア内に設置する場合以下の様に認証レコード値を出力 <label for="key">暗号化キー:</label> <span th:text="${siteClient.record[3]}">Example</span><br><br> */
document.addEventListener('DOMContentLoaded', () => { // 設定値 const csvFileName = 'data.csv'; // ダウンロードされる復号化されたCSVファイル名 async function decryptData(encryptedData, key, iv, tag) { const keyBuffer = new TextEncoder().encode(key); const cryptoKey = await crypto.subtle.importKey( 'raw', keyBuffer, { name: 'AES-GCM' }, false, ['decrypt'] ); const decodedEncryptedData = Uint8Array.from(atob(encryptedData), c => c.charCodeAt(0)); const decodedIV = Uint8Array.from(atob(iv), c => c.charCodeAt(0)); const decodedTag = Uint8Array.from(atob(tag), c => c.charCodeAt(0)); const dataWithTag = new Uint8Array(decodedEncryptedData.length + decodedTag.length); dataWithTag.set(decodedEncryptedData); dataWithTag.set(decodedTag, decodedEncryptedData.length); const decryptedArrayBuffer = await crypto.subtle.decrypt( { name: 'AES-GCM', iv: decodedIV, additionalData: new Uint8Array(), tagLength: 128 }, cryptoKey, dataWithTag ); const decryptedText = new TextDecoder().decode(decryptedArrayBuffer); return decryptedText; } document.getElementById('decrypt-btn').addEventListener('click', async () => { const fileInput = document.getElementById('file'); const key = document.getElementById('key').value; if (key.length !== 32) { alert('復号化キーは32文字である必要があります。'); return; } const file = fileInput.files[0]; if (!file) { alert('ファイルを選択してください。'); return; } const reader = new FileReader(); reader.onload = async (e) => { const encryptedDataWithIvAndTag = e.target.result; try { const encryptedObject = JSON.parse(atob(encryptedDataWithIvAndTag)); const iv = encryptedObject.iv; const tag = encryptedObject.tag; const encryptedData = encryptedObject.data; const decryptedData = await decryptData(encryptedData, key, iv, tag); const jsonData = JSON.parse(decryptedData); const csv = convertToCSV(jsonData); const blob = new Blob([csv], { type: 'text/csv' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = csvFileName; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); } catch (error) { console.error('復号化エラー:', error); alert('復号化中にエラーが発生しました。'); } }; reader.readAsText(file); }); function flattenObject(ob) { let toReturn = {}; for (let i in ob) { if (!ob.hasOwnProperty(i)) continue; if (typeof ob[i] === 'object' && ob[i] !== null) { let flatObject = flattenObject(ob[i]); for (let x in flatObject) { if (!flatObject.hasOwnProperty(x)) continue; toReturn[i + '.' + x] = flatObject[x]; } } else { toReturn[i] = ob[i]; } } return toReturn; } function convertToCSV(objArray) { const array = typeof objArray !== 'object' ? JSON.parse(objArray) : objArray; const flattenedArray = array.map(item => flattenObject(item)); let str = ''; let headers = Object.keys(flattenedArray[0]).join(',') + '\r\n'; str += headers; for (const obj of flattenedArray) { let line = ''; for (const index in obj) { if (line !== '') line += ','; line += obj[index]; } str += line + '\r\n'; } return str; } });
- 必要項目を入力の上、「CSVとしてダウンロード」ボタンを押下すると、ファイル選択ダイアログが表示され、
- 復号化されたデータがdata.csvという名前でダウンロードされれば、設定が完了になります。
注意点
- 本記事のコードはサンプルです。
- 実際の運用環境に合わせて適切に修正・確認をお願いします。
- セキュリティに関する情報は常に最新の状態に保ち、適切な対策をお願いします。