Alice’s private key is stored in a Java KeyStore (JKS) file. The server already knows the corresponding public key. Alice will prove possession of the private key by signing a fresh challenge.
For this warm-up, the store password is classroom-store and the key password is warmup-key-2026. JKS can use separate passwords for file integrity and private-key protection.
Inspect the keystore
$ keytool -list -storetype JKS -keystore data/artifacts/warmup.jks -storepass classroom-store
Find the alias alice and the PrivateKeyEntry. The associated certificate contains the public key; the private key itself is protected inside the file.
Explanation:
keytool -listdisplays entries without exporting their private material.-storetype JKStells Java to parse this legacy Java KeyStore format.-keystoreselects the lab file, while-storepasssupplies its integrity password. It is not necessarily the password that protects Alice’s private key.
About the JKS migration warning.
keytoolwarns that JKS is proprietary and recommend PKCS#12. Following our NDSS work, Mind Your Keys? A Security Evaluation of Java Keystores, this warning was introduced alongside the fixes and mitigations developed after responsible disclosure of weaknesses in Java keystore implementations. See also the open NDSS presentation slides. In this lab, we use JKS to demonstrate how quickly weak passwords can be brute-forced. Its legacy password protection has a fixed work factor that cannot be configured, which is one reason modern tools favor PKCS#12.
Sign a fresh challenge
This task uses a simple public-key challenge-response protocol. A Python helper generates a fresh challenge, and a Java program signs it using the user’s private key stored in the keystore.
$ python3 tools/labctl.py challenge warmup $ LAB_KEY_PASSWORD=warmup-key-2026 java java/LabSigner.java data/artifacts/warmup.jks work/challenge.txt work/signature.b64
Explanation:
labctl.py challenge warmupasks the local server for a fresh, single-use challenge and writes it towork/challenge.txt.LAB_KEY_PASSWORD=...sets the private-key password only for the Java program; it is not stored in the shell environment afterwards.java java/LabSigner.javaloads the private key from the JKS filedata/artifacts/warmup.jks, signs the challengework/challenge.txt, and store the Base64-encoded signature inwork/signature.b64
Observe integrity of the signed message
$ openssl base64 -d -A -in work/signature.b64 -out work/signature.bin $ openssl dgst -sha256 -verify data/artifacts/warmup-public.pem -signature work/signature.bin work/challenge.txt
The first command decodes the Base64 signature; the second verifies it with the public key. You should see Verified OK. Now verify the same signature against a different message:
$ (cat work/challenge.txt; echo -n 'x') > work/altered.txt $ openssl dgst -sha256 -verify data/artifacts/warmup-public.pem -signature work/signature.bin work/altered.txt
The first command appends an x to the challenge data and saves the modified message as work/altered.txt. The subsequent signature check must fail because a signature is valid only for the exact message bytes that were signed.
Exercise 1
Practical exercise: test what the server accepts. Use the original work/challenge.txt and work/signature.b64 produced above; do not recreate them yet.
- Keep that original pair unchanged. First try two rejected submissions using separate, disposable challenges:
$ python3 tools/labctl.py challenge warmup --out work/other-challenge.txt $ python3 tools/labctl.py submit --challenge work/other-challenge.txt --signature work/signature.b64
Explanation:
- The first command requests a new challenge and saves the message in
work/other-challenge.txtand its identifier inwork/other-challenge.json. - The second command runs the
submitaction.--challengeselects the challenge file, from whichlabctl.pylocates the adjacent.jsonrecord, while--signatureselects the Base64 signature to submit. The tool sends the challenge identifier and signature to the server.
Here, the new challenge is deliberately combined with the signature created for the original challenge, so the server must reject it.
$ python3 tools/labctl.py challenge warmup --out work/bad-signature-challenge.txt $ printf "AAAA" > work/bad-signature.b64 $ python3 tools/labctl.py submit --challenge work/bad-signature-challenge.txt --signature work/bad-signature.b64
Explanation: These commands request a fresh challenge, create a deliberately invalid Base64 signature, and submit the pair. The server must reject the invalid signature.
- Finally, request and sign a fresh challenge because the original challenge may have expired while you completed the experiments:
$ python3 tools/labctl.py challenge warmup $ LAB_KEY_PASSWORD=warmup-key-2026 java java/LabSigner.java data/artifacts/warmup.jks work/challenge.txt work/signature.b64
Submit the fresh pair, then immediately replay the same pair:
$ python3 tools/labctl.py submit --challenge work/challenge.txt --signature work/signature.b64 $ python3 tools/labctl.py submit --challenge work/challenge.txt --signature work/signature.b64
The first submission returns the password for Task 2. The second is rejected because the server has already consumed the challenge. Record the Task 2 password before continuing.
Explanation: Both commands submit the same challenge and signature. The first valid submission consumes the fresh challenge; the replay demonstrates that a valid signature cannot be reused.
Reference: Oracle: keytool.