開発情報・ナレッジ

投稿者: ShiningStar株式会社 2024年9月25日 (水)

SPIRALのデータを暗号化・復号化してダウンロードさせる方法

全体像

SPIRALのAPIから取得したデータを暗号化し、ダウンロード可能な形式に変換します。
ダウンロードしたファイルは、別ページにて復号化し、CSVとしてダウンロードできます。

データを暗号化しダウンロードボタンに引き渡す設定

PHP

<?//<!-- SMP_DYNAMIC_PAGE DISPLAY_ERRORS=OFF NAME=XXX -->>?>
<?php
//------------------------------
// 設定値
//------------------------------
define("DB_TITLE", "");
define("ENCRYPT_KEY", "This_Is_A_32-Character_Key_Ex@mp");// 暗号化キー(文字列は32文字で設定、AES-256なので256 bits)
/**認証エリア内にページを作成する場合は以下を使用
$authKey = $SPIRAL->getContextByFieldTitle("暗号化キー格納フィールドのフィールドタイトル");
define("ENCRYPT_KEY", $authKey);
**/
$SelectFields = ["name", "pref", "age1"]; //取得したいフィールドの差替えキーワードをカンマ区切りで記載してください。

//------------------------------
// API実行
//------------------------------
// レコードの取得
$db = $SPIRAL->getDataBase(DB_TITLE);
//出力対象のフィールドを指定してください
$db->addSelectFields(...$SelectFields);
/*レコードの絞り込みを行いたい場合は各種検索条件を指定してください
$db->addEqualCondition("field_title", "value");
*/
$db->setLinesPerPage(1000);
$result = $db->doSelect();
// JSONデータの取得
$jsonData = json_encode($result['data'], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
//var_dump($jsonData); //取得した内容をデバッグしたい時
// 初期化ベクトル (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)
]));

?> 

  • DB_TITLE、ENCRYPT_KEYを適切な値に置き換えてください。
  • ENCRYPT_KEYは英字(大文字、小文字区別有)+数字+記号の32文字の文字列で設定してください。

マイエリア内に作成いただくことで、ユーザごとにランダムを設定することができます。
ユーザごとの暗号化キーを生成する必要があるので、ログインDBのトリガを利用して設定を行ってください。

英数字記号の32文字のランダム値を生成する必要がありますが、SPIRAL Ver.1ではデフォルトの機能だと32文字のランダム値を生成する事ができません。
ですので、16桁のフィールド値自動生成トリガを2つ生成した後にその2つを結合するフィールドを用意しトリガにて文字列結合してください。
例:
フィールド名 フィールドタイプ 備考
暗号化キー生成用フィールド1 簡易パスワード フィールド値自動生成トリガ:数字・記号・アルファベット16桁
暗号化キー生成用フィールド2 簡易パスワード フィールド値自動生成トリガ:数字・記号・アルファベット16桁
暗号化キー 数字・記号・アルファベット(32 bytes) 新規登録トリガ:暗号化キーフィールド = 暗号化キー生成用フィールド1 || 暗号化キー生成用フィールド2

PHP

$authKey = $SPIRAL->getContextByFieldTitle("暗号化キー格納フィールドのフィールドタイトル");
define("ENCRYPT_KEY", $authKey); 

認証エリア内にページを作成する場合は設定値のキーの部分を上記のように変更してください。

暗号化されたデータのダウンロード

以下のようなJavaScriptコードを記述することで、暗号化されたデータ(txt形式)のダウンロードボタン設置とダウンロード処理を作成できます。
HTMLとJavaScriptをそれぞれのタブへ貼り付けてください。

HTML

<button id="download-btn">ダウンロード</button>


JavaScript
<script>
	document.addEventListener('DOMContentLoaded', () => {
    // 設定値
    const textFileName = 'data.txt'; // ダウンロードされる暗号化されたテキスト名

    const encryptedDataWithIvAndTag = "<?echo $encryptedDataWithIvAndTag?>"

    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('ファイルのダウンロード中にエラーが発生しました。');
        }
    });
  });
</script>

  • ダウンロードボタンを押下すると、暗号化されたデータがdata.txtという名前でダウンロードされます。
  • ダウンロードが確認出来れば暗号化ファイルダウンロードの設定が完了になります。

復号化

ダウンロードしたファイルを復号化し、CSV形式に変換してダウンロードさせます。

HTML

<?//<!-- SMP_DYNAMIC_PAGE DISPLAY_ERRORS=OFF NAME=XXX -->?>
<label for="key">復号化キー (32文字):</label>
<input type="text" id="auth-key-input" readonly value="<?php echo htmlspecialchars($SPIRAL->getContextByFieldTitle("authKey"), ENT_QUOTES, 'UTF-8'); ?>"><br><br>
<!--マイエリア内の認証値ではなく手動入力で復号化キーを入力させる場合
<input type="text" id="auth-key-input" value=""><br><br>
-->
<label for="file">暗号化されたファイルを選択:</label>
<input type="file" id="file" name="file" accept=".txt" required><br><br>

<button id="decrypt-btn">CSVとしてダウンロード</button>
JavaScript
<script>
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('auth-key-input').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;charset=UTF-8' });
        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;
  }

  // CSVの値をエスケープする関数
  function escapeCSVValue(value) {
    if (typeof value === 'string') {
      // ダブルクオートをエスケープし、必要に応じて値をダブルクオートで囲む
      if (value.includes('"') || value.includes(',') || value.includes('\n')) {
        return `"${value.replace(/"/g, '""')}"`;
      }
    }
    return value;
  }

  // オブジェクト配列をCSV形式に変換する関数
  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 += escapeCSVValue(obj[index]);
      }
      str += line + '\r\n';
    }

    // BOM (Byte Order Mark) を付与して、UTF-8として保存
    return '\uFEFF' + str;
  }
});

</script>

  • 必要項目を入力の上、「CSVとしてダウンロード」ボタンを押下すると、ファイル選択ダイアログが表示され、
  • 復号化されたデータがdata.csvという名前でダウンロードされれば、設定が完了になります。

注意点

  • 本記事のコードはサンプルです。
  • 実際の運用環境に合わせて適切に修正・確認をお願いします。
  • セキュリティに関する情報は常に最新の状態に保ち、適切な対策をお願いします。
解決しない場合はこちら コンテンツに関しての
要望はこちら