可怕的公钥技巧
Horible public key tricks

原始链接: https://maurycyz.com/projects/badcert/

作者批评 TLS/ACME 的复杂性和频繁的证书续期,认为防止被动窃听并不需要完整的证书颁发机构模型。他们提出“抗议证书”:一种有效且由 CA 签名的证书,其 RSA 公钥模数包含可读消息。文章概述了 RSA 密钥生成以及在保留可用密钥的前提下将文本嵌入模数的方法,指出可能的 base64 对齐问题和安全权衡,包括熵降低以及素数可能过近或私钥指数过小的风险。它还指出续期可通过重新提交 CSR 自动化,并认为现代 TLS 前向保密使这种密钥即使在私钥可能被泄露的情况下也适用于公开网站。

一个 Hacker News 帖子讨论《Horible public key tricks》,一篇探索公钥与证书怪异现象的博客文章。评论者就 TLS 安全性展开辩论,指出未认证会话并不真正安全,中间人攻击者可注入内容或广告。其他人批评苹果的公证费用与 SIP 限制。Web PKI 与浏览器信任库因剥夺用户对信任决策的自主权而受指责。Let's Encrypt 的 ACME 流程被认为繁琐,但优于过去昂贵的证书。有人建议在证书透明度日志中嵌入数据。CA 的弱密钥检查与 eMudhra 事件也被提及。文章中大量拼写错误引发关于故意体现人类作者的猜测。整体而言,该帖子涵盖 PKI 缺陷、中心化与安全权衡。
相关文章

原文
—

So, I've always been somewhat annoyed at SSL/TLS. It requires everyone to go through a complex "ACME" dance just to put up a public website. Worse, certificates expire every few months, so it's not a one and done thing: even with automation, maintaining a certificate requires care to ensure the ACME client always has access to DNS records or the web server.

... and the whole system doesn't even work because certificates are issued over plaintext connections, and trusted certificate aurthorities use very secure keys like "4"

There is prior art of protest SSL implementations, but I do actually want to encrypt web traffic. Passive eavesdropping is a real risk... but preventing it doesn't require the whole certificate thing: therefore, the best choice is a protest certificate.

The only part of the certificate I directly control is the public key. Therefore, the simplest way to obtain a bad certificate is use a vanity private key such as 313333333337 (one of the most eleet primes). However, while the resulting public key would be weak, no one would know unless they try to break it.

A good protest should be obvious:

# 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-----

First, let's look at mathematical framework of RSA. To generate a key, pick two large prime numbers (p and q) and multiply them to yeild the modulus (n). Pick a small number, e as public exponent.

The RSA public key is composed of the modulus (n) and the public exponent:

# Encryption requires knowing e (public)
cyphertext = plaintext^e mod n

The private key contains the modulus and the private exponent (d) which is chosen to be the multiplicate inverse of e modulo n. Exactly how d is found is not relevent, but the process requires knowing p and q.

# Decryption requires knowing d (pivate)
= cyphertext^d mod n

= plaintext^e^d mod n
= plaintext^(e*d) mod n
= plaintext^1 mod n
= plaintext

Ok, back to vanity keys. The standard value for e is 65537 (0x10001), because the small number of set bits makes encryption faster. In fact, the CA I used will refuse to sign keys with a non-standard public exponent, so I can't do anything funny there.

The modulus, n, is either 2,048 or 4,096 "user controlled" bits, so it's the ideal place to hide a message. However, to use the certificate, I'll need to find two large primes that multiply out to the vanity modulus.

My method starts by picking two random primes and calculating the resulting n value. Then, the bytes of n are modified to introduce message into the base64 encoding. Of course, the modified n is not the product of p and q, but this is fixable by setting q to n/p.

The resulting q value is very unlikely to be a prime (or integer), so I round it and find the next prime after the ideal q: which usually only requires changing the value of q by a few hundred. Since p and q are similar in size, a small change q only affects the least-sigifiant half of the product:

58174124 * 918491824 = 53432457262362176
(incrementing until the next prime...)
58174124 * 918491843 = 53432458367670532

As long as the message is embedded into the upper half of n, it's likely to survive undamaged. The original p and newly selected q can now be used to construct a functional RSA private key in the usual manner.

From here, it's really just a matter of getting a certificate aurthority to sign the key:

# Single domain
openssl req -new -key priv.pem -subj '/C=US/CN=example.com' > cert_request
certbot certonly --manual --csr cert_request

# Wildcard
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
... although since base64 operates on groups of three byte, it is possible for the message to be misaligned and not readable. However, since the layout of the certificate is static, it is possible to adjust the message's placement until it works: this should take less than four attempts.

The result is a functional, trusted certificate with a longish message hidden in the public key. Renewal is done by resumbitting the CSR and repeating the ACME chalange, which can be automated.

Base64 is missing most punctuation, but it provides the full ASCII alphabet, and the ~150 (RSA-2048) or ~320 (RSA-4096) avalable charaters provides plenty of space for jokes. Another option would be to target the base16 displayed in most browser's certificate viewer: this would provide a lot more space (2 chars/byte instea of 0.75), but requires careful word choice.

As for security, this trick obviously reduces the entropy in the private key. However, it should still be secure: nearly identical methods have been considered as a form of compression. If the top-half of the modulus is fixed to a well known value, it doesn't have to be sent or stored as part of the key.

A reduction of around 1/2 can be achieved through division, but the state of the art can fix 2/3 of the key, reducing the size to 1/3 of standard RSA while retaining a similar level of security... although I should note that such tricks have largly been abandoned in favor of other crytosystems which can directly use small keys.

The manipulation of q may produce primes that are very close together, which makes it possible to factor n and recover the private key. A very small d value can also be a problem. Both of these are unlikely, but my script doesn't check for insecure values

However, regardless of the key's security, it should be perfectly fine for a public website: modern TLS provides forward secrecy, so eavesdropping requires a man-in-the-middle attack even if the private key is compromised.

联系我们 contact @ memedata.com