ローカルLLMで暗号解読を実測|シーザー暗号は解けても、換字式は4モデルとも未解読(RTX 3090)
要点
- 鍵を伝えたシーザー暗号とROT13は、モデルで明暗が分かれた。 gemma4:31bは4課題すべて、gpt-oss:20bは1文字欠落の1課題を除いて完全一致。qwen2.5:14bは4課題とも、暗号文を戻したものではない文を答えました。
- 鍵を伝えない約200字の単一換字式は、4モデルとも解けなかった。 2モデルが300秒で打ち切り、1モデルが思考だけで8,192トークンを使い切り、1モデルが無関係な英文を返しました。
- 「解くコードを書かせる」と結果が変わった。 gpt-oss:20bは、直接の解読で1文字落とした同じ暗号文を、自分で書いた総当たりのPythonコードでは完全一致で解きました。
- qwen3.6:35b-a3bは6課題すべて時間切れ。 このPC・この設定では回答まで届かず、能力の比較にはなっていません。各課題1回・6課題だけの結果です。
生成AIに暗号を解かせた話題は注目を集めやすいのですが、手元のGPUで動くローカルLLMに同じことをさせると、どの種類の暗号で何が起きるのかを確かめました。RTX 3090 24GBとOllamaで4モデルに6課題を1回ずつ出し、答えを機械的に採点しています。クラウドの大型モデルの性能評価や、他サイトの検証の追試ではありません。
問題文・正解・採点・実行条件はすべて、配布するスクリプトと結果JSONに入っています。本文の数値はこのJSONからの転記です。
6課題と測定条件
課題はすべて、平文を決めてからスクリプトで暗号化したものです。シーザー暗号は英字を一定数ずらし、単一換字式は26文字を固定のシードで並べ替えた鍵で置き換えます。空白はどの課題でも残しています。
| 課題 | 内容 | 鍵の扱い |
|---|---|---|
| a | シーザー暗号 シフト3(英文) | シフト量を問題文で伝える |
| b | シーザー暗号 シフト17(英文) | シフト量を問題文で伝える |
| c | ROT13(英文) | 方式名を伝える |
| d | 単一換字式暗号(英文約200字) | 鍵は伝えない |
| e | シフト不明のシーザー暗号を解くPythonコードを書く(暗号文はbと同じ) | シフト量は伝えない |
| f | シーザー暗号 シフト5(日本語のローマ字) | シフト量とローマ字であることを伝える |
a・b・c・fは、鍵が分かっていれば手作業でも機械的に戻せる課題です。dは頻度や単語の形から鍵を推理する必要があり、eは26通りを試すコードを書けば解けます。たとえば課題aの問題文は次のとおりです。
次の暗号文はシーザー暗号(アルファベットを3文字後ろへずらしたもの。空白はそのまま)で暗号化した英文です。解読して、最後の行に「PLAINTEXT: 」に続けて平文だけを書いてください。
暗号文: pb qhljkeru sdlqwv rog elfbfohv euljkw rudqjh hyhub vsulqj dqg vhoov wkhp dw wkh kdueru pdunhw実行は2026年10月26日11:51〜12:36(日本時間)、生成コードの実行は12:37です。
- GPUはNVIDIA GeForce RTX 3090(24,576 MiB、ドライバ617.42)、OSはWindows、Ollama 0.30.6、Node.js v24.22.1
- Ollamaの
/api/chatへ、temperature0、seed20261026、num_ctx8192、num_predict8192で送信。1回答の上限は300秒 - モデルはgemma4:31b → qwen3.6:35b-a3b → gpt-oss:20b → qwen2.5:14bの順に、6課題ずつ1回だけ実行。思考(thinking)の出し方はOllamaとモデルの既定のままで、
thinkは指定していません - 採点は、回答の最後の「PLAINTEXT:」行を取り出し、全角半角・大文字小文字・記号の差を除いてから正解と完全一致したかどうか。補助として編集距離による類似度(1が完全一致)も記録しました
- 課題eは、回答の最後のPythonコードブロックを目視したあと、一時フォルダで
python -Iとして10秒上限で実行し、標準出力の最後の行を同じ方法で採点
結果:完全一致はgemma4が5/6、gpt-ossが4/6
課題ごとの結果です。秒数はリクエストから回答の終わりまでの壁時計時間で、モデルの読み込みやプロンプトの処理も含みます。誤答の括弧内は類似度です。
| 課題 | gemma4:31b | gpt-oss:20b | qwen2.5:14b | qwen3.6:35b-a3b |
|---|---|---|---|---|
| a シフト3 | 正答 108.0秒 | 正答 27.4秒 | 誤答(0.298)11.2秒 | 時間切れ 300.0秒 |
| b シフト17 | 正答 78.3秒 | 誤答(0.99)8.9秒 | 誤答(0.229)0.9秒 | 時間切れ 300.0秒 |
| c ROT13 | 正答 28.0秒 | 正答 8.5秒 | 誤答(0.3)1.1秒 | 時間切れ 300.0秒 |
| d 換字式 | 時間切れ 300.0秒 | 回答なし(上限到達)61.2秒 | 誤答(0.226)4.8秒 | 時間切れ 300.0秒 |
| e コード生成 | 正答 105.7秒 | 正答 27.1秒 | 誤答(0.181)8.0秒 | 時間切れ 300.0秒 |
| f ローマ字 | 正答 70.6秒 | 正答 19.1秒 | 誤答(0)2.0秒 | 時間切れ 300.0秒 |
課題別に見ると、完全一致したモデルはa・c・e・fが各2、bが1、dが0です。所要時間は次のとおりで、短いほど正確という関係はありません。
失敗の型:作話、1文字欠落、思考の使い切り、時間切れ
鍵を伝えても、別の英文を作る
qwen2.5:14bは6課題とも0.9〜11.2秒で答えましたが、解読を求めた5課題の答えは、どれも暗号文を戻した文ではありませんでした。シフト3の課題には「3つ前にずらすことで解読できます」と正しい手順を述べたうえで、次の文を答えています。
正解: my neighbor paints old bicycles bright orange every spring and sells them at the harbor market回答: on keyphrase little red riding hood met them in the middle of the woodシフト17の課題bは「the quick brown fox jumps over lazy dogs the end」、換字式の課題dでも「the quick brown fox jumps over lazy dogs」の繰り返しでした。ローマ字の課題fは、一度ローマ字の文を出したあとで日本語に「翻訳」し、最後の「PLAINTEXT:」行が日本語の文になっています。
無関係な英文でも類似度は0.181〜0.3になりました。英文らしい文字列同士なら一致する文字がある程度出るためで、この類似度を部分点として読むことはできません。速く、もっともらしい形で返ってくる誤答は、答えを検証しない使い方では見落としやすい失敗です。
1文字だけ落とす
gpt-oss:20bの課題bは、thursdaysがthursdayになった以外は正解と同じで、類似度は0.99でした。完全一致の採点では誤答です。暗号の解読では1文字の違いが意味を変えることがあるため、今回は部分点を与えていません。
換字式で、思考の途中に上限へ達する
課題dでは、思考を出力するモデルが答えにたどり着く前に止まりました。
- gemma4:31bは300秒の時点で思考が17,226文字、回答本文は0文字
- gpt-oss:20bは61.2秒で停止。プロンプト250トークンと生成7,942トークンの合計が
num_ctxの8,192に達し、終了理由はlength。思考は22,280文字、回答本文は0文字
どちらも「鍵が分からない」と答えたわけではなく、試行錯誤の途中で打ち切られています。上限を広げれば解けたかどうかは、今回は測っていません。
qwen3.6:35b-a3bは、6課題とも300秒で回答に届かない
qwen3.6:35b-a3bは、最も短い課題でも300秒以内に回答本文を1文字も返しませんでした。300秒間に出力された思考は1,253〜4,901文字です。同じ条件のgpt-oss:20bは、8.5〜61.2秒で2,436〜22,280文字の思考を出しています。
生成がこれだけ遅かった原因、たとえばGPUメモリに収まらずCPUへ処理が回ったかどうかは、今回記録していません。したがって、この結果はこのPC・num_ctx 8192・300秒の条件で回答まで届かなかったという記録で、モデルの解読能力を比べたものではありません。GPUが使われているかの確かめ方はOllamaがWindowsでGPUを使わないときの確認手順にまとめています。
直接解かせるより、コードを書かせる
課題eの暗号文は課題bと同じですが、シフト量は伝えず、解読するPythonコードを書かせました。直接の解読より条件が厳しいにもかかわらず、gpt-oss:20bはこちらで完全一致しています。
シーザー暗号の解読は、文字を決まった数だけずらす機械的な処理です。モデルが文字を1つずつ書き換えるより、総当たりのコードを書いて実行したほうが、1文字の取りこぼしは起きにくくなります。ただし、生成コードにもそれぞれ癖がありました。
- gemma4:31bは、シフトを0から順に試し、「 the 」を含むか「the 」で始まる最初の結果を出力して終わります。theを含まない文では何も出力しません
- gpt-oss:20bは、よく使う英単語の集合に、
# words that appear in the expected plaintextというコメント付きでlibraryやpotteryなどの単語を加えていました。思考の中で先に解読し、その答えの単語を判定用の辞書へ入れたと読めます。この追加分を除いて同じ規則をNode.jsで再現したところ、元の辞書だけでも正しいシフトが選ばれることを確認しました(2026年10月26日、ローカルで再計算) - qwen2.5:14bは、
Shift 1:からShift 25:までの25行を出力しました。Shift 9:の行に正しい平文がありますが、条件は「平文だけを1行」です。最後の行を答えとして読む仕組みでは、Shift 25:の行が答えになります
生成コードは、実行前に中身を読む必要があります。配布スクリプトの--execute-codeは、一時フォルダとpython -Iで実行しますが、ネットワークは遮断しません。
手元で再現する
local-llm-cipher-bench.mjsとlocal-llm-cipher-bench-20261026.jsonを同じフォルダに保存します。スクリプトはNode.jsの標準機能だけで動き、npmパッケージの追加やAPIキーは要りません。接続先は既定でhttp://127.0.0.1:11434、変える場合は環境変数OLLAMA_HOSTを設定します。課題eのコードを実行する手順だけはPython 3を使います。
# 6課題の暗号文と正解を表示する(Ollamaには接続しない)node .\local-llm-cipher-bench.mjs --print-tasks
# 1モデルだけ実行する。raw フォルダに問題文・思考・回答の全文を保存するnode .\local-llm-cipher-bench.mjs --models gpt-oss:20b --out results.json --raw-dir raw
# raw フォルダで課題eのコードを読んでから、実行して採点するnode .\local-llm-cipher-bench.mjs --execute-code --in results.jsonモデル名を省くと、今回の4モデルを順に実行します。GPUメモリに余裕がない場合は--num-ctx 4096のようにコンテキストを指定できますが、今回の結果とは条件が変わります。
公開した結果JSONの採点を確かめるだけなら、Ollamaは要りません。同じフォルダに次の内容でregrade.mjsを作って実行します。
import { readFileSync } from 'node:fs';import { buildTasks, grade } from './local-llm-cipher-bench.mjs';
const data = JSON.parse(readFileSync('./local-llm-cipher-bench-20261026.json', 'utf8'));const tasks = new Map(buildTasks().map(t => [t.id, t]));const diff = data.results.filter(r => grade(tasks.get(r.task).plaintext, r.extracted ?? '').exact !== r.exact);console.log(data.results.length + ' results, ' + diff.length + ' mismatches');24 results, 0 mismatchesこの出力は、保存された回答(extracted)を同じ採点関数で採点し直すと、24件すべてで正誤が記録と一致するという意味です。モデルを実行し直して同じ回答が出ることの確認ではありません。
測定の限界
よくある質問
ローカルLLMはシーザー暗号を解読できますか?
今回の実測では、シフト量を問題文で伝えた英文のシーザー暗号(シフト3・17)とROT13を、gemma4:31bはすべて完全一致で解き、gpt-oss:20bはシフト17でthursdaysのsを1文字落とした以外は正答しました。qwen2.5:14bは暗号文と関係のない英文を答え、qwen3.6:35b-a3bは300秒以内に回答を出せませんでした。各課題1回の結果です。
鍵が分からない換字式暗号は解けましたか?
約200字の英文を単一換字式で暗号化した課題は、4モデルとも正答できませんでした。gemma4:31bとqwen3.6:35b-a3bは300秒で打ち切り、gpt-oss:20bは思考の途中でnum_ctxの8,192トークンに達して回答が空、qwen2.5:14bは暗号文と無関係な英文を返しました。時間やコンテキストの上限を広げた再試験は行っていません。
同じ測定を自分のPCで再現できますか?
同梱のlocal-llm-cipher-bench.mjsはNode.jsの標準機能だけで動き、手元のOllamaの/api/chatへ6課題を送ります。—print-tasksで課題と正解を確認でき、結果JSONと同梱の採点関数で採点を再計算できます。temperature 0とseedを固定しても、Ollamaやモデルの版、GPUが違えば同じ出力になるとは限りません。
次に読む
日本語の実務課題でローカルLLMがどう失敗するかはJ-WorkBenchの失敗例7ケース、クラウドとの相対得点と測定の限界はローカルLLMとクラウドの比較にあります。num_ctxを広げる前に必要なGPUメモリを見積もるなら、VRAM別モデルサイズ早見を参照してください。
この記事の情報・検証メモ
- local-llm
- ollama
- benchmark
- nodejs
- 公開日
- 情報確認
- 更新性
- 定期更新
仕様・料金・提供範囲が変わりやすいテーマは、公開日・更新日・情報確認日を分けて管理します。 導入前には必ず記事末尾の一次情報と公式ドキュメントで最新状況を確認してください。