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.
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.