What do Signed Properties Look Like When Hashing?

It turns out the spaces matter even in the version you are submitting to ZATCA’s servers, not just when hashing.

ZATCA’s servers are hashing the SignedProperties you submit via the API as-is, they do not transform them to the spacing format that is required before hashing. Thus if you do not use the same spaces you will always get a hashing error returned by the server, even if your hash is actually correct.

In our call I suggested one of 2 changes to make spaces not matter:

  1. As the original documentation used to say, canonicalize before hashing. This would require ZATCA to accept both the current and canonicalized format and may result in significant overhead and issues.
  2. On ZATCA’s serverside, simply transform any signed properties block that an API client sends to ZATCA to ensure it has the same spaces. This matches the way we hash invoices, both the client and server format the content in the same way (canonicalization) and then the hash is generated and compared. The problem right now is that ZATCA’s server does not format the SignedProperties and expects the client to do it, but this behavior is not documented. Following this suggestion should retain backwards-compatability and make spaces not matter.

tl;dr version
For now just ensure any step you are using the SignedProperties block in 100% match ZATCA’s samples in terms of whitespace.

@obahareth-mrsool Are youe solved this issue

Finally, I resolved this error by having 36 spaces in the first line and 4 spaces after that.

    $signaturePart = '<xades:SignedProperties xmlns:xades="http://uri.etsi.org/01903/v1.3.2#" Id="xadesSignedProperties">' . PHP_EOL .

xades:SignedSignatureProperties’ . PHP_EOL .
xades:SigningTimeSIGNING_TIME_VALUE</xades:SigningTime>’ . PHP_EOL .
xades:SigningCertificate’ . PHP_EOL .
xades:Cert’ . PHP_EOL .
xades:CertDigest’ . PHP_EOL .
’ <ds:DigestMethod xmlns:ds=“XML-Signature Syntax and Processing” Algorithm=“XML Encryption Syntax and Processing”/>’ . PHP_EOL .
’ <ds:DigestValue xmlns:ds=“XML-Signature Syntax and Processing”>HASH_DIGEST_VALUE</ds:DigestValue>’ . PHP_EOL .
’ </xades:CertDigest>’ . PHP_EOL .
xades:IssuerSerial’ . PHP_EOL .
’ <ds:X509IssuerName xmlns:ds=“XML-Signature Syntax and Processing”>ISSUER_NAME</ds:X509IssuerName>’ . PHP_EOL .
’ <ds:X509SerialNumber xmlns:ds=“XML-Signature Syntax and Processing”>SERIAL_NUMBER</ds:X509SerialNumber>’ . PHP_EOL .
’ </xades:IssuerSerial>’ . PHP_EOL .
’ </xades:Cert>’ . PHP_EOL .
’ </xades:SigningCertificate>’ . PHP_EOL .
’ </xades:SignedSignatureProperties>’ . PHP_EOL .
’ </xades:SignedProperties>';

1 Like

@obahareth-mrsool @MAl-tamimi
I’m encountering an issue where the invoice generated through the SDK produces the correct signing invoice hash. However, when I attempt to manually generate the same signing hash, I’m unable to match it. Can someone please help me in this regard.

Dear @hamza-id

Thanks for reaching out,

Please find the attached (SigningProcessUpdated.pdf) for signing process manually ensure to follow all the steps.

Uploading: SigningProcessUpdated.pdf…

If you faced any issues do not hesitate to reach out our support team via below mail mentioning all the detailed concerns to provide comprehensive support as usual.

SP mail: sp_support@zatca.gov.sa

Thanks,
Ibrahem Daoud.

@idaoud The attached file cannot be downloaded. Seems your attachment did not go well.
Thanks

Dear @creativebusiness

Thanks for notifying,

Please find the attached:
SigningProcessUpdated.pdf (927.7 KB)

Thanks,
Ibrahem Daoud.

What do you mean in first line ?

I resolved the issue by doing the following:

  • The first line of the $signaturePart (the opening tag) had to start with exactly 36 spaces.

  • Every subsequent line had to be indented with exactly 4 spaces.

This fixed the hash mismatch and allowed the XML to produce the expected digest during signature generation.When generating hashes for digitally signed XML, every space and line break matters. XML canonicalization treats whitespace as part of the content, so make sure your formatting is 100% identical to the expected structure, both in content and indentation.

$signaturePart = str_repeat(’ ‘, 36) . ‘<xades:SignedProperties …>’ . PHP_EOL .
<xades:SignedSignatureProperties>’ . PHP_EOL .
<xades:SigningTime>SIGNING_TIME_VALUE</xades:SigningTime>’ . PHP_EOL .
// continue with 4-space indentation per line…
’ </xades:SignedSignatureProperties>’ . PHP_EOL .
‘</xades:SignedProperties>’;

1 Like

Thank you.

When I save my xml, i was adding 4 spaces ( just to make it clear for human )
and then i saw them putting the UBL:Extensions on the same line as the <Invoice…

Again thanks.

2 Likes

Dear @hamza-id ,

I am also encountering the same issue. In my implementation, the SignedProperties tag is included within the XML. I would like to confirm whether the SignedProperties element embedded in the XML and the one used for generating the hash are identical. Could you please share the exact SignedProperties XML fragment for reference?

is canonicalisation required before generating Signed Properties Hash?

                                <xades:SignedProperties Id="xadesSignedProperties">
                                    <xades:SignedSignatureProperties>
                                        <xades:SigningTime>SET_SIGN_TIMESTAMP</xades:SigningTime>
                                        <xades:SigningCertificate>
                                            <xades:Cert>
                                                <xades:CertDigest>
                                                    <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256" />
                                                    <ds:DigestValue>SET_CERTIFICATE_HASH</ds:DigestValue>
                                                </xades:CertDigest>
                                                <xades:IssuerSerial>
                                                    <ds:X509IssuerName>SET_CERTIFICATE_ISSUER</ds:X509IssuerName>
                                                    <ds:X509SerialNumber>SET_CERTIFICATE_SERIAL_NUMBER</ds:X509SerialNumber>
                                                </xades:IssuerSerial>
                                            </xades:Cert>
                                        </xades:SigningCertificate>
                                    </xades:SignedSignatureProperties>
                                </xades:SignedProperties>

Above is a snippet of signed properties.

Yes, canonicalisation is required; otherwise, your generated hash will not be matched.

Dear @hamza-id ,

My SignedProperties tag is exactly same.

As you mentioned above ,
The first line of the $signaturePart (the opening tag) had to start with exactly 36 spaces.
Every subsequent line had to be indented with exactly 4 spaces.

i follow the same but still i am getting validation error.

{
“type”: “ERROR”,
“code”: “signed-properties-hashing”,
“category”: “CERTIFICATE_ERRORS”,
“message”: “Invalid signed properties hashing, SignedProperties with id=‘xadesSignedProperties’”,
“status”: “ERROR”
}

What is your final xml looks like ?

on the final xaml output, count the spaces from <xades:SignedProperties
it should be 32 spaces.

before <xades:SignedProperties tag i have 36 spaces in my final xml and if i create SignedProperties tag having 36 spaces will it work ? or its mandatory to have 32 spaces in both.

Many developers face the SignedProperties hashing error even when their XML structure looks perfectly correct.

The culprit is often not malformed XML, but rather inconsistent line endings across different operating systems.

  • Windows saves newlines as \r\n (carriage return + line feed).

  • Linux/macOS saves newlines as just \n (line feed).

When the SignedProperties block is generated on Windows, every line break is encoded as \r\n (two bytes instead of one). These extra \r characters change the byte sequence that gets hashed.

As a result, the computed digest on Windows doesn’t match the expected digest (usually calculated in an \n environment). This mismatch leads to signature validation errors—even though the XML looks identical in an editor.

In short, the hashing issue often comes down to newline inconsistencies, not XML formatting.

My friend explained this issue beautifully on StackOverflow.
Do check out the answer here: https://stackoverflow.com/questions/79737195/getting-invalid-signed-properties-hashing-error-for-signedproperties-in-zatca-e/79737224#79737224

1 Like

I have resolved this issue by adding 32 spaces before <xades:SignedProperties tag in both my final xml and the SignedProperties xml fragment before hashing.

thank you all