Skip to main content

    How to Sign a JAR with jarsigner (Step by Step)

    Sign a JAR with jarsigner: the right keystore flags for a file or PKCS#11 token, a timestamp via -tsa, verification with -verify -certs, and common fixes.

    MS
    My-SSL Security Team
    ·
    12 min read
    ·
    Published July 6, 2026
    ·
    Last updated July 6, 2026

    In short

    To sign a JAR, run jarsigner -keystore keystore.p12 -tsa http://time.certum.pl MyApp.jar myalias. jarsigner hashes every entry in the archive, signs those hashes with your certificate's private key, and stores the result inside META-INF/. Always pass -tsa — without a timestamp the signature dies with your certificate. If the certificate is publicly trusted, the key lives on hardware, so you point jarsigner at a PKCS#11 config file instead of a keystore file.

    jarsigner has shipped with every JDK since the nineties, and the core command hasn't changed much. What has changed is everything around it: keys for publicly trusted certificates now live on hardware tokens or in cloud services and are reached through PKCS#11, modern JDKs silently treat SHA-1-signed archives as unsigned, and a signature applied without a timestamp quietly expires with the certificate. This guide walks the whole path — what signing writes into the JAR, which keystore setup fits your situation, the exact commands, and the warnings jarsigner prints when something is off.

    What signing a JAR actually does

    Signing a JAR writes proof of origin into the archive itself: jarsigner computes a SHA-256 digest for every entry, records those digests in the manifest, then signs them with your certificate's private key and stores the signature — with your certificate chain and an optional timestamp — as new files under META-INF/. Anyone holding the JAR can later confirm both who signed it and that not a single byte has changed since.

    What jarsigner adds inside a JAR fileBefore-and-after diagram of a JAR archive. The unsigned JAR contains META-INF/MANIFEST.MF and the application classes. After running jarsigner, the manifest gains a SHA-256 digest for every entry, and two new files appear in META-INF: MYALIAS.SF with digests of the manifest entries, and MYALIAS.RSA holding the signature, the certificate chain, and the timestamp token.What jarsigner adds inside the JARMyApp.jar (unsigned)META-INF/MANIFEST.MFcom/app/Main.classcom/app/… , resourcesNothing proves who built thisjarsignerMyApp.jar (signed)META-INF/MANIFEST.MF+ SHA-256 digest for every entryMETA-INF/MYALIAS.SFdigests of the manifest entriesMETA-INF/MYALIAS.RSAsignature + certificate chain+ timestamp tokencom/app/… (unchanged)
    Your classes never change — the proof lives entirely in three META-INF files, which is why adding a file after signing leaves it invisibly uncovered.

    Three files carry the whole mechanism. The manifest (MANIFEST.MF) gains a digest line for every entry in the archive. The signature file (MYALIAS.SF, named after your keystore alias) contains digests of the manifest's sections, so the manifest can't be quietly rewritten. And the signature block (MYALIAS.RSA, or .EC for elliptic-curve keys) holds the actual cryptographic signature plus the certificates needed to check it. Your classes and resources are untouched — which is also why a signed and unsigned JAR behave identically at runtime.

    One detail that surprises people coming from Windows code signing: coverage is per entry, not per file-blob. If any tool adds an entry to the archive after signing, the JAR doesn't become invalid — it becomes partially signed, and verification flags it as containing unsigned entries. That's the root cause behind most mysterious verification failures in build pipelines.

    When do you still need to sign a JAR?

    You need to sign a JAR when something downstream verifies it: a plugin framework, an enterprise deployment policy, middleware that refuses unsigned archives, or customers who check vendor artifacts before letting them near production. The applet era that made JAR signing famous is over — browsers dropped the Java plugin years ago and Java Web Start left the JDK with version 11 — but the verification habit moved into infrastructure rather than disappearing.

    • Plugin ecosystems. Eclipse p2 and other OSGi-based installers surface warnings — or refuse installation outright, depending on policy — for unsigned bundles. If you distribute Eclipse or IDE plugins, signing is part of shipping.
    • Enterprise and middleware requirements. Plenty of corporate Java estates verify signatures on the JARs they deploy, and products like Oracle E-Business Suite require customizations to be signed with a trusted certificate.
    • Supply-chain assurance. A signed, timestamped JAR gives customers a one-command check (jarsigner -verify) that the artifact they downloaded is byte-for-byte what you released — independent of the download channel.

    Worth being clear about what JAR signing is not: Maven Central verifies PGP signatures, not jarsigner signatures, and a desktop app packaged with jpackage gets signed at the installer level with OS-native tools — SignTool on Windows. jarsigner is specifically for the archives the Java world itself verifies.

    What you need before you sign

    Two things: a JDK (jarsigner and keytool live in its bin directory, no separate download) and a certificate with the code signing purpose whose private key jarsigner can reach. Where that certificate comes from depends on who must trust the result — the same three-tier logic as every other kind of code signing.

    • Testing and internal tools: keytool -genkeypair -alias myalias -keyalg RSA -keysize 3072 -validity 365 -keystore keystore.p12 creates a self-signed certificate in a PKCS#12 keystore (the default format since JDK 9). Signatures verify only where you've imported that certificate into the truststore by hand.
    • Inside one organization: a code signing certificate from your internal CA, imported into a keystore, verifies wherever machines already trust that CA's root.
    • Software that leaves your organization: you need a certificate from a publicly trusted CA, issued to a validated identity. My-SSL carries Certum standard and EV code signing certificates that jarsigner uses the same way SignTool does — Certum's signing manual documents both tools side by side. The validation paperwork is covered in the required documents guide.

    No .p12 file for publicly trusted keys

    Under CA/Browser Forum rules in force since June 1, 2023, publicly trusted code signing keys must be generated and kept on certified hardware — a USB token, smart card, HSM, or a cloud signing service. You will not receive a keystore file to pass to -keystore. Instead, jarsigner talks to the hardware through PKCS#11, which is what the next section sets up. The self-signed keystore path above remains available for anything that doesn't need public trust.

    Sign the JAR with jarsigner

    With a keystore file, one command does everything: name the keystore, name a timestamp authority with -tsa, then give the JAR and the alias of the key to sign with. jarsigner prompts for the keystore password, rewrites the archive in place (or writes a copy if you pass -signedjar), and prints jar signed. on success.

    Sign a JAR from a PKCS#12 keystore file
    # Check which alias your keystore actually contains
    keytool -list -keystore keystore.p12
    
    # Sign in place, with a timestamp
    jarsigner -keystore keystore.p12 \
      -tsa http://time.certum.pl \
      MyApp.jar myalias
    
    # Or keep the original and write a signed copy
    jarsigner -keystore keystore.p12 \
      -tsa http://time.certum.pl \
      -signedjar MyApp-signed.jar MyApp.jar myalias
    
    # Expected output:
    #   jar signed.

    Unlike SignTool, you don't need to force the hash algorithm: a current JDK defaults to SHA-256 digests and, for RSA keys, a SHA256withRSA signature — sensible defaults you'd only override with -digestalg and -sigalg in unusual compatibility situations. The flag you should treat as mandatory is -tsa. Without it, jarsigner still signs but warns that the JAR may not validate after the certificate expires — and it means exactly that: every copy you ever shipped stops verifying on that date. Our timestamping guide covers why in detail; the short version is that the timestamp proves signing happened while the certificate was valid, which lets the signature outlive it.

    The trailing myalias argument trips up more first attempts than any flag: it's the name of the key entry inside the keystore, not a file name or your company name. When in doubt, keytool -list shows every alias the keystore holds.

    Signing from a USB token or the cloud (PKCS#11)

    When the private key lives on a hardware token or in a cloud signing service, jarsigner reaches it through PKCS#11: you write a two-line config file pointing at the vendor's PKCS#11 library, then tell jarsigner to use that instead of a keystore file. The signing command itself barely changes, and the token PIN takes the place of the keystore password.

    Where your key lives decides the jarsigner keystore flagsTwo paths converge on the same jarsigner command. Left path: a PKCS#12 keystore file, used for test and internal-CA certificates, reached with the flag -keystore keystore.p12. Right path, highlighted: a hardware USB token or SimplySign cloud service, used for publicly trusted certificates, reached with -keystore NONE, -storetype PKCS11 and a provider configuration file. Both end in jarsigner signing MyApp.jar with a timestamp from time.certum.pl.Where the key lives decides the flagsKeystore file (PKCS#12)self-signed tests · internal CA certs-keystore keystore.p12USB token / SimplySign cloudrequired for publicly trusted certs-keystore NONE -storetype PKCS11-providerArg provider.cfgjarsigner … -tsa http://time.certum.pl MyApp.jar myaliassame command, same timestamp — only the keystore flags differ
    Switching from a test keystore to a production token changes three flags, not your build script — worth knowing before you wire signing into CI.
    Sign with a Certum card or SimplySign through PKCS#11
    # provider.cfg — points Java at the token's PKCS#11 library
    # (path shown for a Certum card on Windows; the SimplySign
    #  desktop app installs its own library)
    name = Certum
    library = C:\Windows\System32\crypto3PKCS.dll
    
    # List the aliases the token exposes
    keytool -list -keystore NONE -storetype PKCS11 \
      -providerClass sun.security.pkcs11.SunPKCS11 \
      -providerArg provider.cfg
    
    # Sign — jarsigner prompts for the token PIN
    jarsigner -keystore NONE -storetype PKCS11 \
      -providerClass sun.security.pkcs11.SunPKCS11 \
      -providerArg provider.cfg \
      -tsa http://time.certum.pl \
      MyApp.jar "certificate alias"

    The alias on a token is usually derived from the certificate's Common Name — your validated company or personal name, often with spaces, so quote it. If the keytool -list step shows no aliases, the token middleware isn't installed or the card isn't connected; sort that out before blaming jarsigner. The same PKCS#11 setup works whether the key sits on a physical Certum card or in Certum's SimplySign cloud service, where a desktop app stands in for the card reader — handy when the person (or CI job) signing isn't near a USB port. The trade-offs between those two setups get a full article in cloud signing vs USB tokens.

    Verify the signature

    Verification is one command: jarsigner -verify -verbose -certs MyApp.jar. It re-digests every entry, checks the signature and the certificate chain, and ends with jar verified. when everything holds. The -verbose -certs pair is what makes the output useful: per-entry flags, the full signer chain, and a Timestamped by line confirming your -tsa flag did its job.

    Verify a signed JAR
    jarsigner -verify -verbose -certs MyApp.jar
    
    # Healthy output ends with:
    #   - Signed by "CN=Your Company, O=Your Company, C=..."
    #   - Timestamped by "CN=Certum Timestamping 2024 CA" on Mon Jul 06 ...
    #   jar verified.
    
    # In CI, add -strict so problems become a non-zero exit code
    jarsigner -verify -strict MyApp.jar

    Read the per-entry flags in the verbose listing: s means the entry's signature verified, m means it's listed in the manifest. An entry with neither is unsigned — the partially-signed trap from earlier. And note that plain -verify is forgiving by design; it can print jar verified. followed by warnings you'd rather treat as failures. -strict turns those warnings into exit codes, which is the behavior you want in a pipeline.

    Three verification outcomes for a signed JARThree rows compare how jarsigner -verify judges a JAR over time. Row one, highlighted: signed with current defaults and timestamped — the signature keeps verifying after the certificate expires. Row two: signed but not timestamped — verification warns, and trust ends the day the certificate expires. Row three: signed with SHA-1 after 2019 — modern JDKs disable the algorithm and treat the JAR as unsigned.How the same JAR verifies over timeSHA-256 + timestamp (-tsa)the setup this guide producesjar verified.still valid after the certificate expiresSHA-256, no timestampjarsigner warns at signing timevalid — until the cert expiresthen every copy ever shipped stops verifyingSHA-1 signed (after 2019)old JDK defaults, legacy pipelinestreated as unsigneddisabled by JDK policy since late 2022
    Only the first row survives both your certificate's expiry and the JDK's algorithm policy — the two clocks that break signatures long after you've stopped thinking about them.

    Common warnings and what they mean

    jarsigner rarely fails outright — its habit is to sign or verify anyway and attach a warning, which makes the warnings worth reading carefully. These are the ones that actually stop people, roughly in the order they tend to appear.

    "Certificate chain not found for: myalias"

    The alias doesn't exist in the keystore you named, or it exists but has no private key. Run keytool -list against the same keystore and copy the alias exactly — it's case-insensitive but must otherwise match. On tokens, remember the alias comes from the certificate's Common Name, not anything you chose.

    "keystore load: … password was incorrect" or "keystore was tampered with"

    Wrong keystore password — the "tampered with" phrasing is historical JKS wording for the same thing. Against a PKCS#11 token, the "password" is the token PIN; too many wrong attempts can lock the card, so check with the vendor's card manager app rather than retrying blindly.

    "no -tsa or -tsacert is provided … may not be able to validate this jar after the signer certificate's expiration"

    You signed without a timestamp. The signature works today and breaks the day the certificate expires. Re-run the same command with -tsa http://time.certum.pl (any RFC 3161 timestamp authority works) — re-signing simply replaces the old signature.

    "This jar contains unsigned entries" or "entries whose signer certificate … " after a build

    Something modified the archive after signing — shade/assembly plugins, resource filtering, even a well-meaning re-zip. Coverage is per entry, so the added files are simply outside the signature. Make jarsigner the final build step and verify with -strict right after.

    "jar treated as unsigned, because it is signed with a weak algorithm that is now disabled"

    The SHA-1 policy. Since the October 2022 JDK updates, archives whose digest, signature, or timestamp uses SHA-1 are treated as unsigned unless they carry a timestamp from before January 1, 2019. You'll see this on JARs signed years ago with old defaults, or by pipelines that still pass -digestalg SHA1. The fix is to re-sign with a current JDK and a timestamp — the defaults are already right.

    "The signer's certificate is self-signed"

    Expected with a keytool-generated test certificate — it's a statement of fact, not an error. It disappears when you sign with a CA-issued certificate whose chain ends in a trusted root. If you're seeing it in production distribution, that's the signal you've outgrown the test setup.

    Frequently Asked Questions

    Get instant answers to common questions about SSL certificates and our services.

    Still Have Questions?

    Our SSL experts are available 24/7 to help with any questions about certificates, installation, or technical issues.

    The bottom line

    Find your alias with keytool -list, sign with jarsigner -keystore … -tsa http://time.certum.pl MyApp.jar myalias, and confirm with jarsigner -verify -strict. Two habits carry all the long-term weight: never sign without the timestamp, and always sign as the last build step. When your JARs need to verify on machines that have never heard of you, a Certum code signing certificate plugs into this exact workflow through PKCS#11 — and the same certificate signs your Windows binaries too.