Sign and Verify a JAR File with keytool and jarsigner

Create a code-signing keypair with keytool, sign a sample JAR with jarsigner, verify the signature, and read the PKIX chain warnings jarsigner prints for a self-signed certificate.

Published

Updated

Read time 9 min read

Reviewed byDeepak Prasad

Sign and verify JAR with keytool and jarsigner banner with PKCS12 keystore and signed Java archive

jarsigner signs and verifies Java Archive (JAR) files using a private key and certificate from a keystore. Oracle ships it beside keytool in every JDK: you create the signing keypair with keytool -genkeypair, then jarsigner writes signature metadata under META-INF/ and anyone with your public certificate can check that the archive was not tampered with.

This guide builds a tiny runnable JAR on Ubuntu, signs it with a PKCS12 keystore, verifies the signature, and walks through the chain warnings jarsigner prints for a self-signed certificate — the same PKIX messages you see when a corporate code-signing CA is missing from trust. For keystore basics, see Java keystore vs truststore. For CA-signed signing keys, see Create a CSR and import a CA-signed certificate.

Tested on: Ubuntu 26.04 LTS; OpenJDK 25.0.3; kernel 7.0.0-27-generic.


Quick answer: keytool plus jarsigner

Tool Role
keytool Create PKCS12 keystore, generate RSA keypair (PrivateKeyEntry), list aliases
jarsigner Sign JAR (jar signed.) or verify signature (jar verified.) using keystore entry
keytool -printcert -jarfile Read signer certificate from a signed JAR without opening the keystore

jarsigner never generates keys — it only consumes what keytool stored.


Prerequisites

  • OpenJDK 11+ with keytool, jarsigner, jar, and javac (Install keytool on Ubuntu) — see keytool command.
  • A writable working directory for the lab files.
  • Password changeit below matches other guides in this course.

Step 1 — Build a sample JAR

Create a minimal application and package it with an explicit Main-Class:

bash
mkdir -p ~/jar-sign-lab/src/com/example ~/jar-sign-lab/build
cd ~/jar-sign-lab

cat > src/com/example/Hello.java << 'EOF'
package com.example;
public class Hello {
    public static void main(String[] args) {
        System.out.println("Hello from signed JAR lab");
    }
}
EOF

javac -d build src/com/example/Hello.java
printf 'Manifest-Version: 1.0\nMain-Class: com.example.Hello\n\n' > manifest.txt
jar --create --file demo.jar -m manifest.txt -C build .

Confirm the unsigned archive runs:

bash
java -jar demo.jar
text
Hello from signed JAR lab

Signing happens after packaging. You can also sign an existing JAR from a build tool — jarsigner modifies the file in place unless you pass -signedjar.


Step 2 — Create a signing keypair with keytool

Generate a self-signed code-signing key inside PKCS12. For production you would use a CA-issued code-signing certificate; a self-signed key is enough to learn the jarsigner workflow and reproduce chain warnings:

bash
STOREPASS=changeit

keytool -genkeypair -alias codesigner -keyalg RSA -keysize 2048 -validity 365 \
  -keystore signer.p12 -storetype PKCS12 -storepass "$STOREPASS" \
  -dname "CN=Demo Code Signer, OU=Lab, O=GoLinuxCloud, C=US" \
  -ext "KU=digitalSignature" \
  -ext "EKU=codeSigning"

Confirm the certificate is marked for code signing — Oracle treats missing codeSigning extended key usage as a severe badExtendedKeyUsage warning during verify:

bash
keytool -list -v -alias codesigner -keystore signer.p12 -storepass "$STOREPASS" \
  | grep -A4 -E 'KeyUsage|ExtendedKeyUsage'
text
ExtendedKeyUsages [
  codeSigning
]

#2: ObjectId: 2.5.29.15 Criticality=false
KeyUsage [
  DigitalSignature
]

List the keystore — you need a PrivateKeyEntry alias for jarsigner:

bash
keytool -list -keystore signer.p12 -storepass "$STOREPASS"
text
Keystore type: PKCS12

Your keystore contains 1 entry

codesigner, Jul 3, 2026, PrivateKeyEntry,
Certificate fingerprint (SHA-256): 64:B3:CC:57:74:29:7C:DF:8D:9C:12:2A:59:B1:28:55:15:92:FE:F8:73:B1:81:1E:96:48:DC:2E:92:74:8F:81

The alias name (codesigner) becomes the base name for META-INF/CODESIGN.SF and META-INF/CODESIGN.RSA (uppercased, max eight characters).


Step 3 — Sign the JAR with jarsigner

NOTE
This lab passes -storepass on the command line for repeatability. In production, avoid exposing keystore passwords in shell history, CI logs, or process listings. Use protected CI secrets, environment injection, prompt-based entry, or a secured file-based secret mechanism.

Point jarsigner at the keystore, store password, JAR file, and alias:

bash
jarsigner -keystore signer.p12 -storetype PKCS12 -storepass "$STOREPASS" \
  demo.jar codesigner
text
jar signed.

Warning:
The signer's certificate is self-signed.

jar signed. means signature files were written. The self-signed warning at sign time is informational — verification performs the full chain check.

To keep the original unsigned file untouched, package a fresh copy and write a separate signed output:

bash
jar --create --file demo-unsigned.jar -m manifest.txt -C build .
jarsigner -keystore signer.p12 -storetype PKCS12 -storepass "$STOREPASS" \
  -signedjar demo-signed.jar demo-unsigned.jar codesigner

Run the signed output file:

bash
java -jar demo-signed.jar
text
Hello from signed JAR lab

The in-place demo.jar from the first sign command also runs with java -jar demo.jar — both archives contain the same signed classes.


Step 4 — Verify the signature

Basic verification checks that signatures match file digests:

bash
jarsigner -verify demo.jar
text
jar verified.

Warning:
This jar contains entries whose certificate chain is invalid. Reason: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
This jar contains entries whose signer certificate is self-signed.
This jar contains signed entries that are not signed by alias in this keystore.
This jar contains signatures that do not include a timestamp. Without a timestamp, users may not be able to validate this jar after any of the signer certificates expire (as early as 2027-07-03).

Re-run with the -verbose and -certs options for more details.

jar verified. means the archive integrity check passed — no file listed in the signature was modified after signing. The warnings underneath are about trust in the signer certificate, not about tampering.

An unsigned copy fails immediately:

bash
jar --create --file unsigned.jar -m manifest.txt -C build .
jarsigner -verify unsigned.jar
text
jar is unsigned.

What happens if the JAR changes after signing?

Modify a signed entry and verification fails. Add a new file after signing and jarsigner may still verify the original signed entries but warn about unsigned content — Oracle documents this in the jarsigner man page:

bash
cp demo.jar demo-tampered.jar
echo "new file after signing" > extra.txt
jar --update --file demo-tampered.jar extra.txt
jarsigner -verify -verbose demo-tampered.jar
text
? = unsigned entry

jar verified.

Warning:
This jar contains unsigned entries which have not been integrity-checked.

The ? marker flags extra.txt as unsigned. Re-sign the JAR after all contents are final. The original demo.jar is unchanged and is used in the next steps.


Step 5 — Read chain warnings with -verbose -certs

Add -verbose -certs to see per-entry signature status and the PKIX chain result Oracle documents in the jarsigner man page:

bash
jarsigner -verify -verbose -certs demo.jar

Trimmed output from the lab:

text
s        203 Fri Jul 03 16:11:34 IST 2026 META-INF/MANIFEST.MF

      >>> Signer
      X.509, CN=Demo Code Signer, OU=Lab, O=GoLinuxCloud, C=US
      Signature algorithm: SHA384withRSA, 2048-bit RSA key
      [certificate is valid from 7/3/26, 4:11 PM to 7/3/27, 4:11 PM]
      [Invalid certificate chain: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target]

sm       441 Fri Jul 03 16:11:26 IST 2026 com/example/Hello.class

      >>> Signer
      X.509, CN=Demo Code Signer, OU=Lab, O=GoLinuxCloud, C=US
      Signature algorithm: SHA384withRSA, 2048-bit RSA key
      [Invalid certificate chain: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target]

  s = signature was verified
  m = entry is listed in manifest
  k = at least one certificate was found in keystore

jar verified.

The sm prefix on Hello.class means the entry is listed in the manifest and its signature verified. Invalid certificate chain appears because the signer cert is self-signed and not anchored in a trusted certificate available to jarsigner during verification. That is expected in this lab.

When you verify with a truststore that contains the issuing CA — for example $JAVA_HOME/lib/security/cacerts for public code-signing CAs or a corporate code-signing CA truststore — chain validation can succeed:

bash
jarsigner -verify -verbose -certs -keystore /path/to/truststore.p12 -storetype PKCS12 \
  -storepass "$STOREPASS" demo.jar

For this self-signed lab keystore, passing -keystore signer.p12 may help jarsigner match the alias, but it does not turn a self-signed lab certificate into a publicly trusted signing certificate. With a CA-imported chain in the keystore, the >>> Signer block shows the alias and omits Invalid certificate chain. Timestamp warnings may still appear if you did not pass -tsa.


Step 6 — Use -strict for severe warnings

Oracle treats invalid chains and self-signed signers as severe warnings. Without -strict, jarsigner exits 0 even when those warnings print. With -strict, severe warnings become errors:

bash
jarsigner -verify -strict demo.jar
text
jar verified, with signer errors.

Error:
This jar contains entries whose certificate chain is invalid. Reason: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
This jar contains entries whose signer certificate is self-signed.

Warning:
This jar contains signed entries that are not signed by alias in this keystore.
This jar contains signatures that do not include a timestamp. Without a timestamp, users may not be able to validate this jar after any of the signer certificates expire (as early as 2027-07-03).

Use -strict in CI pipelines when you want a non-zero exit code for untrusted signers, even if the JAR bytes were not tampered with.


Step 7 — Inspect the signer certificate

Read the embedded certificate without the keystore password:

bash
keytool -printcert -jarfile demo.jar

Sample output:

text
Signer #1:

Signature:

Owner: CN=Demo Code Signer, OU=Lab, O=GoLinuxCloud, C=US
Issuer: CN=Demo Code Signer, OU=Lab, O=GoLinuxCloud, C=US
Serial number: ...
Valid from: Fri Jul 03 16:11:34 IST 2026 until: Sat Jul 03 16:11:34 IST 2027
Certificate fingerprints:
	 SHA256: 64:B3:CC:57:74:29:7C:DF:8D:9C:12:2A:59:B1:28:55:15:92:FE:F8:73:B1:81:1E:96:48:DC:2E:92:74:8F:81
Signature algorithm name: SHA384withRSA
Subject Public Key Algorithm: 2048-bit RSA key
Version: 3

Owner equals Issuer here because the certificate is self-signed.


Fix chain warnings for real deployments

Lab symptom Production fix
certificate chain is invalid / PKIX Ensure the keystore contains the full chain (trustedCertEntry for CA, PrivateKeyEntry for signer); distribute the CA to verifiers or use -keystore during verify
signatures that do not include a timestamp Add -tsa http://timestamp.digicert.com (or your TSA URL) when signing
signed entries not signed by alias in this keystore Pass -keystore and the expected alias during verify, or ignore if you only need integrity
NOTE
jar verified confirms digests match the signature. It does not mean every verifier will trust the signer. Treat chain warnings as a trust problem, not a signing failure.

Troubleshooting

Symptom Likely cause Fix
jarsigner: unable to sign jar / alias not found Wrong alias or empty keystore keytool -list -keystore signer.p12 and match alias
Keystore was tampered with, or password was incorrect Wrong -storepass Use the password from keytool -genkeypair
jar is unsigned Never signed or signature stripped Re-run jarsigner without -verify
Integrity OK but chain invalid Self-signed or private CA not in trust Import CA chain; see Fix PKIX path building failed
unsigned entries which have not been integrity-checked Files added after signing Re-sign the JAR after all contents are final
badKeyUsage / badExtendedKeyUsage Cert not marked for code signing Add -ext KU=digitalSignature -ext EKU=codeSigning to keytool -genkeypair or reissue with extendedKeyUsage = codeSigning

References


Summary

Create a signing PrivateKeyEntry with keytool -genkeypair in PKCS12 (include EKU=codeSigning), then run jarsigner -keystore signer.p12 demo.jar codesigner to embed a signature. jarsigner -verify demo.jar prints jar verified when digests match. Add -verbose -certs to see Invalid certificate chain for a self-signed lab key — the signer is not anchored in a trusted certificate available during verification. Use -strict when CI should fail on untrusted chains, pass -keystore with your CA truststore for explicit verification, and add -tsa for timestamped signatures that survive certificate expiry.


Frequently Asked Questions

1. What is the difference between keytool and jarsigner?

keytool creates and manages keystores — private keys and certificates in PKCS12 files. jarsigner reads a keystore entry and embeds a digital signature inside a JAR file. You generate the signing key with keytool, then call jarsigner -keystore with the alias.

2. How do I verify a signed JAR file?

Run jarsigner -verify demo.jar. A successful check prints jar verified. Add -verbose -certs to see signer DN, digest algorithm, and certificate chain status. Use -strict to treat severe warnings such as an invalid chain as errors.

3. Why does jarsigner say certificate chain is invalid?

jarsigner validates the signer certificate chain against trusted certificates available during verification. A self-signed lab certificate or a private CA that is not trusted triggers PKIX path building failed. For explicit verification, pass -keystore with a truststore that contains the issuing CA, such as $JAVA_HOME/lib/security/cacerts or your corporate code-signing CA truststore.

4. Does signing a JAR change how java -jar runs it?

No. Signing adds META-INF signature files but does not change application bytecode. A correctly signed JAR still runs with java -jar. Verification proves integrity and identifies the signer; it is separate from execution.

5. What does jar verified with signer errors mean?

The cryptographic signature matches the JAR contents, but jarsigner found severe warnings — often an invalid certificate chain or self-signed signer. Without -strict the exit code may still be 0. With -strict, jarsigner exits non-zero and prints jar verified, with signer errors.

6. Do I need a timestamp when signing JARs?

Production code signing should use a Timestamping Authority (-tsa URL) so the signature stays valid after the signer certificate expires. Lab self-signed certs work without a timestamp; jarsigner prints an informational warning when one is missing.
Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with more than 15 years of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive …