恐ろしい公開鍵のトリック
Horible public key tricks (maurycyz.com)
要約
この記事では、SSL/TLS証明書の取得プロセスを簡略化し、その証明書に秘密のメッセージを埋め込むための「恐ろしい公開鍵トリック」について解説しています。著者は、RSA公開鍵のモジュラス(n)にメッセージをエンコードし、それを満たす素数pとqを見つけることで、機能する証明書を作成する手法を説明しています。この方法は、セキュリティ上のリスクを伴う可能性はあるものの、現代のTLSのフォワードシークレット機能により、公開ウェブサイトでは問題ないと論じています。
全文翻訳
恐ろしい公開鍵のトリック:
2026-11-08 — 2026-11-09
私は以前からSSL/TLSに少々うんざりしていました。公開ウェブサイトを立ち上げるために、誰もが複雑な「ACME」ダンスを経なければならないからです。さらに悪いことに、証明書は数ヶ月ごとに期限切れになるため、一度設定すれば終わりというわけではありません。自動化されていても、証明書の維持には、ACMEクライアントが常にDNSレコードやWebサーバーにアクセスできることを確認するための注意が必要です。
...そして、証明書はプレーンテキスト接続で発行され、信頼された認証局が非常に安全な鍵(例えば"4")を使用しているため、システム全体が機能していません。
抗議のためのSSL実装の先行事例はありますが、私は実際にWebトラフィックを暗号化したいのです。パッシブな盗聴は現実的なリスクです...しかし、それを防ぐために証明書というもの全体が必要なわけではありません。したがって、最良の選択肢は抗議証明書です。
私が直接制御できる証明書の唯一の部分は公開鍵です。したがって、悪い証明書を取得する最も簡単な方法は、313333333337(最もエリートな素数の一つ)のようなバニティ秘密鍵を使用することです。しかし、結果として得られる公開鍵は弱くなりますが、誰もそれを破ろうとしない限り、それを知ることはありません。
良い抗議は明白であるべきです。
# openssl s_client -connect maurycyz.com:443 < /dev/null 2> /dev/null \
| sed -n /BEGIN/,/END/p | grep -zE '////.?*////' --color=yes
-----BEGIN CERTIFICATE-----
MIIFAjCCA+qgAwIBAgISBkOkuuyaN1ypD3PAPrLMyT7tMA0GCSqGSIb3DQEBCwUA
MDMxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQwwCgYDVQQD
EwNZUjIwHhcNMjYxMTA5MTcyOTQ2WhcNMjcwMjA3MTcyOTQ1WjAXMRUwEwYDVQQD
EwxtYXVyeWN5ei5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCg
JyPf37Sj6PvU06m5////You+dont+actually+have+to+pick+random+privat
e+keys//No+one+checks+these+things//A+certificate+authority+got+
away+with+the+number+4+for+a+year/////DSfCAfXcsJfBpnHekS0qnf5YLl
c54VuUNRVINcDkEwJDwyB96+xE/uXhtiGdk8ty1PF6jciLEYCJBHcmqOa5pOyV8m
63yR3NE6hCQlaSLQQDo96Sb0/10aC5LqE7KwEskrP/qeCzT7vDN7fh+dXOGs+VMw
eJvTAb0xHJpxhSK05i7DAgMBAAGjggIqMIICJjAOBgNVHQ8BAf8EBAMCBaAwEwYD
VR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUBKBfDbwf
dCnKeU83pMTWyFrJBwAwHwYDVR0jBBgwFoAUQBUtJnntMiCe35pyHdYyH4EMgQww
MwYIKwYBBQUHAQEEJzAlMCMGCCsGAQUFBzAChhdodHRwOi8veXIyLmkubGVuY3Iu
b3JnLzAnBgNVHREEIDAegg4qLm1hdXJ5Y3l6LmNvbYIMbWF1cnljeXouY29tMBMG
A1UdIAQMMAowCAYGZ4EMAQIBMC8GA1UdHwQoMCYwJKAioCCGHmh0dHA6Ly95cjIu
Yy5sZW5jci5vcmcvMTEwLmNybDCCAQsGCisGAQQB1nkCBAIEgfwEgfkA9wB1AExj
3JjlnB2riPYeij3ero+rRKM3e1+blMP7oZz8wb4mAAABocGRbw4AAAQDAEYwRAIg
HShWaEI2nVJEizSI6sJ/jnLaABZ3aTKoUL3pLo1uOgsCIEn+XiT2WHhm9LGN6kug
O9R7Z4q0HiFA4RZ7tIMJQH5XAH4AjspHC6zeavOiBrCkeoS3Rv4fxr+VPiXmm07k
AkjzxugAAAGhwZFyfQAIAAAFAEE0hdwEAwBHMEUCIDSms4Vf0llcvcldJd2Ool8i
AiUQQlS0Zzbej+7Y1aR7AiEA79Ll+t6inu1PkUIgzNTXkE0S20VHYEYel5fkdKSA
VTQwDQYJKoZIhvcNAQELBQADggEBAFi3A4fcjj4UKa9MFz1a8DWc+Jya2gJng13m
2UFdWCaqsN7FJWQEjRiJ0npynmDnA3VMyjswUMHAMx4d8G3lHNPfZWGsCgpjVa/d
IEoeZ6gTYjFa/Ovb64m4CcuQkm0GxcyAqMPs348e6Px32iU1R6MnV4UEUosnSPeK
4ocuI3SoR9LHxX/yHOKcYTKLpIMwMdaAmWt0N7l59GOb8o+dz3R7cxtVV/lrZReZ
/YPEAYRiNi7f+jNI1JCp0i8wXUb9NN7WlbmcFDecDG/++cRpAkoIk4m/LJindsuu
kErrVZ4yLqB0Hvm+uNvWxr9y/qKFTYw9POx1oaZJO/SwXZdDBeY=
-----END CERTIFICATE-----
まず、RSAの数学的フレームワークを見てみましょう。鍵を生成するには、2つの大きな素数(pとq)を選び、それらを掛けてモジュラス(n)を得ます。小さな数eを公開指数として選びます。
RSA公開鍵は、モジュラス(n)と公開指数で構成されます。
# 暗号化にはe(公開)を知る必要がある
cyphertext = plaintext^e mod n
秘密鍵にはモジュラスと、eのモジュロnにおける乗法逆数として選ばれた秘密指数(d)が含まれます。dがどのように見つけられるかは重要ではありませんが、そのプロセスではpとqを知る必要があります。
# 復号化にはd(秘密)を知る必要がある
= cyphertext^d mod n
= plaintext^e^d mod n
= plaintext^(e*d) mod n
= plaintext^1 mod n
= plaintext
さて、バニティ鍵の話に戻りましょう。eの標準値は65537(0x10001)です。これは、セットされているビット数が少ないため、暗号化が速くなるからです。実際、私が使用した認証局は、標準的でない公開指数を持つ鍵の署名を拒否するため、その点では何も面白いことはできません。
モジュラスnは、2048ビットまたは4096ビットの「ユーザー制御可能」なビット数なので、メッセージを隠すのに理想的な場所です。しかし、証明書を使用するには、バニティモジュラスと掛け合わせる2つの大きな素数を見つける必要があります。
私の方法は、2つのランダムな素数を選び、結果のn値を計算することから始まります。次に、nのバイトを変更して、base64エンコーディングにメッセージを導入します。もちろん、変更されたnはpとqの積ではありませんが、これはqをn/pに設定することで修正できます。
結果として得られるq値は素数(または整数)である可能性は非常に低いので、それを丸めて、理想的なqの次の素数を見つけます。これは通常、qの値だけを数百程度変更するだけで済みます。pとqのサイズが似ているため、qの小さな変更は積の下位半分にのみ影響します。
58174124 * 918491824 = 53432457262362176
(次の素数までインクリメント...)
58174124 * 918491843 = 53432458367670532
メッセージがnの上位半分に埋め込まれている限り、損傷を受けずに済む可能性が高いです。元のpと新しく選択されたqを使用して、通常の方法で機能するRSA秘密鍵を構築できます。
ここから先は、認証局に鍵に署名してもらうだけです。
# 単一ドメイン
openssl req -new -key priv.pem -subj '/C=US/CN=example.com' > cert_request
certbot certonly --manual --csr cert_request
# ワイルドカード
cat > config<<EOF
[ req ]
req_extensions = req_ext
[ req_ext ]
subjectAltName = @alt_names
[ alt_names ]
DNS.1 = *.example.com
DNS.2 = example.com
EOF
openssl req -new -key priv.pem -config config -subj '/C=US/CN=example.com' > cert_request
certbot certonly --manual --csr cert_request
...ただし、base64は3バイトのグループで動作するため、メッセージがずれて読めなくなる可能性があります。しかし、証明書のレイアウトは固定されているため、メッセージの配置を調整して機能させることが可能です。これには4回未満の試行で済むはずです。
結果は、公開鍵にかなり長いメッセージが隠された、機能的で信頼できる証明書になります。更新は、CSRを再提出し、ACMEチャレンジを繰り返すことで行われ、これは自動化できます。
Base64はほとんどの句読点を欠いていますが、完全なASCIIアルファベットを提供し、約150(RSA-2048)または約320(RSA-4096)の利用可能な文字は、ジョークを仕込むのに十分なスペースを提供します。別の選択肢として、ほとんどのブラウザの証明書ビューアに表示されるbase16をターゲットにすることもできます。これにより、より多くのスペース(バイトあたり2文字、0.75文字ではなく)が得られますが、慎重な単語の選択が必要です。
セキュリティについて、このトリックは明らかに秘密鍵のエントロピーを低下させます。しかし、それでも安全であるはずです。ほぼ同様の方法は、圧縮の一形態として検討されてきました。モジュラスの上半分が既知の値に固定されている場合、鍵の一部として送信または保存する必要はありません。
除算によって約1/2の削減を達成できますが、最先端技術では鍵の2/3を固定でき、標準RSAの1/3のサイズに縮小しながら同様のセキュリティレベルを維持できます...ただし、そのようなトリックは、小さな鍵を直接使用できる他の暗号システムに取って代わられて、ほとんど放棄されていることに注意すべきです。
qの操作は非常に近い素数を生成する可能性があり、nを因数分解して秘密鍵を復元できる可能性があります。非常に小さいd値も問題になる可能性があります。これらはどちらも可能性は低いですが、私のスクリプトは安全でない値をチェックしません。
しかし、鍵のセキュリティに関係なく、公開ウェブサイトにとっては完全に問題ないはずです。最新のTLSはフォワードシークレットを提供しているため、秘密鍵が侵害されたとしても、盗聴には中間者攻撃が必要です。
関連:
- https://tom7.org/httpv/httpv.pdf: [⭐ World's worst TLS server ]
- /projects/badcert/gen.py: バニティ鍵を生成するスクリプト
- https://rya.nc/cert-tricks.html: その他のバニティ証明書
- https://doi.org/10.1007/978-3-540-79104-1_9: nの一部を修正することに関する実際の暗号学者の意見