研究室内の短期実験：APIキーの暗号化と復号

用途
信頼できる参加者に復号用パスワードを渡し、Cognitionの実験画面からOpenAIを直接呼ぶ構成の準備用です。
参加者の実験スクリプトは別途作成します。このフォルダーはその実装に使う暗号化・復号の補助コードです。
復号用パスワードを知らない人にキーを渡さないことが目的です。復号できる参加者からキーを隠す仕組みではありません。

準備
1. OpenAIで実験専用のプロジェクトと、そのプロジェクトに属するAPIキーを作ります。
2. プロジェクトの月額上限を20米ドルなどに設定し、Enforce a hard limitを有効にします。
   上限はプロジェクト全体に適用されます。APIキー単独・日額の金額上限ではありません。
   強制停止には反映遅延があり、厳密に20ドルで止まる保証ではありません。
3. キーの有効期限を設定し、実験終了後は失効させます。月をまたぐと月額上限はリセットされます。
4. このフォルダーを手元に展開し、prepare.htmlを対応ブラウザで開きます。
   file:でWeb Cryptoが使えない場合は、フォルダー内で次を実行して開きます。
   python3 -m http.server 8765 --bind 127.0.0.1
   http://127.0.0.1:8765/prepare.html
記事への埋め込みでは、同じprepare.htmlを`?mode=demo`または`?mode=key`で開き、該当モードだけを表示します。modeを省略した場合や不正な値の場合は、上部の切替ボタンを含む通常画面になります。modeには固定の表示指定だけを使い、APIキーやパスワードはURLへ入れません。
5. 予算と期限を設定した実験専用キーを入力し、「暗号化する」を押します。
6. 暗号化後、「ファイルをダウンロード」ボタンで`encrypted-api-key.js`を保存し、CognitionのExternal JSに登録します。`experiment.js`はSource code欄へ貼り付けます。
7. 画面に表示された復号用パスワードを参加者へ別途案内します。参加リンクには付けません。
   参加者は実験画面のパスワード入力欄へ貼り付けます。未入力・不正の場合は実験を開始しません。
   公開コードやGist、参加リンクを案内するメッセージにパスワードを含めないでください。

動作を試す
prepare.html上部の「文字列で試す」へ切り替えると、公開のデモ用パスワードと英文で暗号化・復号を確認できます。
JSONと復号結果はデモ欄だけに表示され、実APIキー用の入力欄やJSONには入りません。
このデモはAPIキーではなく、公開の固定パスワードを使うため実験用データには使わないでください。
暗号化後、JSONの下にある「復号用パスワード」欄へ20文字以上の別の値を入力し、「このJSONを復号」を押すと失敗を確認できます。暗号化用パスワードとJSONは変わりません。復号用欄を元の公開値に戻せば、再暗号化せず同じJSONを復号できます。
実APIキーを暗号化する場合は「APIキーの準備」へ戻り、実験専用キーと別途生成されるパスワードを使ってください。

prepare.htmlは外部通信を行いません。crypto.jsとprepare.jsは同じフォルダーから読み込みます。
APIキーや復号用パスワードをlocalStorage、ファイル、ログへ自動保存しません。
暗号化成功後にAPIキー入力欄を空にしますが、ブラウザメモリから完全消去できる保証はありません。
prepare.html/prepare.jsは実施者用です。参加者用コードからAPIキー入力を要求する必要はありません。

暗号化データの形式
PBKDF2で復号用パスワードから暗号の鍵を作り、AES-GCMでAPIキーの暗号化とデータ改変の検出を行います。
ブラウザ標準のWeb Crypto APIを使い、独自の暗号処理は実装しません。
version: 1
kdf: PBKDF2-SHA256
iterations: 600000
cipher: AES-256-GCM
salt: 暗号化ごとに生成する16バイトの乱数（Base64）
iv: 暗号化ごとに生成する12バイトの乱数（Base64）
ciphertext: 暗号文と128ビットの認証タグ（Base64）

Base64はバイト列を文字にするためだけに使います。秘密を守る処理はAES-GCMとPBKDF2です。
復号用パスワードは暗号化データに含めません。準備画面は15バイト（120ビット）の暗号学的乱数をURL-safe Base64で表した20文字を生成します。英大文字・英小文字・数字に`-`と`_`を加えた64種類が使われます。生成値と暗号化JSONはコード表示され、各コピー用ボタンでコピーできます。自動コピーできない場合は文字列が選択されるので、Ctrl+C（Macは⌘+C）でコピーしてください。
短い例示用パスワードをそのまま使わないでください。関数は20文字未満を拒否します。
独自の暗号アルゴリズムを追加せず、ブラウザのWeb Crypto APIを使います。

実験コードからの使用
実験では、公開サンプルの[`experiment.js`](https://github.com/kishiyamat/jspsych-openai-utils/blob/0fa076ec49a1253248a7a7e1a2abf1231161ffc9/experiment.js)を使います。Cognitionへ登録する前に、[共通ライブラリ`feedback-utils.js`](https://github.com/kishiyamat/jspsych-openai-utils/blob/0fa076ec49a1253248a7a7e1a2abf1231161ffc9/dist/feedback-utils.js)を開き、「Download raw file」で保存してください。External JSには保存した`feedback-utils.js`、`encrypted-api-key.js`、[surveyプラグインのJavaScript](https://unpkg.com/@jspsych/plugin-survey@1.0.1)を登録します。External CSSには[surveyプラグインのスタイルシート](https://unpkg.com/@jspsych/plugin-survey@1.0.1/css/survey.css)を登録します。プラグイン欄では`survey`を重ねて選択せず、`html-button-response`と`call-function`を有効にします。`experiment.js`はSource code欄へ貼り付けます。同意しない場合はAPIキーの復号もOpenAIへのリクエストもしません。同意後はセッション開始時に一度だけパスワードを入力して復号し、3条件の課題が終わるまでキーをメモリ内に保ちます。`manual`課題自体ではキーを使わず、OpenAIへのリクエストを行いません。キーはセッション終了時に消去します。
独自の実験コードで共通ライブラリを使う場合は、[共通リポジトリのREADME](https://github.com/kishiyamat/jspsych-openai-utils/blob/0fa076ec49a1253248a7a7e1a2abf1231161ffc9/README.md)にあるAPIと利用例を参照してください。
共通GitHubリポジトリには平文APIキー、暗号化キー、encrypted-api-key.js、パスワード、参加者の回答、実験固有のプロンプトをコミットしないでください。パスワード、APIキー、参加URL全体もjsPsychデータ、ログ、localStorageへ保存せず、APIキーは実行中のメモリで認証に使います。
共通ライブラリはAPIキーの復号・OpenAIとの通信・通信エラーの整理を担い、実験コードは条件・画面・実行順と研究用の記録項目を決めます。実験コードは前の回答をjsPsychのtrialデータから参照し、Cognitionは各trialデータを自動保存します。暗号化補助ツールは研究データを保存しません。

費用と期間
日々の使用量をOpenAIの画面で確認します。利用できる日次の回数・トークン制限はモデルや権限で確認してください。
参加者ブラウザの回数カウントや日付判定だけでは、APIキーの利用を強制的に止められません。
このサンプルには日額の金額上限やAPIキーの有効期限を設定する機能はありません。
OpenAIの設定で管理し、予算や期限が反映されていることを実施者が確認します。
広く募集する実験、長期運用、参加者からもキーを隠したい場合はサーバーによる仲介へ切り替えます。
リンクとパスワードの別送は、リンクの共有だけで復号できる状態を避けるための運用です。
両方を入手された場合や、同じ連絡経路から両方が漏れた場合の保護を保証するものではありません。

公式資料
https://developers.openai.com/api/docs/guides/spend-limits
https://developers.openai.com/api/docs/guides/production-best-practices
https://developers.openai.com/api/docs/guides/text
https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/deriveKey

確認範囲
実際のAPIキーを使わない暗号化・復号と、準備画面のローカル動作を検証しました。
誤った復号値やデータ改変の拒否、Node.jsによる独立した復号、外部通信・保存を行わないことを確認しました。
実験スクリプト、OpenAIへの実接続、課金設定、Cognitionでの保存は別途確認が必要です。
