| Bytes | Lang | Time | Link |
|---|---|---|---|
| 166 | 166 bytes | 241117T212148Z | Anders K |
| 290 | Python's cryptography | 241116T103212Z | user7610 |
| 392 | openssl req | 241113T233840Z | user7610 |
| 493 | openssl req | 241115T112306Z | Themooni |
166 bytes
-----BEGIN CERTIFICATE-----
MFAwRgIBADADBgEAMAAwHhcNNTAwMTAxMDAwMDAwWhcNNDkxMjMxMjM1OTU5WjAAMBgwCwYJKoZIhvcNAQEBAwkAMAYCAQACAQAwAwYBAAMBAA==
-----END CERTIFICATE-----
This “certificate”, generated by hand with ascii2der, has an empty issuer, empty subject, 0-bit RSA public key, no extensions, and empty signature with algorithm null, but openssl x509 does load it:
Certificate:
Data:
Version: 1 (0x0)
Serial Number: 0 (0x0)
Signature Algorithm: 0.0
Issuer:
Validity
Not Before: Jan 1 00:00:00 1950 GMT
Not After : Dec 31 23:59:59 2049 GMT
Subject:
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (0 bit)
Modulus: 0
Exponent: 0
Signature Algorithm: 0.0
Signature Value:
(I think we need much more clarification about what “valid” means. After such clarification is provided, I’m happy to delete or revise this.)
Python's cryptography, 290 bytes
I could not beat my previous result using openssl req, so I switched to Python's cryptography module, which is somehow able to produce even shorter certs. I again used elliptic curve-based certs because the curves are smaller than RSA. openssl is still happy to load such certificate.
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import ed25519
from cryptography.x509 import CertificateBuilder
from cryptography import x509
import datetime
def generate_minimal_certificate() -> bytes:
private_key = ed25519.Ed25519PrivateKey.generate()
subject = x509.Name([
# x509.NameAttribute(x509.NameOID.COMMON_NAME, 'localhost')
])
cert = (
CertificateBuilder()
.subject_name(subject)
.issuer_name(subject)
.public_key(private_key.public_key())
.serial_number(1)
.not_valid_before(datetime.datetime.now(datetime.UTC))
.not_valid_after(datetime.datetime.now(datetime.UTC) + datetime.timedelta(days=365))
.sign(private_key, None)
)
pem_cert = cert.public_bytes(encoding=serialization.Encoding.PEM)
return pem_cert
if __name__ == '__main__':
cert = generate_minimal_certificate()
print(cert.decode())
print(len(cert))
-----BEGIN CERTIFICATE-----
MIGrMF+gAwIBAgIBATAFBgMrZXAwADAeFw0yNDExMTYxMDI4MzNaFw0yNTExMTYx
MDI4MzNaMAAwKjAFBgMrZXADIQCf30GvRnHbs9gukA3DLXDK6W5JVgYw6mERU/60
2M8+rjAFBgMrZXADQQCGmeaRp/AcjeqmJrF5Yh4d7aqsMSqVZvfGNDc0ppXyUgS3
WMQ1+3T+/pkhU612HR0vFd3vyFhmB4yqFoNV8RML
-----END CERTIFICATE-----
openssl req, 737 558 526 497 392 bytes
% openssl ecparam -name secp112r1 > ec.params
% openssl req -nodes -x509 -newkey ec:ec.params -days 1 -set_serial 1 -out ca-cert.pem -subj "/"
With the above I am getting certs that are either 392 or 396 bytes long. So I picked one of the shorter ones.
-----BEGIN CERTIFICATE-----
MIH2MIHBoAMCAQICAQEwCgYIKoZIzj0EAwIwADAeFw0yNDExMTUxODMzMjFaFw0y
NDExMTYxODMzMjFaMAAwMjAQBgcqhkjOPQIBBgUrgQQABgMeAAQ5m1Yk+0ZvpZWY
rCwQ4VBB54eR+XUcHcqsHfEmo1MwUTAdBgNVHQ4EFgQUI3lNljOTV7EyOCeIadTV
9dmqMx0wHwYDVR0jBBgwFoAUI3lNljOTV7EyOCeIadTV9dmqMx0wDwYDVR0TAQH/
BAUwAwEB/zAKBggqhkjOPQQDAgMkADAhAg4UpmaNZ5cNCi8I9JkAswIPAM25KTiV
t33Lue3ZYVo/
-----END CERTIFICATE-----
changelog
-newkey rsa:512, use smallest possible private key size-subj "/", the/seems to be the shortest possible accepted argument for-subj-set_serial 1, the default is a large random number that takes up bytes-newkey ec:ec.params, picked an elliptic curve from the listopenssl ecparam -list_curvesthat is giving me the smallest key, after trying each once-days 1, does not seem to have impact, but smaller numbers are usually better, copied from Themoonisacheese's answer
openssl req, 518 493 bytes
-25 bytes: --set-serial 1 by user7610
-----BEGIN CERTIFICATE-----
MIIBUTCB/qADAgECAhRuvWYi4YQN7neCssfB+Rsq+yMv5TANBgkqhkiG9w0BAQsF
ADAAMB4XDTI0MTExNTExMTQxNVoXDTI0MTExNjExMTQxNVowADBZMA0GCSqGSIb3
DQEBAQUAA0gAMEUCPgLItpCnD7Ul4vED7loyin38YYetYx4MjeqQNPv6Ri9cvub7
giiduROYMed8ZbXm4h33knJNzVh+RWwnlhY7AgMBAAGjUzBRMB0GA1UdDgQWBBSJ
ST+wBPwEsQqLEDRgo4A0cuugGDAfBgNVHSMEGDAWgBSJST+wBPwEsQqLEDRgo4A0
cuugGDAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAAz8AAeLZkP0i1rMQ
4D2Nt8DdKZgIESBzpFyPI17QR8kqypOdx43Tn+btsB6gAoqhv69GDSFxMtYw+90A
/S/+Fns=
-----END CERTIFICATE-----
generated using:
openssl req -nodes -x509 -key key490.pem -days 1 -subj "/" --set-serial 1> cert.pem
with key490.pem a 490-bit rsa key generated using:
import rsa
import sys
pubkey,privkey = rsa.newkeys(int(sys.argv[1]))
print(privkey.save_pkcs1('PEM').decode('UTF-8'))
While 512 bits is the smallest openssl will allow you to generate, an RSA key can be as small as 16 bits (!). However, as the key gets smaller, you can encrypt less and less data with it, and openssl is not happy about that. I performed a manual binary search between 16 and 512 to find that 490 bits is the smallest possible key using this technique.