reviewing-cryptography について
暗号の誤用を見つけるコードレビュー向けスキルです。暗号化、認証、鍵、トークン、パスワード、乱数、TLSを具体的な観点で確認し、攻撃内容と修正策を示すレビューを支援します。
主な機能
- 01認証付き暗号とノンスの確認
- 02鍵管理とローテーションのレビュー
- 03パスワードハッシュと乱数の確認
- 04定時間比較のレビュー
- 05JWT、署名、TLSの検証
- 06プロトコル層の誤用チェック
活用シーン
- 01暗号化・復号コードの監査
- 02トークンとAPI署名方式のレビュー
- 03TLSと証明書検証の評価
- 04パスワードとシークレットの保存確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ trilwu/ secskills --skill reviewing-cryptographyインストール後、Claude Code で/reviewing-cryptographyを入力して読み込まれたか確認します。
詳細
暗号のレビュー
実際のシステムが暗号解析によって破られることはほとんどありません。破られる原因は誤用です。 ノンスの再利用、認証されていない暗号文、早期に戻る比較処理、git にコミットされた鍵などです。誤用がないかレビューし、プリミティブの設計は暗号学者に任せてください。
使用する場面
- 暗号化、復号、署名、検証、ハッシュ処理を行うコードの監査
- 鍵管理、ローテーション、保存方法のレビュー
- TLS/mTLS 設定と証明書検証の評価
- JWT、セッショントークン、API 署名方式のレビュー
- パスワードとシークレットの保存方法の確認
- セキュリティ上重要な値に使われる乱数の品質評価
使用しない場面
- 新しいプリミティブやプロトコルの設計 — コード監査ではなく、暗号学者による正式なレビューが必要です
- CTF で暗号を破る場合 — 別の専門分野です。
solvingを目的とした攻撃系スキルや既知の攻撃ツールを使用してください - 一般的なコードレビュー —
auditing-code-for-vulnerabilitiesを使用してください - パスワードクラッキング —
cracking-passwordsを使用してください
誤用チェックリスト
以下の順に確認してください。ここに挙げる項目は、実際に本番環境の障害を引き起こしています。
1. 暗号文は認証されていますか?
認証なしの暗号化は、最もよく見つかる重大な問題です。 MAC なしの CBC や CTR では、攻撃者が平文を改ざんできます。さらに、復号オラクルがある場合は、平文を復元できます(パディングオラクル)。
Good: AES-GCM, ChaCha20-Poly1305, AES-CBC + HMAC (encrypt-then-MAC)
Bad: AES-CBC alone, AES-ECB (ever), CTR without a MAC, MAC-then-encrypt
rg -n 'AES/ECB|AES\.MODE_ECB|CipherMode\.ECB|"AES"\)' -i
rg -n 'AES/CBC/PKCS5Padding|MODE_CBC|createCipheriv\(.*cbc' -i
CBC が使われている場合は、MAC があるか確認してください。MAC がなければ、暗号文の転送方法にかかわらず問題として報告します。
2. ノンスと IV の扱い
| モード | ルール | 問題 |
|---|---|---|
| GCM / ChaCha20-Poly1305 | (鍵、ノンス)の組み合わせを再利用しない | 壊滅的な結果を招きます。認証鍵が漏えいし、偽造が容易になります |
| CBC | IV は予測不能で、メッセージごとにランダムである必要があります | 選択平文攻撃(BEAST クラス) |
| CTR | 同じ鍵でカウンターを再利用しない | キーストリームの再利用。平文同士の XOR が得られます |
# The classic bug: a fixed or zero IV
rg -n 'iv\s*=\s*(b?["\x27]0|new byte\[\d+\]|bytes\(\d+\)|\[0\]\s*\*)' -i
rg -n 'IvParameterSpec\(new byte\[16\]\)|createCipheriv\([^,]+,[^,]+,\s*["\x27]'
GCM のランダムな 96 ビットノンスは、鍵ごとにおよそ 2^32 件のメッセージまで安全です。 カウンターベースのノンスのほうが安全ですが、カウンターの状態が再起動後も確実に保持され、インスタンス間で重複しない場合に限ります。カウンターがどこに永続化されているか確認してください。「メモリ内」で水平スケーリングしている場合は、再利用につながります。
3. 鍵管理
- 鍵はどこから取得されますか?ハードコード、環境変数、KMS/HSM、派生のいずれですか?
- ハードコードされた鍵、ソース内の鍵、リポジトリにコミットされた設定内の鍵、コンテナイメージ内の鍵、CI ログ内の鍵をすべて確認してください。
- 鍵は用途ごとに分けられていますか?暗号化と署名での鍵の使い回しや、テナント間での使い回しは問題です。
- ローテーションの手段はありますか?ローテーションできないシステムには、侵害への対応手段がありません。
- 派生鍵の場合、適切な KDF を使用していますか(鍵素材には HKDF、パスワードには Argon2id/scrypt/PBKDF2)?パスワードをそのまま SHA-256 でハッシュしても KDF にはなりません。
rg -n 'BEGIN (RSA |EC |OPENSSH )?PRIVATE KEY|-----BEGIN'
rg -n '(secret|api[_-]?key|password|token)\s*[:=]\s*["\x27][A-Za-z0-9/+=]{16,}' -i
gitleaks detect --source . --redact # history matters more than the tree
4. パスワードの保存
Correct: Argon2id (preferred), scrypt, bcrypt, PBKDF2-HMAC-SHA256 with a
high iteration count — each with a per-user random salt
Wrong: MD5, SHA-1, SHA-256, SHA-512 (raw or salted), any unsalted hash,
encryption instead of hashing, a "pepper" as the only defense
コードを書いた当時は十分だった値ではなく、現在の指針に照らしてワークファクターを確認してください。また、検証処理で文字列を比較するのではなく、ライブラリの定時間検証関数を使っているか確認してください。
5. 乱数
トークン、セッション ID、ノンス、ソルト、パスワードリセットコード、IV など、セキュリティ上重要な値には CSPRNG を使用する必要があります。
rg -n 'math/rand|Math\.random\(\)|random\.random\(|rand\(\)|mt_rand|Random\(\)'
# Correct: crypto/rand, secrets.token_bytes, window.crypto.getRandomValues,
# SecureRandom, os.urandom, RandomNumberGenerator
さらに、タイムスタンプや PID によるシード、弱い乱数源に基づく UUIDv1/v4 をシークレットとして使用していないか、推測不能であることが前提の箇所で予測可能な連番 ID を使用していないかも確認してください。
6. タイミングサイドチャネル
シークレットの比較はすべて定時間である必要があります。対象には MAC、トークン、API 署名、パスワードハッシュ、OTP が含まれます。
rg -n 'hmac.*==|token\s*==|signature\s*==|\.equals\(.*(hmac|token|sig)' -i
# Correct: hmac.compare_digest, crypto.timingSafeEqual, subtle.ConstantTimeCompare,
# MessageDigest.isEqual, hash_equals
HMAC を早期に戻る文字列比較で検証するのは、理論上の問題ではなく、現実的なリモート攻撃につながります。
7. 署名の検証
- 署名は実際に検証されていますか。それとも単に解析されているだけですか?
- アルゴリズムはメッセージから取得されていますか?(JWT の
algの混同:noneや、公開鍵が HMAC キーになる RS256→HS256。) - 攻撃者が制御する識別子(
kid、jku、x5u)によって鍵が選択されていますか?これらのフィールドは攻撃者からの入力として扱ってください。 - 署名検証後にクレームを検証していますか?具体的には、
exp、nbf、iss、aud、そして重要な点として、サブジェクトの現在の認可状態です。
rg -n 'jwt\.decode\(|verify\s*[:=]\s*(False|false)|algorithms\s*=\s*\[?["\x27]?none' -i
rg -n 'InsecureSkipVerify|verify\s*=\s*False|CURLOPT_SSL_VERIFYPEER.*0|rejectUnauthorized:\s*false'
8. TLS の設定
# Server side
testssl.sh --severity MEDIUM https://target
sslyze --regular target:443
nmap --script ssl-enum-ciphers -p 443 target
# Look for: TLS < 1.2, RC4/3DES/NULL/EXPORT ciphers, no forward secrecy,
# weak DH params, expired or misissued certs, missing HSTS
クライアント側は、サーバー側よりも誤った設定になっていることがよくあります。証明書の検証が有効になっていること、ホスト名の検証が有効になっていること(複数のライブラリでは、証明書チェーンの検証とは別の機能です)、カスタムの信頼ストアが気付かれないままあらゆる証明書を受け入れていないことを確認してください。例外をスローせずに戻るカスタムの TrustManager は、Java では「TLS がまったくない」ことを意味します。
9. 耐量子計算機への対応状況
機密性を長期間維持する必要があるものについては、今収集して後で復号する攻撃への露出と、スタックでハイブリッド鍵交換(例:X25519 + ML-KEM)が利用可能かどうかを記載してください。これは通常、緊急ではなくロードマップ上の指摘です。その旨を明記し、省略しないでください。
プロトコルレベルの質問
基本要素に加えて、次の点を確認してください。
- リプレイ:nonce、タイムスタンプ、またはシーケンス番号があり、実際にチェックされ、保存されていますか?
- バインディング:署名は、受信者やコンテキストを含むメッセージ全体に結び付けられていますか?署名の削除や、別のプロトコルでの再利用は、署名対象の範囲が狭すぎることから生じます。
- ダウングレード:クライアントやサーバーが、より弱いモードにネゴシエートされる可能性はありますか?
- オラクル:エラー処理で「パディング不正」と「MAC 不正」、または復号失敗と認可失敗を区別していますか?タイミングや応答サイズを含め、観測可能な違いはすべてオラクルになります。
- 正規化:署名がシリアル化された構造を対象としている場合、同じ構造を異なる方法でシリアル化できますか?JSON や XML の署名方式では、これがたびたび問題になります。
退けるべき言い訳
- 「暗号化されています。」 暗号化されていても、認証されているとは限らず、認可されているとも限りません。
- 「AES-256 を使っているので、強固です。」 鍵のサイズが弱点になることは、ほとんどありません。
- 「IV は十分にランダムです。」 どこで、何を使って生成しているのかを示してください。
- 「ネットワーク経由でのタイミング攻撃は現実的ではありません。」 現実に可能であり、インターネット上で繰り返し実証されています。
- 「ライブラリが使いにくかったので、独自実装しました。」 独自の暗号実装は、それ自体が指摘事項です。代わりに使うべきライブラリを明記してください。
- 「鍵はコードではなく、環境変数にあります。」 そのほうがよいですが、環境変数がどこで設定されるか、誰がプロセスの環境を読み取れるか、ログやクラッシュダンプ、コンテナイメージに含まれていないかを確認してください。
- 「証明書のピン留めは面倒すぎます。」 それでも構いません。ただし、検証を無効にするのではなく、受容したリスクとして明記してください。
- 「内部トラフィックです。」 ラテラルムーブメントが発生するのは、内部ネットワークです。
成果物
指摘事項ごとに、関係する基本要素またはプロトコル、具体的な誤用、それによって可能になる具体的な攻撃(「暗号が弱い」ではなく)、影響を受けるデータとその機密性を維持すべき期間、そして具体的に正しい構成(ライブラリ名、モード名、パラメーター名)を記載してください。「強力な暗号化を使う」と推奨する暗号関連の指摘事項は、修正済みとは認められません。
外部ソースの読み込み
公開されている勧告、仕様、ベンダーレポートを Markdown として取得します。
curl -sL "https://defuddle.md/<url>" # scheme in the path is optional
ページの定型部分を取り除くため、文章中心のページではトークン数を約 78%削減できます。また、要約ではなく全文を返すため、grep で検索でき、該当箇所がないという結果も信頼できます。
この用途に適さないものが 3 つあります。JSON や API レスポンスは、可読性向上のための抽出によって構造化データが損なわれるため、生のまま取得してください。認証が必要なページや JavaScript で描画されるページは、匿名で取得されるため、直接取得してください。また、攻撃者のインフラ(フィッシングリンク、C2、マルウェアのホスティング)、クライアント所有のホスト、エンゲージメント URL は、絶対にこの経由で取得しないでください。リクエストが第三者に送信されるうえ、稼働中の攻撃者インフラの場合は運営者に気付かれるおそれもあります。
サイトによっては抽出ツールをブロックし、ページではなくエラーの内容を返すことがあります。たとえば、freedesktop.org からは {"error":"Failed to fetch: 418 I'm a teapot"} が返されます。これは取得が拒否されたという意味であり、ソースがその情報の存在を否定しているわけではありません。結論を出す前に、URL を直接再取得してください。
参考資料
auditing-code-for-vulnerabilities— 関連するコードレビューcracking-passwords— 脆弱なパスワード保存方式の攻撃者側の視点testing-apis— API レイヤーでのトークンと署名の処理- 推奨ライブラリ:libsodium/NaCl、Google Tink、
age、プラットフォームの AEAD API testssl.sh、sslyze、cryptography(Python)の監査 API、cargo-crev