projet / octobre 2025

OpenSSL PKI and secure architecture lab

Hands-on CA setup, certificate lifecycle, two-layer decryption, digital signatures, CRL and OCSP with OpenSSL.

OpenSSLPKIX.509CertificatesCryptography

This lab is from the Security Architectures course at Université Grenoble Alpes (CySec, October 2025). The work follows a complete PKI workflow: generate keys, decrypt a provided encrypted message, sign and verify, build a CA from scratch, issue user certificates, revoke them, generate a CRL, and set up an OCSP responder. The entire thing runs with OpenSSL 3.5.3 on the command line.

The entropy question first

Every cryptographic operation starts with random data. The lab begins by generating a 16KB entropy file:

dd if=/dev/urandom of=.rand bs=1024 count=16

Output: 16384 bytes (16 kB, 16 KiB) copied, 0.000326721 s, 50.1 MB/s.

The choice of /dev/urandom over /dev/random is correct here. /dev/random blocks when the kernel’s entropy pool is exhausted, which historically stalled key generation. Since Linux 4.8, /dev/urandom is a CSPRNG seeded from the entropy pool, and once seeded it does not block. The security is equivalent for any practical purpose. The .rand file should be destroyed after use — shred -vfz -n 5 .rand — since its presence on disk after key generation is a liability.

Key generation

openssl genrsa -aes256 -rand .rand -passout pass:"MySecurePassphrase2024!" -out myprivatekey.pem 2048

This generates a 2048-bit RSA key pair. The private key is stored encrypted with AES-256-CBC. The PEM file cannot be used without the passphrase. Extracting the public key:

openssl rsa -in myprivatekey.pem -pubout -out mypublickey.pem -passin pass:"MySecurePassphrase2024!"

Inspecting with openssl rsa -text -noout shows all the RSA private key components: modulus n, public exponent e (65537 = 2^16 + 1), private exponent d, the two primes p and q, and the CRT optimization values dP = d mod (p-1), dQ = d mod (q-1), and coefficient q^(-1) mod p. Those CRT components are what make RSA decryption fast on real hardware — rather than one large modular exponentiation mod n, the implementation computes two smaller ones mod p and mod q and combines the results, giving roughly a 4× speedup.

The two-layer decryption exercise

The exercise provided clefrsa.pem (an RSA private key protected with passphrase “A very long and very secret passphrase”) and clef-sym-crypt.bin. The task was to recover a poem.

The structure is hybrid encryption: a symmetric key encrypts the bulk data, and that symmetric key is encrypted with RSA. Step one:

openssl pkeyutl -decrypt -inkey clefrsa.pem -in clef-sym-crypt.bin \
  -out symmetric_key.bin -passin pass:"A very long and very secret passphrase"

hexdump -C symmetric_key.bin showed:

00000000  6d 79 6d 6f 73 74 73 65  63 72 65 74 77 6f 72 64  |mymostsecretword|
00000010  0a

Step two:

openssl enc -d -aes-256-cbc -pbkdf2 -iter 300000 -md sha3-256 \
  -in encrypte.bin -out decrypted_message.txt -pass pass:mymostsecretword

The parameters matter precisely. -pbkdf2 uses PBKDF2 for key derivation instead of the older OpenSSL EVP_BytesToKey. -iter 300000 is 300,000 PBKDF2 iterations, making brute-force significantly slower. -md sha3-256 specifies SHA3-256 as the PBKDF2 hash. Getting any parameter wrong produces silent garbage output — the decryption completes but yields unintelligible bytes, which is a debugging trap.

The decrypted content was Wisława Szymborska’s poem “Could Have.” The file was 736 bytes total: 720 bytes of AES-CBC ciphertext (45 blocks × 16 bytes) plus the 8-byte OpenSSL Salted__ header. PKCS#7 padding was removed automatically.

Digital signatures

Attempting to sign the 599-byte poem directly with openssl pkeyutl -sign fails. RSA operates on values smaller than the key modulus in bytes — 2048 bits means 256 bytes, and the message is too large.

The correct approach signs the SHA-256 hash of the message:

openssl dgst -sha256 -sign clefrsa.pem \
  -passin pass:"A very long and very secret passphrase" \
  -out signature_proper.bin decrypted_message.txt

This hashes the message (producing 32 bytes), then encrypts the hash with the private key. Output: signature_proper.bin is exactly 256 bytes. Verification:

openssl dgst -sha256 -verify clefpub.pem \
  -signature signature_proper.bin decrypted_message.txt

Output: Verified OK. The verification process: decrypt the signature with the public key to recover the stored hash, hash the message independently with SHA-256, compare. A match means the message was signed by whoever holds the private key and has not been changed since.

An alternative two-step version gives identical output — hash with openssl dgst -sha256 -binary to a file, then sign the hash file with openssl pkeyutl -sign. Both paths produce the same 256-byte signature.

Building the CA

The CA directory structure follows the OpenSSL convention:

mkdir -p demoCA/{private,certs,crl,newcerts}
chmod 700 demoCA/private
echo "02" > demoCA/serial
touch demoCA/index.txt
echo "unique_subject = no" > demoCA/index.txt.attr
echo "01" > demoCA/crlnumber

A few non-obvious details: the serial file requires a hexadecimal number with an even number of digits — “02” works, “2” does not. index.txt is a flat-file database tracking every issued certificate: status (V/R/E), expiration, serial, and DN. unique_subject = no allows multiple certificates to the same subject, necessary in a test environment.

The CA key is 4096 bits, double the user key size:

openssl genrsa -aes256 -rand .rand_ca \
  -passout pass:"CASecurePassphrase2024!" \
  -out demoCA/private/cakey.pem 4096

The CA key is longer because it sits higher in the trust hierarchy and has a longer expected lifetime. Compromise of the CA key compromises every certificate ever issued. The 700 permissions on demoCA/private/ and 600 on the key file enforce OS-level access control on top of the passphrase.

Self-signed CA certificate:

openssl req -x509 -new \
  -key demoCA/private/cakey.pem \
  -out demoCA/cacert.pem -days 365 \
  -passin pass:"CASecurePassphrase2024!" \
  -subj "/C=FR/ST=Isere/L=Grenoble/O=UGA/OU=CySec/CN=My Certificate Authority"

OpenSSL assigned a random serial 22:2a:c4:f4:f9:a5:5d:94:db:cb:d2:ab:d6:cd:60:89:91:34:b4:f3. The certificate’s issuer and subject DNs are identical — this is the defining characteristic of a root CA.

Issuing a user certificate

The user generates a CSR:

openssl req -new -key myprivatekey.pem -out user_request.csr \
  -passin pass:"MySecurePassphrase2024!" \
  -subj "/C=FR/ST=Isere/L=Grenoble/O=UGA/OU=Student/CN=Student User"

The CSR contains the public key and requested subject DN, signed by the user’s private key to prove key possession. The validity period is not in the CSR; the CA determines it. The CA signs:

openssl ca -config openssl.cnf -in user_request.csr -out newcert.pem \
  -days 90 -passin pass:"CASecurePassphrase2024!" -batch

Output confirmed serial 0x02 (read from demoCA/serial, which contained “02”), validity Oct 6 to Jan 4 2026. The index.txt entry:

V       260104122230Z           02      unknown /C=FR/ST=Isere/L=Grenoble/O=UGA/OU=Student/CN=Student User

Fields: status, expiration (YYMMDDHHMMSSZ), revocation date (empty), serial, filename, DN.

Revocation and the CRL

openssl ca -config openssl.cnf -revoke newcert.pem \
  -passin pass:"CASecurePassphrase2024!"

Output: Revoking Certificate 02. The index.txt entry changed to:

R       260104122230Z   251006122311Z   02      unknown /C=FR/ST=Isere/L=Grenoble/O=UGA/OU=Student/CN=Student User

Status changed from V to R, revocation timestamp 251006122311Z added. The certificate still exists and its signature still verifies. Revocation is a claim the CA makes in a separate structure, not a property of the certificate itself.

Generating the CRL:

openssl ca -config openssl.cnf -gencrl -out demoCA/crl.pem \
  -passin pass:"CASecurePassphrase2024!"
openssl crl -in demoCA/crl.pem -text -noout

The CRL output showed: Version 2, issuer the CA, Last Update Oct 6 12:23:23 2025 GMT, Next Update Nov 5 12:23:23 2025 GMT, CRL Number 1, and Revoked Certificate serial 02 with revocation time Oct 6 12:23:11 2025. The CRL is signed by the CA and has a validity period — relying parties must download a fresh CRL before the Next Update. The window between revocation and the next published CRL is the lag window where a revoked certificate can still be accepted.

OCSP: querying revocation in real time

OCSP solves the CRL lag problem with per-certificate real-time queries. Building a request:

openssl ocsp -issuer demoCA/cacert.pem -cert newcert.pem -reqout ocsp_request.der

The request contains a Certificate ID without the full certificate: issuer name hash 8012122E244D05313E868A3733CF5B06E10230CC, issuer key hash EE9BFC03F2234791CDC77941101688C6A750F596, and serial 02. A nonce (0410117DA8DBC2124C5E5FECA32C985B25BB) prevents replay attacks.

Running the responder:

openssl ocsp -index demoCA/index.txt -port 8888 \
  -rsigner demoCA/cacert.pem -rkey demoCA/private/cakey.pem \
  -CA demoCA/cacert.pem -passin pass:"CASecurePassphrase2024!"

Querying it:

openssl ocsp -issuer demoCA/cacert.pem -cert newcert.pem \
  -url http://localhost:8888 -resp_text

Response: Cert Status: revoked, Revocation Time: Oct 6 12:23:11 2025 GMT. The responder read index.txt, found serial 02 with status R, and returned a signed response. The response is signed by the CA so the client can verify it was not fabricated.

The OCSP trade-off versus CRL: OCSP is real-time but adds a round-trip per TLS handshake and reveals to the responder which certificates are being used. OCSP stapling eliminates both problems by having the TLS server pre-fetch and cache its own OCSP response and include it in the handshake.

What this lab teaches about trust

Building a CA from scratch makes one thing concrete: the trust in a certificate chain is not a mathematical property. The cryptography can only verify that a signature is valid and the certificate was not tampered with. Whether the certificate was issued correctly — whether the person who received it actually controls the domain or organization it claims — is entirely a function of the CA’s verification procedures and the integrity of the people running them.

A CA that issues certificates without proper identity verification breaks the entire trust model for its subscribers, regardless of whether the underlying key lengths and algorithms are sound. This is why browsers maintain curated trust stores and remove CAs that misissue, and why the CA/Browser Forum exists to set baseline issuance requirements. The cryptographic mechanisms are necessary but not sufficient.