Yii2/php - Invalid signed properties hashing (xadesSignedProperties)

I know this issue has been pulled many times, but hear me out please.
As usual, the error is:

{\n "type": "ERROR",\n "code": "signed-properties-hashing",\n "category": "CERTIFICATE_ERRORS",\n "message": "Invalid signed properties hashing, SignedProperties with id='xadesSignedProperties'",\n "status": "ERROR"\n }\n ],\n "status": "ERROR"\n },\n "reportingStatus": "NOT_REPORTED"\n}

I was following official guidelines from here, as well as instructions from this post.

Here is what I am hashing:

<xades:SignedProperties xmlns:xades="http://uri.etsi.org/01903/v1.3.2#" Id="xadesSignedProperties"><xades:SignedSignatureProperties><xades:SigningTime>2025-01-24T16:23:43</xades:SigningTime><xades:SigningCertificate><xades:Cert><xades:CertDigest><ds:DigestMethod xmlns:ds="http://www.w3.org/2000/09/xmldsig#" Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"></ds:DigestMethod><ds:DigestValue xmlns:ds="http://www.w3.org/2000/09/xmldsig#">OWQ5ODc1NmQxZTljMzM0ODNiYTkwOTI1NjFhY2RlNTAwNzhjZGVkY2MyNWFjZTBkMTA4NDFjNTRiMGU4N2MzNg==</ds:DigestValue></xades:CertDigest><xades:IssuerSerial><ds:X509IssuerName xmlns:ds="http://www.w3.org/2000/09/xmldsig#">CN=PEZEINVOICESCA1-CA, DC=extgazt, DC=gov, DC=local</ds:X509IssuerName><ds:X509SerialNumber xmlns:ds="http://www.w3.org/2000/09/xmldsig#">2207773856276085586394728823149103726765030759</ds:X509SerialNumber></xades:IssuerSerial></xades:Cert></xades:SigningCertificate></xades:SignedSignatureProperties></xades:SignedProperties>

and here is the same tag in the invoice xml:

<xades:SignedProperties Id="xadesSignedProperties"><xades:SignedSignatureProperties><xades:SigningTime>2025-01-24T16:23:43</xades:SigningTime><xades:SigningCertificate><xades:Cert><xades:CertDigest><ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/><ds:DigestValue>OWQ5ODc1NmQxZTljMzM0ODNiYTkwOTI1NjFhY2RlNTAwNzhjZGVkY2MyNWFjZTBkMTA4NDFjNTRiMGU4N2MzNg==</ds:DigestValue></xades:CertDigest><xades:IssuerSerial><ds:X509IssuerName>CN=PEZEINVOICESCA1-CA, DC=extgazt, DC=gov, DC=local</ds:X509IssuerName><ds:X509SerialNumber>2207773856276085586394728823149103726765030759</ds:X509SerialNumber></xades:IssuerSerial></xades:Cert></xades:SigningCertificate></xades:SignedSignatureProperties></xades:SignedProperties>

Neat and clear, no spaces, no extra stuff…
However, the difference is in prefixes (namespaces) for hashed part, as instructed in the ZATCA guidelines.

The function that do hashing:

public function zatcaGenerateCertificateHash()
    {
        $certificate = Cfg::get('zatca_certificate');
        $hashedCertificate = hash('sha256', $certificate);
        $base64EncodedHash = base64_encode($hashedCertificate);
        return $base64EncodedHash;
    }

    public function zatcaSignedPropertiesHash($issueTime)
    {
        $signingTime = date('Y-m-d\TH:i:s', strtotime($issueTime)); // provided on function call
        $certificateHash = $this->zatcaGenerateCertificateHash(); // look above
        $issuerName = Cfg::get('zatca_issuer_name'); // extracted from the certificate
        $serialNumber = Cfg::get('zatca_serialnumber'); // extracted from the certificate
		
        // Construct the SignedProperties XML block
        $xml = 
        '<xades:SignedProperties xmlns:xades="http://uri.etsi.org/01903/v1.3.2#" Id="xadesSignedProperties"><xades:SignedSignatureProperties><xades:SigningTime>'.$signingTime.'</xades:SigningTime><xades:SigningCertificate><xades:Cert><xades:CertDigest><ds:DigestMethod xmlns:ds="http://www.w3.org/2000/09/xmldsig#" Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/><ds:DigestValue xmlns:ds="http://www.w3.org/2000/09/xmldsig#">'.$certificateHash.'</ds:DigestValue></xades:CertDigest><xades:IssuerSerial><ds:X509IssuerName xmlns:ds="http://www.w3.org/2000/09/xmldsig#">'.$issuerName.'</ds:X509IssuerName><ds:X509SerialNumber xmlns:ds="http://www.w3.org/2000/09/xmldsig#">'.$serialNumber.'</ds:X509SerialNumber></xades:IssuerSerial></xades:Cert></xades:SigningCertificate></xades:SignedSignatureProperties></xades:SignedProperties>';

        // Load XML into DOMDocument with UTF-8 encoding
        $dom = new \DOMDocument('1.0', 'UTF-8');
        $dom->loadXML($xml, LIBXML_NOBLANKS);
		
		// Apply canonicalization
        $canonicalizedXml = $dom->C14N(true, false); 

        // Hash the canonicalized XML using SHA-256 (raw binary output)
        $hashedPropertyTag = hash('sha256', $canonicalizedXml, true);
 
        // Encode the hash using Base64
        return base64_encode($hashedPropertyTag);
    }

What I may be doing wrong?

Thank you for your support.

Make sure you have 36 space before <xades:SignedSignatureProperties>

or you can see working PHP code Example from this github repository ZatcaPHP

Thank you for your reply.
I did try this, and did not solve my issue.

However, cannot believe that ZATCA overseen this! Why would they expect everyone to count spaces and create xml templates of that kind without mentioning that anywhere?!

If my both xmls are canonicalized (i.e.: xadesSignedProperties part, and full invoice part), the hashes should be the same, right?

Its not clear why am I getting that error:

Invalid signed properties hashing, SignedProperties with id=‘xadesSignedProperties’

What that even means??

Bro, has the issue been resolved so far?

Bro, has the issue been resolved so far?

I’m not sure, I didn’t find any issues with my code examples on github. Some people reported that my code runs fine on Sandbox and Simulation.

1 Like

Can you tell me how to generate the hash for the following XML?

<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>CN=SET_CERTIFICATE_ISSUER, DC=extgazt, DC=gov, DC=local</ds:X509IssuerName>
                    <ds:X509SerialNumber>SET_CERTIFICATE_SERIAL_NUMBER</ds:X509SerialNumber>
                </xades:IssuerSerial>
            </xades:Cert>
        </xades:SigningCertificate>
    </xades:SignedSignatureProperties>
</xades:SignedProperties>

Should the xmlns:ds="http://www.w3.org/2000/09/xmldsig#" be included when creating the hash, or should it be added after generating the hash?

Dear @creativebusiness

Thanks for reaching out & welcome to the community.

Please find the signing document attached, you can find the Signed Properties tag in slide 7.

SigningProcessUpdated.pdf (927.7 KB)

Kindly review it carefully, and if you faced any challenges, please do not hesitate to reach out to our support team via email.

SP email: sp_support@zatca.gov.sa

Thanks

@eCloud Thank you for sharing your code on GitHub.
It was very helpful, and it indeed pointed to some very important details that ZATCA did not mention anywhere.

Finally we managed to fix the issue, and here is what we struggled about for weeks!

Hope someone will find this useful:

When you do hashing for SignedProperties, IT MUST follow specific format and could not be changed in any way.

Also, the xml template must follow the same format.
The best way is to use Zatca SDK and sign any invoice, or use their provided sample, and build on it WITHOUT changing formatting of the ext:UBLExtension part, that whole part of xml must stay in same formatting.
it MUST stay as is, or you will never get SignedProperties hash correctly.

Note: xades:SignedProperties must contain namespace declarations (xmlns:) or prefixes, while Invoice XML template must NOT contain those namespaces.

Also, canonicalizeing xml (C14N) as mentioned in updated ZATCA guide, did NOT WORK in our case.

Hope this helped.
Good luck!

1 Like

@creativebusiness
Please take a look at this and guide me on where I am making a mistake.



In the first image, I have shown my template, which I am using to create tags.
In the second image, I am inserting data into those tags.
In the third image, I am applying SHA-256 and Base64 encoding to them.
Then, I am inserting those values into the XML using:

$content = str_replace("SET_SIGNED_PROPERTIES_HASH", $signed_properties_hash, $content);
$content = str_replace("SET_SIGNED_PROPERTIES_XML", $ubl_signature_signed_properties_xml_string, $content);

@obaidrehman
What exactly your SET_CERTIFICATE_ISSUER returns?
Why you have “CN=…” in your template?
your final line in xml should look like this:
ds:X509IssuerNameCN=PEZEINVOICESCA1-CA, DC=extgazt, DC=gov, DC=local</ds:X509IssuerName>

Can you explain the steps you took to fix the ‘Invalid signed properties hashing’ and ‘SignedProperties’ issues in detail? It would be helpful for us.