May 4, 2026 · Security Guide

E-Signature API Security: SHA-256, TLS, and Audit Trails Explained

Your compliance team wants to know: how does an e-signature API keep documents tamper-proof? Here's the security stack — from transport encryption to cryptographic signing certificates.

Michael Beckett
Michael Beckett

Founder, Signbee

TL;DR

E-signatures are more secure than wet-ink. Three layers protect your documents: TLS encryption in transit, SHA-256 hashing for tamper detection, and audit trails for non-repudiation. Learn how each works and what to look for when choosing an API. See how Signbee's SHA-256 certificate works.

The Cryptographic Foundations of Electronic Signatures

When compliance teams evaluate modern electronic signature systems, their questions often center on one primary concern: How do we know the document we see today is identical to the document that was signed? Wet-ink signatures on paper offer zero mathematical protection against alteration; pages can be swapped, clauses modified, and handwriting forged. Cryptographic signing certificates replace the subjective verification of a handwritten mark with hard mathematical guarantees.

An e-signature API achieves this by treating the entire document payload as an immutable stream of bytes. Security is maintained through three distinct, interdependent layers: transport security (to protect the data in transit), document integrity (to prove the payload has not been modified), and identity linkage/non-repudiation (to legally tie the signature event to a specific individual).

The Three Layers of E-Signature Security

Layer 1: Transport Encryption (TLS 1.3)

All API communication, webhook transmissions, and user interactions take place exclusively over HTTPS, using Transport Layer Security (TLS 1.3). TLS 1.3 simplifies the handshake process, reducing latency while eliminating deprecated, vulnerable cipher suites (such as RC4 and 3DES) and enforcing Perfect Forward Secrecy (PFS). This ensures that even if a private key is compromised in the future, past sessions remain secure and encrypted.

Layer 2: Document Integrity (SHA-256)

When a document is executed, the API generates a SHA-256 hash — a unique 64-character hexadecimal fingerprint representing the document contents. If any actor alters a single byte, comma, or metadata tag after the signature is generated, the recalculated hash will not match. This hash mismatch is flagged automatically by PDF readers, rendering the signature invalid. See our deep-dive on how SHA-256 signing certificates work.

Layer 3: Audit Trail (Non-Repudiation)

Every signing event is accompanied by an audit trail that logs detailed, verifiable metadata: email and SMS verification tokens, precise UTC timestamps, originating IP addresses, and user-agent strings. The audit trail itself is cryptographically linked to the signed document, preventing the signer from deniably claiming they did not execute the document (non-repudiation).

Under the Hood: PDF Byte-Level Integrity Sealing

Integrating a cryptographic signature directly into a PDF document presents a structural chicken-and-egg problem. To sign a file, you must compute a cryptographic hash of its contents and encrypt that hash. However, the resulting signature block (typically formatted as a PKCS#7 or Cryptographic Message Syntax envelope) must be written inside the PDF file itself. Writing the signature into the file changes the file size and byte values, which would naturally invalidate the computed signature.

The PDF specification (specifically ISO 32000) resolves this dilemma using Byte Range Exclusion Zones. When a PDF is prepared for signature, a Signature Dictionary (a /Sig object) is allocated within the PDF body. Within this dictionary, a key named /Contents is created. The PDF engine reserves a fixed-size block of empty hexadecimal space (indicated by angle brackets < and > filled with zero bytes) to hold the eventual binary signature payload.

The engine then calculates the signature hash over the remaining parts of the document, completely skipping the allocated /Contents block. The offsets of the hashed regions are explicitly saved as an array of integers under the /ByteRange key:

% PDF Signature Object Definition
5 0 obj
<<
  /Type /Sig
  /Filter /Adobe.PPKLite
  /SubFilter /adbe.pkcs7.detached
  /Contents <0000000000000000...[padded zero bytes]...>
  /ByteRange [0 24500 32700 4800]
>>
endobj

The /ByteRange array contains pairs of integers representing the offset and length of each segment to be hashed:

  • The first pair 0 24500 tells the validator to read the file from byte offset 0 up to byte 24,500. This covers the entire PDF preceding the signature value.
  • The second pair 32700 4800 indicates the hash resumes at byte offset 32,700 and continues for 4,800 bytes, encompassing the cross-reference tables, structural updates, and trailer until the absolute end of the file.
  • The interval between 24,500 and 32,700 (exactly 8,200 bytes) represents the Exclusion Zone. This zone houses the DER-encoded signature envelope and is ignored during document hashing.

ASCII DIAGRAM: PDF BYTE RANGE EXCLUSION ZONE

+-------------------------------------------------------------+
|  PDF Document Payload (Header, Catalog, Pages, Fonts)       | <-- Hashed [Offset: 0, Len: 24500]
|  ...                                                        |
|  /Type /Sig /Filter /Adobe.PPKLite                          |
|  /Contents <                                                |
+=============================================================+
|  EXCLUSION ZONE (Padded space inside /Contents)            | <-- Excluded from Hashing
|  [DER-encoded PKCS#7 / CMS Cryptographic Signature block]    |
+=============================================================+
|  >                                                          |
+-------------------------------------------------------------+
|  PDF Structural Metadata (XREF Table, Catalog, Trailer)      | <-- Hashed [Offset: 32700, Len: 4800]
|  %%EOF                                                       |
+-------------------------------------------------------------+

Incremental Updates and the Multi-Signer Chain

Real-world contract management requires documents to be signed by multiple parties sequentially. In standard documents, writing new signatures would modify the file, invalidating earlier signatures. The PDF specification overcomes this via Incremental Updates.

Instead of regenerating or rewriting the entire document, each subsequent signature is appended to the end of the existing file without altering a single byte of the previously signed revisions. This appends a new signature dictionary, field values, and trailer to the file, and registers a new /ByteRange array.

For a second signer, the new /ByteRange includes the entirety of Revision 1 (including its signature block and trailer) plus the new revisions up to the second signature dictionary's /Contents key:

ASCII DIAGRAM: MULTI-SIGNER INCREMENTAL UPDATES

+-------------------------------------------------------------+
|  REVISION 1 (Base PDF + Signer 1 Data)                      |
|  - Contains Sig 1 exclusion zone (<Hex Block 1>)            | <-- Hashed for Sig 2
|  - Original %%EOF                                           |
+-------------------------------------------------------------+
|  REVISION 2 INCREMENTAL UPDATE                              |
|  - Form field modifications, visual signatures              |
|  - Sig 2 Dictionary Object (/Type /Sig)                      |
|  - /Contents <                                              |
+=============================================================+
|  EXCLUSION ZONE 2 (Sig 2 Hex PKCS#7 Block)                  | <-- Excluded from Sig 2
+=============================================================+
|  >                                                          |
+-------------------------------------------------------------+
|  XREF 2 & Trailer 2 for Revision 2                          | <-- Hashed for Sig 2
|  %%EOF (Revision 2 EOF)                                     |
+-------------------------------------------------------------+

This layout ensures that modifying any part of the base document invalidates both signatures. If a tampering attempt occurs in the incremental updates, the second signature fails validation while the first signature remains valid. This structural integrity chain allows PDF readers to roll back to older revisions and inspect what the document looked like when the first party signed it.

Security Comparison by Provider

Not all APIs implement security at the same standard. Some write signatures as simple visual annotations and log metadata to a secondary database, leaving the document itself unprotected from outside alteration. A secure API embeds the cryptographic verification payload directly into the PDF.

ProviderTLSDoc HashingAudit TrailCertificate Type
SignbeeTLS 1.3SHA-256Full (IP, UA, OTP)Embedded PDF Certificate (LTV)
DocuSignTLS 1.2+SHA-256ComprehensiveCoC Attached Page
HelloSignTLS 1.2+SHA-256StandardDatabase Log Only
DocusealSelf-configuredSHA-256Basic loggingSelf-Signed / External HSM

How to Verify a PDF Signature Programmatically

Developers do not need to rely on external web APIs to verify a signed document's integrity. By reading the `/ByteRange` key directly from the PDF file, you can slice the document content buffer, compute its SHA-256 hash, and check it against the signature block. Below are fully functional standalone scripts in Node.js and Python that perform this verification on a local PDF file path.

Node.js Signature Verification Script

The following Node.js script uses only the standard, built-in fs and crypto modules. It parses the file, matches the latest signature byte range, extracts the signed regions, and computes the SHA-256 hash.

// verify-pdf-signature.js
const fs = require('fs');
const crypto = require('crypto');

function verifyPdfSignature(pdfPath) {
  console.log(`Reading PDF: ${pdfPath}...\n`);
  const pdfBuffer = fs.readFileSync(pdfPath);
  
  // Convert buffer to latin1 encoding to allow safe regex operations over binary bytes
  const pdfString = pdfBuffer.toString('latin1');
  
  // Regular expression to locate the /ByteRange values in the signature dictionary
  const byteRangeRegex = /\/ByteRange\s*\[\s*(\d+(?:\s+\d+)+)\s*\]/g;
  let match;
  let lastMatch = null;
  
  // Iterate to capture the last matching /ByteRange, which corresponds to the latest signature
  while ((match = byteRangeRegex.exec(pdfString)) !== null) {
    lastMatch = match;
  }
  
  if (!lastMatch) {
    throw new Error('Verification failed: No /ByteRange signature dictionary found in this PDF.');
  }
  
  // Split the byte range coordinates and parse them to numbers
  const byteRanges = lastMatch[1].split(/\s+/).map(Number);
  console.log('Parsed /ByteRange offsets:', byteRanges);
  
  if (byteRanges.length % 2 !== 0) {
    throw new Error('Verification failed: Invalid byte range array length.');
  }
  
  // Read and concatenate the exact byte slices specified in the ranges
  const chunks = [];
  for (let i = 0; i < byteRanges.length; i += 2) {
    const offset = byteRanges[i];
    const length = byteRanges[i + 1];
    console.log(` -> Slicing bytes: Offset ${offset} to ${offset + length}`);
    chunks.push(pdfBuffer.subarray(offset, offset + length));
  }
  
  // Join the slices to form the original signed document payload
  const signedData = Buffer.concat(chunks);
  
  // Calculate the SHA-256 hash of the signed content
  const hash = crypto.createHash('sha256').update(signedData).digest('hex');
  console.log(`\n✔ Recalculated SHA-256 Hash: ${hash}`);
  
  // Extract the signature block (exclusion zone) stored between range 1 end and range 2 start
  const exclusionStart = byteRanges[0] + byteRanges[1];
  const exclusionEnd = byteRanges[2];
  const exclusionZone = pdfBuffer.subarray(exclusionStart, exclusionEnd);
  
  // Clean raw hex signature by removing brackets and whitespace
  const hexSignature = exclusionZone.toString('latin1').replace(/[<>\s]/g, '');
  console.log(`✔ Extracted Cryptographic Signature Hex (First 32 chars): ${hexSignature.substring(0, 32)}...`);
  console.log(`✔ Hex Length: ${hexSignature.length} characters (${hexSignature.length / 2} bytes)`);
  
  return { hash, hexSignature };
}

// Read the PDF path from command-line arguments
const filePath = process.argv[2];
if (!filePath) {
  console.log('Usage: node verify-pdf-signature.js <path_to_pdf>');
  process.exit(1);
}

try {
  verifyPdfSignature(filePath);
} catch (err) {
  console.error('❌ Error verifying PDF:', err.message);
  process.exit(1);
}

Python Signature Verification Script

The following Python script uses standard library packages: sys, re, and hashlib. It processes the PDF as a binary file to ensure byte precision, parses the signature coordinates, and calculates the SHA-256 checksum.

# verify_pdf_signature.py
import sys
import re
import hashlib

def verify_pdf_signature(pdf_path):
    print(f"Reading PDF file: {pdf_path}...\n")
    with open(pdf_path, 'rb') as f:
        pdf_bytes = f.read()

    # Decode using latin1 to keep binary data structure safe in regular expression searching
    pdf_text = pdf_bytes.decode('latin1')
    
    # Locate all /ByteRange sequences in the file
    byte_range_pattern = r'\/ByteRange\s*\[\s*(\d+(?:\s+\d+)+)\s*\]'
    matches = list(re.finditer(byte_range_pattern, pdf_text))
    
    if not matches:
        print("❌ Error: No /ByteRange signature dictionary found in PDF.")
        sys.exit(1)
        
    # Process the last match in the file for the latest document revision
    last_match = matches[-1]
    range_values = [int(x) for x in last_match.group(1).split()]
    print(f"Parsed /ByteRange offsets: {range_values}")
    
    if len(range_values) % 2 != 0:
        print("❌ Error: Byte range must contain an even number of entries.")
        sys.exit(1)

    # Slice the buffer segments matching the byte ranges
    signed_payload = bytearray()
    for i in range(0, len(range_values), 2):
        start = range_values[i]
        length = range_values[i + 1]
        print(f" -> Slicing bytes: Offset {start} to {start + length}")
        signed_payload.extend(pdf_bytes[start : start + length])

    # Hash the concatenated slices with SHA-256
    sha256_hash = hashlib.sha256(signed_payload).hexdigest()
    print(f"\n✔ Recalculated SHA-256 Hash: {sha256_hash}")

    # Extract the hex-encoded signature block from the exclusion zone
    exclusion_start = range_values[0] + range_values[1]
    exclusion_end = range_values[2]
    exclusion_zone = pdf_bytes[exclusion_start:exclusion_end]

    # Decode and strip angle brackets and whitespace delimiters
    exclusion_str = exclusion_zone.decode('latin1')
    hex_signature = re.sub(r'[<>\s]', '', exclusion_str)
    
    print(f"✔ Extracted Cryptographic Signature Hex (First 32 chars): {hex_signature[:32]}...")
    print(f"✔ Hex Length: {len(hex_signature)} characters ({len(hex_signature) // 2} bytes)")
    
    return sha256_hash, hex_signature

if __name__ == '__main__':
    if len(sys.argv) < 2:
        print("Usage: python verify_pdf_signature.py <path_to_pdf>")
        sys.exit(1)
        
    verify_pdf_signature(sys.argv[1])

FAQ: Deep Questions in Cryptographic E-Signatures

"Can someone forge the signature?"

No. E-signatures aren't images of handwriting — they're cryptographic events tied to a specific person, time, IP address, and document state. Forging one would require access to the signer's email and device.

"What if the document is altered after signing?"

The SHA-256 hash catches this. Any change — even a single space — produces a completely different hash. The signing certificate stores the original hash, making tampering detectable and provable.

"Will this hold up in court?"

Yes. E-signatures are legally binding under ESIGN (US), eIDAS (EU), and ECA (UK). Courts have consistently ruled that e-signatures with audit trails are more reliable than wet-ink.

"How collision-resistant is the SHA-256 hashing algorithm used in electronic signatures?"

SHA-256 produces a unique 256-bit (32-byte) digest. The total number of possible hash combinations is 2^256, which is approximately 1.157 x 10^77. Finding two separate PDF files that yield the exact same SHA-256 hash (a hash collision) is computationally impossible with current and foreseeable technology. Even if a massive distributed supercomputing network evaluated billions of hashes per second, finding a collision would require more time than the age of the universe. This guarantee ensures that a signed document hash is a mathematically unique identifier, making silent tampering impossible.

"Can a signed PDF document be verified offline without contacting the e-signature API?"

Yes, standard-compliant PDF signatures are fully self-verifying. The PKCS#7 or CMS signature block contained in the PDF's exclusion zone embeds the signer's public key, the cryptographic signature of the document hash, and the X.509 certificate chain. When an offline validator (such as a PDF viewer or a local backend process) verifies the document, it extracts the `/ByteRange` offsets, computes the document's SHA-256 hash over those segments, decrypts the signature using the embedded public key, and verifies that the hashes match. It then validates the certificate chain against system-level root certificates, all without contacting external servers.

"What happens to the validity of an e-signature when the signing certificate expires or is revoked?"

To prevent signatures from becoming invalid when certificates expire or are revoked, e-signature engines use Long-Term Validation (LTV). LTV embeds Online Certificate Status Protocol (OCSP) responses and Certificate Revocation Lists (CRLs) directly into the PDF's Document Security Store (DSS) at the time of signing. When combined with a trusted RFC 3161 cryptographic timestamp, this provides immutable proof that the certificate was valid and trusted at the exact moment the signature was written. Even if the certificate expires years later, the PDF validator can confirm its historical validity offline.

SHA-256 certified, tamper-proof — 5 free docs/month.

Last updated: May 29, 2026 · Michael Beckett is the founder of Signbee and B2bee Ltd.

Related resources