JSON Web Tokens (JWT) are a popular way to secure communication between parties using digitally signed tokens. In this article, we will explore how to generate and validate JWT tokens in Java using PEM (Privacy Enhanced Mail) files. PEM files are commonly used to store cryptographic keys and certificates.

Generate JWT Token

In this section, we'll walk through how to generate a JWT token in Java using a private key stored in a PEM file.

Setting up the Environment

Before we proceed, make sure you have the following prerequisites:

  1. Java Development Kit (JDK) installed on your system.
  2. A PEM file containing a private key (referred to as <PRIVATE_KEY_PEM>).

Code Implementation

We'll use Java to create a JWT token. Here's the code for generating a JWT token:

import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
import java.util.UUID;

public class GenerateJWTSignedByPEM {

    public static void main(final String ... args) throws Exception {
        // Load the private key and certificate
        final var privateKeyPEM = """
                <PRIVATE_KEY_PEM>              
                """;

        final var privateKey = getPrivateKeyFromPEM(privateKeyPEM);

        // Create JWT claims
        final var subject = "user123";
        final var issuer = "yourapp.com";
        final var expirationTimeMillis = System.currentTimeMillis() + 3600 * 1000; // 1 hour
        final var jwtID = UUID.randomUUID().toString();

        // Build JWT claims
        final var jwtHeader = "{\"alg\":\"RS256\",\"typ\":\"JWT\"}";
        final var jwtClaims = "{\"sub\":\"" + subject + "\",\"iss\":\"" + issuer + "\",\"exp\":" + expirationTimeMillis + ",\"jti\":\"" + jwtID + "\"}";

        // Base64Url encode the JWT header and claims
        final var base64UrlHeader = base64UrlEncode(jwtHeader.getBytes());
        final var base64UrlClaims = base64UrlEncode(jwtClaims.getBytes());

        // Combine header and claims with a period separator
        final var headerClaims = base64UrlHeader + "." + base64UrlClaims;

        // Sign the JWT
        final var signature = signWithRSA(headerClaims, privateKey);

        // Combine the JWT components
        final var jwtToken = headerClaims + "." + signature;

        System.out.println("JWT Token: " + jwtToken);
    }

    // Helper function to load a PrivateKey from PEM format
    private static PrivateKey getPrivateKeyFromPEM(String privateKeyPEM) throws Exception {
        privateKeyPEM = privateKeyPEM.replace("-----BEGIN PRIVATE KEY-----", "")
                .replace("-----END PRIVATE KEY-----", "")
                .replaceAll("\\s+", "");

        final var privateKeyBytes = Base64.getDecoder().decode(privateKeyPEM);

        final var keyFactory = KeyFactory.getInstance("RSA");
        final var keySpec = new PKCS8EncodedKeySpec(privateKeyBytes);
        return keyFactory.generatePrivate(keySpec);
    }

    // Base64 URL encoding
    private static String base64UrlEncode(final byte[] data) {
        return Base64.getUrlEncoder().withoutPadding().encodeToString(data);
    }

    // Sign the JWT using RSA
    private static String signWithRSA(final String data, final PrivateKey privateKey) throws Exception {
        final var signature = Signature.getInstance("SHA256withRSA");
        signature.initSign(privateKey);
        signature.update(data.getBytes());
        final var signatureBytes = signature.sign();
        return base64UrlEncode(signatureBytes);
    }
}

Here's a breakdown of the code:

  1. We load the private key from the PEM file.
  2. Create JWT claims, including subject, issuer, expiration time, and a unique JWT ID.
  3. Base64Url encode the JWT header and claims.
  4. Combine the header and claims with a period separator.
  5. Sign the JWT using the private key.
  6. Combine all the JWT components to get the final JWT token.

Make sure to replace <PRIVATE_KEY_PEM> with the actual content of your private key PEM file.

Validate JWT Token

In this section, we'll learn how to validate a JWT token using a public certificate stored in a PEM file.

Setting up the Environment

Ensure you have the following prerequisites:

  1. Java Development Kit (JDK) installed on your system.
  2. A PEM file containing a public certificate (referred to as <PUBLIC_CERT_PEM>).
  3. A JWT token you want to validate (referred to as <JWT_TOKEN>).

Code Implementation

We'll use Java to validate a JWT token. Here's the code:

import java.io.ByteArrayInputStream;
import java.security.PublicKey;
import java.security.Signature;
import java.security.cert.X509Certificate;
import java.util.Base64;

public class ValidateJWTSignedByPEM {
    public static void main(final String ... args) throws Exception {

        // The JWT token to validate.
        final var jwtToken = "<JWT_TOKEN>";

        // Load the X.509 certificate
        final var certificatePEM = """
                <PUBLIC_CERT_PEM>              
                """;

        final var certificate = getCertificateFromPEM(certificatePEM);

        // Parse JWT components
        final var jwtParts = jwtToken.split("\\.");
        if (jwtParts.length != 3) {
            System.out.println("Invalid JWT format");
            return;
        }

        // Decode and verify the JWT signature
        final var base64UrlHeader = jwtParts[0];
        final var base64UrlClaims = jwtParts[1];
        final var signature = jwtParts[2];

        // Verify the signature
        if (verifySignature(base64UrlHeader, base64UrlClaims, signature, certificate.getPublicKey())) {
            System.out.println("JWT signature is valid");
        } else {
            System.out.println("JWT signature is invalid");
        }
    }

    private static X509Certificate getCertificateFromPEM(String certificatePEM) throws Exception {
        certificatePEM = certificatePEM.replace("-----BEGIN CERTIFICATE-----", "")
                .replace("-----END CERTIFICATE-----", "")
                .replaceAll("\\s+", "");

        final var certificateBytes = Base64.getDecoder().decode(certificatePEM);

        final var certificateFactory = java.security.cert.CertificateFactory.getInstance("X.509");
        return (X509Certificate) certificateFactory.generateCertificate(new ByteArrayInputStream(certificateBytes));
    }

    private static boolean verifySignature(final String base64UrlHeader, final String base64UrlClaims, final String signature, final PublicKey publicKey) throws Exception {
        final var signedData = base64UrlHeader + "." + base64UrlClaims;
        final var signatureBytes = Base64.getUrlDecoder().decode(signature);

        final var verifier = Signature.getInstance("SHA256withRSA");
        verifier.initVerify(publicKey);
        verifier.update(signedData.getBytes());

        return verifier.verify(signatureBytes);
    }
}

Here's how the code works:

  1. Load the JWT token and public certificate from their respective PEM files.
  2. Parse the JWT token into its components: header, claims, and signature.
  3. Verify the signature by re-signing the header and claims and comparing it with the provided signature.

Replace <PUBLIC_CERT_PEM> and <JWT_TOKEN> with the actual content of your public certificate PEM file and the JWT token you want to validate.

Summary

In this article, we've explored how to generate and validate JWT tokens in Java using PEM files. This approach allows you to secure your applications by creating and verifying digitally signed tokens. Make sure to keep your private keys and certificates secure, as they are crucial for the security of your JWT-based authentication system.

Related Topic

Generating a Self-signed CA Certificate for JSON Web Token (JWT) in Java