crypto-audit について
crypto-auditは、アプリケーションのコードや設定における暗号実装を調べるセキュリティスキルです。アルゴリズム、パラメーター、鍵やnonceの扱い、署名、乱数、TLS、鍵のライフサイクルに潜む問題を確認します。
主な機能
- 01アルゴリズムと暗号モードの確認
- 02鍵導出とnonce処理の検査
- 03認証付き暗号と署名の確認
- 04乱数とTLS設定の評価
- 05鍵管理とフレームワークパターンの確認
- 06検証済みの問題と強化案の記録
活用シーン
- 01アプリの暗号方式の監査
- 02パスワードハッシュと鍵導出の確認
- 03JWTとwebhook検証のレビュー
- 04TLSと鍵管理設定の評価
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ briiirussell/ cybersecurity-skills --skill crypto-auditインストール後、Claude Code で/crypto-auditを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/findings-format.mdfindings.schema.jsonseverity-calibration.mdto-sarif.mjsvalidate-findings.mjsverification.md詳細
暗号監査 — 暗号実装レビュー
アプリケーションで暗号がどのように実装されているかを監査します。アルゴリズムの選択、パラメーター、モード、そして優れたプリミティブを脆弱なシステムに変えてしまう実装パターンを確認します。明白な「パスワードにMD5」や「VERIFY_NONE」のケースを検出する owasp-audit A04「暗号化の失敗」よりも踏み込んだ監査です。このスキルは、より微妙な実装上の問題をレビューするためのものです。
暗号の失敗のほとんどは、「MD5を使っていた」ことが原因ではありません。多くは、正しいプリミティブを誤ったモードで使う(GCMではなくECB)、正しいアルゴリズムを誤ったパラメーターで使う(2026年にPBKDF2の反復回数が1,000回)、正しいライブラリを誤った呼び出し順序で使う(データの読み込み後に暗号を初期化する)ことが原因です。
相互参照:owasp-audit A04(基本)+A07(タイミングセーフな比較)、secrets-audit(鍵の保管)、iam-audit(KMS/HSMのパターン)。
認可
ユーザー自身が所有する、または監査を許可されているコード、設定、鍵管理の設定をレビューします。TLSのプロービング(testssl.sh、openssl s_client)は実環境に対するチェックです。ユーザーがテストを許可したエンドポイントに対してのみ実行してください。事前承認されていないため、実行のたびに確認を求めます。アクセス権のない鍵の復元、ハッシュの解読、データの復号は決して試みないでください。
独自実装を避ける
カスタム暗号方式に対する監査のデフォルト判定は、すべて「代わりにlibsodium/Tink/WebCryptoを使う」です。暗号を安全に新規設計できる人は世界中に50人未満で、その人たちはあなたの会社では働いていません。明示的な脅威モデルに別段の定めがない限り、カスタム暗号は指摘事項です。
監査チェックリスト
アルゴリズムとモード
- 共通鍵暗号: AES-256-GCMまたはChaCha20-Poly1305(認証付き暗号化。機密性と完全性を1つのプリミティブで実現)
- 拒否対象: AES-ECB(ブロックパターンが漏えいする。同一の平文 → 同一の暗号文)、HMACを使わないAES-CBC(認証されず、パディングオラクル攻撃を受ける)、HMACを使わないAES-CTR(改ざん可能。ビット反転 = 平文の反転)、DES/3DES、RC4、Blowfish(Twofishを使うか、暗号化自体を省略)
- 公開鍵暗号: 署名と鍵交換にはEd25519/X25519を使用します。互換性のためにRSAが必要な場合は、RSA-OAEP/RSA-PSSを3072ビット以上で使用します。暗号化にPKCS#1 v1.5パディングを使うRSAは決して使用せず(Bleichenbacher攻撃)、生のRSAも使用しないでください
- ハッシュ(汎用): SHA-256、SHA-3、BLAKE2/BLAKE3
- ハッシュ(パスワード)—まったく異なる問題です: Argon2id、scrypt、bcrypt(bcryptではコストを12以上に設定。OWASP 2024の最低基準)
- MAC: 最低でもHMAC-SHA256を使用します。CBC-MACや独自実装の
hash(key + message)は決して使用しないでください - 次の文字列をgrepで検索します:
MD5、SHA1(レガシー互換でのHMAC-SHA1を除く)、DES、RC4、Blowfish、AES.*ECB、pkcs1_v1_5(Python)、OAEPを使わないRSA.encrypt
鍵導出
- パスワードから: メモリハードなArgon2id、または反復回数600,000回以上のPBKDF2-HMAC-SHA256(OWASP 2024)、またはN=2^17、r=8、p=1のscryptを使用します
- 高エントロピーの秘密情報から: HKDF-SHA256を使用します。鍵素材があり、サブキーを導出する必要がある場合に適したプリミティブです
- 低エントロピーの秘密情報から暗号化鍵を導出する場合: PBKDF2/Argon2を使用します(パスワードとして扱います)
- 次の文字列をgrepで検索します:
PBKDF2(反復回数を確認)、HKDF、Argon2、scrypt、pbkdf2_hmac(Python。iterations引数を確認)
IV/ノンスの取り扱い
IV/ノンスの誤った取り扱いは、「暗号化は正しく見えるのに、実際には平文が漏えいしている」原因の上位3つに入ります。
- **AES-GCM:**同じ鍵を使う場合、暗号化ごとに一意のノンスを使用してください。**絶対に再利用しないでください。**同じ鍵でGCMのノンスを再利用すると、攻撃者に2つの平文のXORとメッセージを偽造する能力を与えてしまいます。96ビットのランダムなノンスを使用してください(RFC 5116)。大量のデータを扱うシステムでは、AES-GCM-SIV(ノンスの誤用に耐性があります)に切り替えてください
- **AES-CBC:**IVは予測不可能かつ一意でなければなりません。暗号化ごとにランダムな16バイトのIVを使用してください
- **AES-CTR:**鍵の有効期間中、(鍵、カウンター)の組み合わせを繰り返してはなりません
- **ChaCha20-Poly1305:**メッセージごとに一意のノンスを使用してください。XChaCha20-Poly1305は192ビットのノンスを持つため、大規模な環境でもランダムなノンスを安全に使用できます
- 次をgrepしてください:ハードコードされたIV(
iv = "0000000000000000"、iv = bytes(16))、ゼロIVのコンストラクター、カウンターのリセット - 特に次をgrepしてください:
Cipher.getInstance("AES")(JavaのデフォルトはECB)、AES.new(key)(PyCryptodomeのデフォルトはECB)、crypto.createCipher(Nodeでは非推奨で、鍵からIVを導出します。使用しないでください)
認証付き暗号化
- 必ず認証付きモードを使用してください。AES-GCM、ChaCha20-Poly1305、またはEncrypt-then-MAC(暗号文に対するHMAC-SHA256)を使用します
- MACを確認してから復号するのではなく、必ずMACを確認してから復号してください(逆の順序ではパディングオラクルが発生します)
- Encrypt-then-MACを使用する場合は、暗号化と認証に別々の鍵を使用してください(または、HKDFで1つのマスター鍵から両方を導出します)
- 定数時間比較(Nodeでは
crypto.timingSafeEqual、Pythonではhmac.compare_digest、Goではsubtle.ConstantTimeCompare)を使ってMACを検証してください。owasp-auditA07を参照してください
署名の検証
- 署名検証で最もよくあるバグは、アルゴリズムの欠陥ではなく、署名をまったく確認しないこと、またはメッセージ自体からアルゴリズムを取得して確認することです
- JWT:
algがコードで想定している値と完全に一致することを検証してください。jwt.verifyを使わずにjwt.decodeを呼び出し、クレームを使用してはなりません。多くのライブラリはデフォルトでalg: none(署名なし)を受け入れます。使用しているライブラリのバージョンにこの問題がないことを確認してください - **Webhookの署名:**本文に対してほかの処理を行う前に、署名を検証してください。多くの実装は解析してから検証するため、JSON解析を悪用される攻撃対象領域が残ります
- 署名のバイト列には定数時間比較を使用してください
- タイムスタンプの許容範囲を設定してください。署名は「永久」ではなく、前後5分以内のものを受け入れます(Stripe、GitHub、Slackはいずれもこの方式です)(リプレイ攻撃対策)
- 署名処理における
owasp-auditA04の型強制(parseInt → NaN)を参照してください - 次をgrepしてください:
jwt.decode(後続にverifyがないもの)、alg: 'none'、近くにtimingSafeEqualがないverify.*sig
乱数
- 使用するもの:
crypto.randomBytes(Node)、secrets.token_bytes(Python ≥ 3.6)、crypto/rand(Go)、SecRandomCopyBytes(iOS)、SecureRandom(Java) - 使用してはならないもの:
Math.random()(Node — メルセンヌ・ツイスタであり、予測可能です)、random.random()(Python — 同様です)、rand()(C — 非常に危険です)、暗号用途でのarc4random_uniform(BSD — 名前に歴史的な名残があるだけですが、実行環境を確認してください) - UUIDには、CSPRNGを使ったUUID v4を推奨します(多くの言語の標準ライブラリは正しく処理します。重要な場合は、実装を読んで確認してください)
- トークン/セッションIDの最小エントロピーは128ビット(ランダムな16バイトをbase64にすると22文字)が下限です。256ビットが妥当なデフォルトです
- 次をgrepしてください:
Math.random、random.random、rand(、mt_rand(PHP)、Random.new(Ruby)
TLS の設定
- バージョン: TLS 1.3 を推奨し、TLS 1.2 を最低バージョンとします。TLS 1.0/1.1/SSLv3/SSLv2 は拒否します
- 暗号スイート(TLS 1.2): ECDHE のみ、AEAD 暗号のみ(AES-GCM、ChaCha20-Poly1305)とします。CBC、RC4、NULL、EXPORT、匿名暗号は拒否します
- 証明書の検証:
VERIFY_PEERを使用し、証明書チェーン全体を検証して、ホスト名のチェックを有効にします(マネージドサービスに関する注意点はowasp-auditA04 を参照) - 証明書のピンニング: 信頼度の高い接続(モバイルアプリからバックエンドへの接続、機密性の高い内部サービスなど)で使用します。バックアップ用のピンも設定します(ローテーション用)
- HSTS プリロード: すべてのサブドメインで最初から HTTPS が提供されることを確認します。プリロードの申請は簡単には取り消せません(解除に数か月かかります)
- パフォーマンスとプライバシーのために OCSP ステープリング を使用します
- 次のコマンドでテストします:
testssl.sh https://targetまたはsslyze --regular target
鍵のライフサイクル
- 鍵の保管場所: HSM/クラウド KMS(AWS KMS、GCP Cloud KMS、Azure Key Vault、HashiCorp Vault Transit)を使用します。アプリケーションは鍵のマテリアルに触れることなく、暗号化/復号を要求します
- エンベロープ暗号化: レコードごとのデータ鍵を顧客のマスター鍵でラップします。これにより、単一のデータ鍵が漏えいした場合の被害範囲を限定できます
- ローテーション: マスター鍵は毎年(またはプロバイダーのデフォルトに従って)ローテーションします。データ鍵はレコードごとに作成するため、ローテーションは不要です。侵害が疑われる場合にのみ再暗号化します。KMS プロバイダーで設定すれば、これらはプロバイダーが処理します
- 鍵のバージョン管理: すべての暗号文に、暗号化に使用した鍵 ID を記録します。これにより、ローテーション後も適切な鍵を特定して復号できます
- 失効: 侵害された鍵を使った復号をどう停止しますか?対策が用意され、文書化され、テストされていますか?
フレームワーク別の具体的なパターン
- Rails:
MessageVerifier/MessageEncryptorはデフォルトで最新のプリミティブを使用します。強度の高い鍵(32 バイト/256 ビット)が設定されていることを確認します - Django:
cryptography.fernetは AES-128-CBC + HMAC-SHA256 を使用します(許容範囲ですが、GCM ではありません)。短い署名付き値にはdjango.core.signingを使用します - Node/Express: 独自実装するより、
iron-session/cookie-signatureを優先します - iOS: 最新の Swift コードには CryptoKit を使用します。
CommonCryptoも使用できますが、誤用につながる落とし穴がより多くあります - Android: 高水準ライブラリとして Tink(Google)が推奨されます。JCA を直接使う場合、歴史的な経緯から AES-ECB がデフォルトになっていることがあります
検証してから記録する
チェックリストの各候補について、references/findings-format.md の共通形式を使って、判定を 1 つ記録します。confirmed とする前に、references/verification.md にある反証確認を行います。呼び出し箇所を開き、鍵、IV、ソルト、パラメーターが実際にどこから取得されるかを確認し、使用中のライブラリのバージョンがすでに安全なデフォルトを適用しているかを確認します。
弱いプリミティブを指摘事項とするには、誰が何を得るのかを明確に示せる必要があります。つまり、信頼度の低い主体(暗号文を見る人、漏えいしたハッシュテーブルを持つ人、トークンを送信するクライアント、通信経路上のネットワーク攻撃者)、それによって破られる制御(機密性、完全性、認証、パスワードの秘匿性)、そしてその結果です。保護されているデータに基づいて、references/severity-calibration.md を使って重大度を評価します。「攻撃者がすでにユーザーテーブルのコピーを持っている」などの条件は conditions[](data_state)に記載します。これらの条件は影響度ではなく、発生可能性を下げます。
confirmed — コードに脆弱な使い方が示されており、その結果も裏付けられています。例:
src/lib/vault.tsのcreateCipheriv('aes-256-gcm', key, FIXED_IV)では、すべてのレコードに同じ鍵を使っています。暗号化されたカラムを読める人は誰でも、平文同士の XOR を取得し、暗号文を偽造できます。2 つの値を暗号化し、キーストリームが再利用されていることを示す単体テストでは、reproduced: trueが設定されます。src/lib/session.tsのMath.random()によるセッショントークン:あらゆるユーザーのセッションで、予測可能なトークンが生成されます。jwt.sign(..., process.env.JWT_SECRET || 'dev-secret'):system_configuration条件(「JWT_SECRETを設定していないデプロイ」)と確信度mediumにより、確認済みです。リポジトリを読める人なら誰でも、該当するデプロイ向けのトークンを生成できます。- ソルトなしの SHA-256、またはパスワードに対して反復回数が 1,000 回の PBKDF2。ハッシュテーブルが漏えいした場合の
data_state条件も含めます。
needs_validation — コードからは可能性が示されていますが、事実確認の一部がコードの外にあります。例:
nginx.confではTLSv1 TLSv1.1が許可されていますが、先に CDN やロードバランサーが TLS を終端している可能性があります。確認が必要な点:どの設定がapi.example.comに適用されるか。計画:testssl.sh --protocols https://api.example.com(ライブチェック。実行には許可が必要です)。TLS チェックについて記述されていても、実行されていなければこの状態にします。- 鍵ポリシーが別のリポジトリにある、KMS でラップされたデータキー。確認が必要な点:どのプリンシパルが
kms:Decryptを持つか。計画:aws kms get-key-policy --key-id <id> --policy-name default。
rejected — 明示された場所にある特定の制御によって、該当しないと判断できます。例:
algorithmsオプションなしのjwt.verify(token, key)。ライブラリによって該当しないと判断されます。controlは「jsonwebtoken 9.0.2 のverify()は、非対称鍵に対するalg: noneと HS/RS 混同を拒否する」、ruled_out_by.locationは 9.0.2 を固定するpackage-lock.jsonの行です。src/lib/etag.tsのcreateHash('md5')。キャッシュの ETag のみに使用されています。ruled_out_by.locationは唯一の呼び出し箇所で、レスポンスヘッダーを設定するだけで、信頼に関する判断は行いません。
強化に関する注意事項。レコードにはしません: 追跡できる露出がない、推奨より弱い設定。例:コスト 12 ではなく 10 の bcrypt、正しく実装された AES-CBC による Encrypt-then-MAC(GCM を推奨)、AEAD スイートと並んで提供される TLS 1.2 CBC スイート、OCSP ステープリングなし、KMS 鍵のローテーションスケジュールなし、HSTS preload の欠落。何を保護できていないかを追跡できる場合、独自暗号は検出事項になります。そうでなければ、「パッチではなく置き換える」強化に関する注意事項です。
ライブおよびクラウドの証拠。 TLS スキャンや KMS または HSM の設定が示すのは、ファイルではなくデプロイ済みのリソースです。読み取り専用の出力を機密情報を伏せて <audit dir>/evidence/<name>.json または .txt に保存し(testssl.sh --jsonfile、sslyze --json_out、aws kms describe-key)、location と evidence[] でそのファイルと行を参照します。鍵の素材、秘密鍵、復号した平文、シークレット値は決して保存しないでください。
フィンガープリント: 行番号を含めず、crypto-audit/<class>/<file or resource>/<symbol or name> の形式にします。
crypto-audit/gcm-nonce-reuse/src/lib/vault.ts/encryptFieldcrypto-audit/CWE-338/src/lib/session.ts/newSessionIdcrypto-audit/tls-legacy-protocol/host/api.example.com/443crypto-audit/kms-key-policy/aws/123456789012/us-east-1/alias/app-data
host:port は host/<host>/<port> とし、ARN はスラッシュ区切りで記述します。バリデーターは :<digits> のセグメントを拒否します。
ext キー(任意。任意のレコードで使用可能):
| キー | 値 | 例 |
|---|---|---|
primitive | 使用されているアルゴリズム、モード、パラメーター | "AES-256-GCM, static 96-bit IV"、"PBKDF2-HMAC-SHA256, 1000 iterations" |
library | 処理を実行するライブラリとバージョン | "node:crypto (Node 20)"、"pycryptodome 3.20.0" |
standard | 使用方法が逸脱しているガイダンス | "NIST SP 800-38D 8.2"、"RFC 8996"、"OWASP Password Storage" |
次に、findings.json をフィンガープリント順に並べて作成し、検証します。必要に応じて SARIF にエクスポートできます。
node references/validate-findings.mjs findings.json
node references/to-sarif.mjs findings.json -o findings.sarif
修正を実行時に検証する
- 変更のたびに暗号化/復号の往復テストを行います。暗号化の変更で発生する障害は、気付かれないデータ破損です
- アルゴリズムを変更する場合(例:bcrypt → Argon2id)、次回のユーザーログイン時に移行する計画を立てます(認証フロー中に平文から再ハッシュします)。移行が完了するまでは、古いハッシュも読み取れる状態にしておく必要があります
- TLS を変更する場合は、
testssl.shとcurl --tlsv1.3 --tls-max 1.3 https://targetで検証します(TLS バージョンの下限を適用します) - KMS を変更する場合は、IAM 権限で暗号化と復号の両方を実行できることを検証します(よくある展開時のバグは、データを暗号化できても、元に戻す復号ができないことです)
出力形式
まず findings.json を作成して検証します。それを基に、ハードニングに関する注意事項のリストを加えてレポートを作成します。説明文によって、JSON に記載されていない判定、重大度、阻害要因が変わることはありません。
# Cryptography Implementation Audit
## Project: [name] ## Date: [date]
## Scope: [components covered; full | partial — what was left out, e.g. "no TLS scan run"]
## findings.json: [path] — validated: [yes]
### Summary
- Confirmed: [N] (Critical [n] | High [n] | Medium [n] | Low [n]) Needs validation: [N] Rejected: [N] Hardening notes: [N]
### Confirmed findings
#### [SEVERITY] [Title] `fingerprint`
**Location:** `path:line` **CWE:** [CWE-n] **Confidence:** [medium | high (reproduced)] **Primitive:** [ext.primitive]
**What and why it matters:** [principal, control defeated, result]
**Conditions:** [e.g. data_state: attacker holds the users table]
**Vulnerable code:** [snippet]
**Fix:** [concrete change, including migration of existing ciphertext or hashes]
**Verify the fix:** [round-trip test, rehash on login, testssl output; see Verify Fixes at Runtime]
### Needs validation
| Finding | Blockers | Check for the owner to run |
|---|---|---|
### Hardening notes (not findings)
- [location] — [gap] — [why it isn't a finding] — [effort]
### Rejected (do not re-raise)
| Fingerprint | Claim | Ruled out by | Location |
|---|---|---|---|
### TLS posture (if applicable)
[Summary of saved testssl.sh / sslyze output, with the evidence file path, or "not run"]
### Key inventory
| Key | Purpose | Location | Algorithm | Rotation |
|-----|---------|----------|-----------|----------|
### Prioritized remediation plan
1. [Critical / High confirmed findings]
2. [Medium / Low confirmed findings]
3. [Owner-run checks for needs-validation items]
4. [Hardening notes worth the effort]
確認済みの指摘事項の処置(通常は後で finding-triage によって設定):修正済み/延期/受容済みリスク/緩和済み(プラットフォーム/設定)/大規模な移行が必要。誤検出は処置ではなく、rejected レコードとして記録します。
境界
- ユーザーから提供されたコードと設定を監査します
- 暗号を破る、弱体化する、またはバックドアを組み込む手助けは拒否します
- TLS のテストは、ユーザーにテストの権限があるエンドポイントのみを対象とします
- 監査で設計に根本的な欠陥(独自暗号、保護手段としての ROT13)が見つかった場合、推奨事項は「パッチを当てず、置き換える」です。欠陥のある設計を段階的に改善しようとしてはいけません
- 耐量子移行では、NIST の PQC 標準化を追跡しますが、標準化とライブラリでのサポートが完了するまでは、特定の PQC プリミティブを推奨しません。この分野は変化しています
参考資料
- NIST SP 800-57(鍵管理)
- NIST SP 800-131A(アルゴリズムの移行)
- NIST SP 800-175B(暗号標準ガイドライン)
- NIST FIPS 140-3(暗号モジュール標準)
- IETF RFC 7525(TLS の推奨事項)
- OWASP Cryptographic Storage Cheat Sheet
- OWASP Transport Layer Protection Cheat Sheet
- 『Cryptography Engineering』— Ferguson、Schneier、Kohno(読むべき書籍)
- 『Real-World Cryptography』— David Wong
- libsodium/Tink/BoringSSL のドキュメント
- 共有参考資料(ビルド時に
references/にコピー):findings-format.md、severity-calibration.md、verification.md、validate-findings.mjs、to-sarif.mjs、findings.schema.json