Explanation

The hash and the signature value

This is the chapter to read if only one thing in this guide gets read. Three sentences first, then the detail.

  1. The hash you send to signHash is not the hash of your PDF. It is the hash of the CMS signed attributes, and the hash of the PDF is one of those attributes. In ETSI terms it is the DTBS/R, the data-to-be-signed representation.
  2. What comes back is a signature value over that hash, in the algorithm you asked for in signAlgo. It is not a CMS, not a PKCS#7 blob, not a PDF.
  3. That value only becomes a signature when you put it in a CMS SignedData and put that in the document. Every PAdES library has an "external signing" path for exactly this.

The two hashes

A PAdES signature is a CMS SignedData in the PDF's /Contents, and CMS signs its signed attributes, not the content directly. So there are two digests in play. Step through them:

If you hash the PDF and send that, Cleverbase will sign it without complaint, because Cleverbase cannot tell the difference. Your library will then build a CMS whose signature does not verify, and the first validator you try will report an invalid signature over a perfectly good-looking document.

What the signature value is

The signatures[0] you receive is the raw output of the signature primitive over your hash, standard base64 encoded. With the values Cleverbase requires today, hashAlgo SHA-256 and signAlgo 1.2.840.113549.1.1.1 (rsaEncryption), that is a PKCS#1 v1.5 signature over a SHA-256 digest, as many bytes as the key is long: 256 bytes for RSA-2048, which key.len in credentials/info tells you.

The trap is what to call it afterwards. The API names the key algorithm; the CMS names the combination:

WhereValue
signAlgo in your signHash request1.2.840.113549.1.1.1 rsaEncryption
hashAlgo in your signHash request2.16.840.1.101.3.4.2.1 SHA-256
signatureAlgorithm in the CMS you build1.2.840.113549.1.1.11 sha256WithRSAEncryption
DSSSignatureAlgorithm.RSA_SHA256
pyHankoan RSA signing certificate with digest sha256

Put rsaEncryption in the CMS because that is what you sent, and a validator will reject the signature it cannot match. Send something else on the API side and signHash fails. Neither error tells you which side is wrong, so pin both from one place in your code.

What it is not

  • Not a CMS or PKCS#7. There is no SignedData, no certificates, no attributes in the response. You build those; you already have the certificates from credentials/info.
  • Not a signed PDF. Cleverbase never had the PDF.
  • Not a signature over the PDF bytes. It is a signature over the signed attributes. A validator that checks the signature against the document digest directly will say it is wrong; that is the validator misunderstanding CMS, not a wrong signature.
  • Not usable twice. It fits exactly one set of signed attributes. Change the signing time or the certificate and you need a new authorization and a new signature.

Sizes to reserve

Your library reserves space in /Contents before it knows the CMS. For a B-B signature with a three-certificate chain, RSA-2048, and no timestamp, the CMS is typically 3 to 5 kB; with a B-T timestamp token inside it, 8 to 10 kB. Reserve well above that: 32768 bytes is a safe choice and the number is effectively free, because too little fails at completion, after the signer has already authorized, while too much only adds file size. Going to B-LT or B-LTA later needs nothing more in this field, since that material lands in its own revisions.

Where the time goes

PAdES forbids the CMS signingTime attribute; the claimed signing time lives in the signature dictionary's /M entry and is set when you prepare. It is also inside the signed byte range, so the moment you write /M, it is part of what the hash covers. Fix it in step 4 and reuse the same value in step 7. Our implementation returns a signingDate from prepare and refuses to complete without it.

Checking your understanding before you write code

Take a PDF you already signed with any tool, open it in Cleverbase's PDF analysis tool (or run openssl cms -cmsout -print on the extracted /Contents) and find the messageDigest attribute and the signature field of the SignerInfo. The value you will get from signHash corresponds to that signature field, and the hash you must send is SHA-256 over the DER of the signedAttrs set. If those two land in the right places, the rest is plumbing.