How to Timestamp Files in C# .NET

Qualifed Timestamping in C# .NET (Native Implementation)

Learn how to request and verify qualified RFC 3161 timestamps in .NET 6+ / .NET 8 / .NET 9 using the native Rfc3161TimestampRequest class without third-party dependencies like BouncyCastle.

Prerequisites & Environment:

  • .NET 5 / .NET 6 / .NET 8 / .NET 9 SDK installed.
  • Namespace requirement: System.Security.Cryptography.Pkcs (built-in .NET assembly).
  • TSA Certificate Chain (for offline verification): Download TSAChain.zip.

Step 1: Request a Timestamp Token (.tsr)

Compute the SHA-256 hash of your file, build an Rfc3161TimestampRequest, and send it via HTTP POST with the application/timestamp-query content header.

using System;
using System.IO;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security.Cryptography;
using System.Security.Cryptography.Pkcs;
using System.Threading.Tasks;

public class TimestampClient
{
    public static async Task RequestTimestampAsync(string filePath, string outputTsrPath, string tsaUrl)
    {
        // 1. Compute SHA-256 hash of the target file
        byte[] fileBytes = await File.ReadAllBytesAsync(filePath);
        byte[] hash = SHA256.HashData(fileBytes);

        // 2. Build RFC 3161 timestamp request
        Rfc3161TimestampRequest request = Rfc3161TimestampRequest.CreateFromHash(
            hash,
            HashAlgorithmName.SHA256,
            requestSignerCertificates: true
        );

        // 3. Send HTTP POST request to the TSA server
        using HttpClient client = new HttpClient();
        ByteArrayContent content = new ByteArrayContent(request.Encode());
        content.Headers.ContentType = new MediaTypeHeaderValue("application/timestamp-query");

        HttpResponseMessage response = await client.PostAsync(tsaUrl, content);
        response.EnsureSuccessStatusCode();

        // 4. Save timestamp response token (.tsr)
        byte[] tsrBytes = await response.Content.ReadAsByteArrayAsync();
        await File.WriteAllBytesAsync(outputTsrPath, tsrBytes);

        Console.WriteLine($"[SUCCESS] Timestamp saved to: {outputTsrPath}");
    }
}

Step 2: Verify the Timestamp Token (.tsr) in C#

Validate the received .tsr response against the original file hash and ensure the TSA signing certificate matches your trusted certificate authority chain.

using System;
using System.IO;
using System.Security.Cryptography;
using System.Security.Cryptography.Pkcs;

public class TimestampVerifier
{
    public static bool VerifyTimestamp(string filePath, string tsrPath)
    {
        byte[] fileBytes = File.ReadAllBytes(filePath);
        byte[] hash = SHA256.HashData(fileBytes);
        byte[] tsrBytes = File.ReadAllBytes(tsrPath);

        // Parse RFC 3161 Response
        Rfc3161TimestampToken token = Rfc3161TimestampToken.ReadIfTimestampToken(
            tsrBytes, 
            out _, 
            out _
        );

        if (token == null)
        {
            Console.WriteLine("[ERROR] Invalid timestamp token format.");
            return false;
        }

        // Verify hash integrity and timestamp signature
        bool isHashValid = token.VerifyHash(hash, HashAlgorithmName.SHA256, out _);

        if (isHashValid)
        {
            DateTimeOffset timestampTime = token.AsSignedCms().SignerInfos[0].SigningTime;
            Console.WriteLine($"[SUCCESS] Timestamp is valid! Signed at (UTC): {timestampTime.UtcDateTime}");
            return true;
        }

        Console.WriteLine("[ERROR] Hash mismatch or invalid signature.");
        return false;
    }
}

Complete Program Execution

Run the full workflow directly in your .NET console application:

string fileToSign = "document.pdf";
string tsrOutput = "document.pdf.tsr";
string tsaServerUrl = "https://tsaexample.com/myserver.aspx";

// 1. Request Timestamp
await TimestampClient.RequestTimestampAsync(fileToSign, tsrOutput, tsaServerUrl);

// 2. Verify Timestamp
bool isValid = TimestampVerifier.VerifyTimestamp(fileToSign, tsrOutput);
Console.WriteLine($"Verification Result: {isValid}");
Why use native .NET over BouncyCastle? Starting with .NET 5, System.Security.Cryptography.Pkcs provides built-in support for RFC 3161 timestamping, avoiding third-party dependency vulnerabilities, reducing library bloat, and offering better memory efficiency via Span<T> and ReadOnlySpan<T>.