How to Timestamp Files in Java

Qualified Timestamping in Java using BouncyCastle

Learn how to request and verify qualified RFC 3161 timestamp tokens (.tsr) in Java using the popular BouncyCastle Provider library.

Prerequisites & Dependencies:

Add the official BouncyCastle PKIX/CMS library to your pom.xml (Maven) or build.gradle:

<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcpkix-jdk18on</artifactId>
    <version>1.78.1</version>
</dependency>

Step 1: Request a Timestamp Token (.tsr)

Compute the SHA-256 hash of your target file, construct a TimeStampRequest using BouncyCastle, and send it over HTTP POST with the application/timestamp-query media type.

import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
import org.bouncycastle.tsp.*;

import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.file.Files;
import java.security.MessageDigest;

public class JavaTimestampClient {

    public static void requestTimestamp(String filePath, String outputTsrPath, String tsaUrl) throws Exception {
        // 1. Compute SHA-256 hash of the target file
        byte[] fileBytes = Files.readAllBytes(new File(filePath).toPath());
        MessageDigest digest = MessageDigest.getInstance("SHA-256");
        byte[] hash = digest.digest(fileBytes);

        // 2. Build RFC 3161 TimeStampRequest
        TimeStampRequestBuilder requestBuilder = new TimeStampRequestBuilder();
        requestBuilder.setCertReq(true); // Request TSA certificate in response
        
        ASN1ObjectIdentifier hashAlgOid = NISTObjectIdentifiers.id_sha256;
        TimeStampRequest request = requestBuilder.build(hashAlgOid, hash);
        byte[] requestBytes = request.getEncoded();

        // 3. Send HTTP POST request to TSA Server
        URL url = new URL(tsaUrl);
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        conn.setDoOutput(true);
        conn.setRequestMethod("POST");
        conn.setRequestProperty("Content-Type", "application/timestamp-query");

        try (OutputStream os = conn.getOutputStream()) {
            os.write(requestBytes);
            os.flush();
        }

        if (conn.getResponseCode() != HttpURLConnection.HTTP_OK) {
            throw new IOException("TSA Server responded with HTTP " + conn.getResponseCode());
        }

        // 4. Save response (.tsr) to disk
        try (InputStream is = conn.getInputStream();
             FileOutputStream fos = new FileOutputStream(outputTsrPath)) {
            byte[] buffer = new byte[4096];
            int bytesRead;
            while ((bytesRead = is.read(buffer)) != -1) {
                fos.write(buffer, 0, bytesRead);
            }
        }

        System.out.println("[SUCCESS] Timestamp saved to: " + outputTsrPath);
    }
}

Step 2: Verify the Timestamp Token (.tsr)

Parse the saved .tsr response using TimeStampResponse and validate the token integrity against the original file’s SHA-256 hash.

import org.bouncycastle.tsp.*;
import java.io.File;
import java.nio.file.Files;
import java.security.MessageDigest;
import java.util.Date;

public class JavaTimestampVerifier {

    public static boolean verifyTimestamp(String filePath, String tsrPath) throws Exception {
        // 1. Calculate SHA-256 hash of original file
        byte[] fileBytes = Files.readAllBytes(new File(filePath).toPath());
        MessageDigest digest = MessageDigest.getInstance("SHA-256");
        byte[] hash = digest.digest(fileBytes);

        // 2. Load TSR response
        byte[] tsrBytes = Files.readAllBytes(new File(tsrPath).toPath());
        TimeStampResponse response = new TimeStampResponse(tsrBytes);

        // Validate response status
        response.validate(null);
        TimeStampToken token = response.getTimeStampToken();

        if (token == null) {
            System.err.println("[ERROR] No timestamp token found in response.");
            return false;
        }

        // 3. Verify message imprint (hash matching)
        TimeStampTokenInfo info = token.getTimeStampInfo();
        byte[] tokenHash = info.getMessageImprintDigest();

        if (MessageDigest.isEqual(hash, tokenHash)) {
            Date signingTime = info.getGenTime();
            System.out.println("[SUCCESS] Timestamp signature is valid! Time (UTC): " + signingTime);
            return true;
        } else {
            System.err.println("[ERROR] File hash does not match timestamp imprint.");
            return false;
        }
    }
}

Execution Example

Call the request and verification methods in your Java application main loop:

public class Main {
    public static void main(String[] args) {
        try {
            String fileToSign = "document.pdf";
            String tsrOutput = "document.pdf.tsr";
            String tsaUrl = "https://tsaexample.com/myserver.aspx";

            // 1. Request Timestamp
            JavaTimestampClient.requestTimestamp(fileToSign, tsrOutput, tsaUrl);

            // 2. Verify Timestamp
            boolean isValid = JavaTimestampVerifier.verifyTimestamp(fileToSign, tsrOutput);
            System.out.println("Verification Result: " + isValid);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
Security Tip: Always ensure requestBuilder.setCertReq(true) is set so the TSA server returns its signing certificate embedded within the TimeStampToken for offline verification.