I am doing a project in Django (full Python) specifically a sales system. Now, I want to incorporate electronic invoicing to the system, focusing only on the electronic sales receipt for now. In my country, the regulatory entity requires that to send the invoice, it must be done in an XML file (with a specific format), then, it must be signed with the digital certificate of the company, packed in a .zip file and sent to the testing web service of the entity. But, I don't know how to do the signature using only Python. I was researching and I noticed that most of them choose to use languages like Java or C#, mainly. I also found the lxml and cryptography libraries, with which I could make the signature, but when I send it to the web service, I get the error: Error of the web service: The entered electronic document has been altered - Detail: Incorrect reference digest value I was trying to find out how to fix this error, I don't know if it has to do with the way I'm signing the XML document or the method I'm using. I share with you the code to give you an idea of what I used: from lxml import etree from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import padding from cryptography.hazmat.primitives.serialization import load_pem_private_key import base64 import os import re def sign_xml(xml_path, private_key_path, certificate_path, signed_xml_path): """Signs an XML document with a digital certificate.""" # Load the XML document tree = etree. parse(xml_path) root = tree. getroot() # Load the private key with open(private_key_path, "rb") as key_file: private_key = load_pem_private_key( key_file. read(), password=None, backend=default_backend() ) # Create the Signature element ns = { "cac": "urnasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2", "ds": "http://www.w3.org/2000/09/xmldsig#", "ext": "urnasis:names:specification:ubl:schema:xsd:CommonExtensionComponents-2", "sac": "urn:sunat.gob.pe:billdownload:2", "cbc":"urnasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2" } # Create the UBLExtensions element if it does not exist extensions = root.find("ext:UBLExtensions", ns) if extensions is None: extensions = etree.SubElement(root, "{%s}UBLExtensions" % ns['ext']) # Create the UBLExtension element extension = extensions.find("ext:UBLExtension", ns) if extension is None: extension = etree.SubElement(extensions, "{%s}UBLExtension" % ns['ext']) # Create the ExtensionContent element extension_content = extension.find("ext:ExtensionContent", ns) if extension_content is None: extension_content = etree.SubElement(extension, "{%s}ExtensionContent" % ns['ext']) # Clear any existing content in ExtensionContent extension_content.clear() signature = etree.SubElement(extension_content, "{%s}Signature" % ns['ds'], Id="SignatureSP") signed_info = etree.SubElement(signature, "{%s}SignedInfo" % ns['ds']) canonical_method = etree.SubElement(signed_info, "{%s}CanonicalizationMethod" % ns['ds'], Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315") signature_method = etree.SubElement(signed_info, "{%s}SignatureMethod" % ns['ds'], Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256") reference = etree.SubElement(signed_info, "{%s}Reference" % ns['ds'], URI="") transformations = etree.SubElement(reference, "{%s}Transforms" % ns['ds']) transformation = etree.SubElement(transformations, "{%s}Transform" % ns['ds'], Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature") digest_method = etree.SubElement(reference, "{%s}DigestMethod" % ns['ds'], Algorithm="http://www.w3.org/2001/04/xmlenc#sha256") # Calculate the DigestValue copy_tree = etree.fromstring(etree.tostring(tree)) reference_to_sign = copy_tree.find(".//ds:Reference", ns) if reference_to_sign is not None: reference_to_sign.getparent().remove(reference_to_sign) xml_serialized = etree.tostring(copy_tree, exclusive=1, inclusive_ns_prefixes=None) xml_serialized_without_spaces = re.sub(b'>\s*<', b'><', xml_serialized) print("Serialized XML (without spaces):", xml_serialized_without_spaces) digest = hashes.Hash(hashes.SHA256(), backend=default_backend()) digest.update(xml_serialized_without_spaces) digest_value = base64.b64encode(digest.finalize()).decode() print("DigestValue calculated:", digest_value) # Print the calculated digest value etree.SubElement(reference, "{%s}DigestValue" % ns['ds']).text = digest_value # Calculate the SignatureValue info_firmada_serializado = etree.tostring(signed_info, encoding='UTF-8', method='xml') signature = private_key.sign( info_firmada_serializado, padding.PKCS1v15(), hashes.SHA256() ) signature_value = base64.b64encode(signature).decode() etree.SubElement(signature, "{%s}SignatureValue" % ns['ds']).text = signature_value # Add the certificate information key_info = etree.SubElement(signature, "{%s}KeyInfo" % ns['ds']) x509_data = etree.SubElement(key_info, "{%s}X509Data" % ns['ds']) with open(certificate_path, "rb") as cert_file: certificate_content = cert_file.read() etree.SubElement(x509_data, "{%s}X509Certificate" % ns['ds']).text = base64.b64encode(certificate_content).decode() # Save the signed XML tree.write(signed_xml_path, encoding="UTF-8", xml_declaration=True, pretty_print=True) And this is the test XML document: <?xml version="1.0" encoding="UTF-8"?> <Invoice xmlns="urnasis:names:specification:ubl:schema:xsd:Invoice-2" xmlns:cac="urnasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2" xmlns:cbc="urnasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:ext="urnasis:names:specification:ubl:schema:xsd:CommonExtensionComponents-2"> <ext:UBLExtensions> <ext:UBLExtension> <ext:ExtensionContent /> </ext:UBLExtension> </ext:UBLExtensions> <cbc:UBLVersionID>2.1</cbc:UBLVersionID> <cbc:CustomizationID>2.0</cbc:CustomizationID> <cbc:ID>B001-1</cbc:ID> <cbc:IssueDate>2024-02-25</cbc:IssueDate> <cbc:IssueTime>11:16:38</cbc:IssueTime> <cbc:InvoiceTypeCode listID="0101">03</cbc:InvoiceTypeCode> <cbc:Note languageLocaleID="1000"><![CDATA[SON CIENTO DIECIOCHO CON 00/100 SOLES]]></cbc:Note> <cbcocumentCurrencyCode>PEN</cbcocumentCurrencyCode> <cac:Signature> <cbc:ID>21422312421</cbc:ID> <cac:SignatoryParty> <cacartyIdentification> <cbc:ID>21422312421</cbc:ID> </cacartyIdentification> <cacartyName> <cbc:Name><![CDATA[INVERSION A Y B S.A.C.]]></cbc:Name> </cacartyName> </cac:SignatoryParty> <cacigitalSignatureAttachment> <cac:ExternalReference> <cbc:URI>#LIMENA CAE-SIGN</cbc:URI> </cac:ExternalReference> </cacigitalSignatureAttachment> </cac:Signature> <cac:AccountingSupplierParty> <cacarty> <cacartyIdentification> <cbc:ID schemeID="6">21422312421</cbc:ID> </cacartyIdentification> <cacartyName> <cbc:Name><![CDATA[LIMENA CAE]]></cbc:Name> </cacartyName> <cacartyLegalEntity> <cbc:RegistrationName><![CDATA[INVERSION A Y B S.A.C.]]></cbc:RegistrationName> <cac:RegistrationAddress> <cbc:ID>123123</cbc:ID> <cbc:AddressTypeCode>0000</cbc:AddressTypeCode> <cbc:CitySubdivisionName>CASUARINAS</cbc:CitySubdivisionName> <cbc:CityName>LIMA</cbc:CityName> <cbc:CountrySubentity>LIMA</cbc:CountrySubentity> <cbcistrict>LIMA</cbcistrict> <cac:AddressLine> <cbc:Line><![CDATA[CAL.LOMA SOCOSA NRO. 29 (PISO 5) LIMA - LIMA - SANTIAGO DE SURQUILLO]]></cbc:Line> </cac:AddressLine> <cac:Country> <cbc:IdentificationCode>PE</cbc:IdentificationCode> </cac:Country> </cac:RegistrationAddress> </cacartyLegalEntity> <cac:Contact> <cbc:Telephone>01-1231231</cbc:Telephone> <cbc:ElectronicMail>bginoza7@gmail.com</cbc:ElectronicMail> </cac:Contact> </cacarty> </cac:AccountingSupplierParty> <cac:AccountingCustomerParty> <cacarty> <cacartyIdentification> <cbc:ID schemeID="1">24516534</cbc:ID> </cacartyIdentification> <cacartyLegalEntity> <cbc:RegistrationName><![CDATA[PERSON 1]]></cbc:RegistrationName> </cacartyLegalEntity> </cacarty> </cac:AccountingCustomerParty> <cac:TaxTotal> <cbc:TaxAmount currencyID="PEN">18.00</cbc:TaxAmount> <cac:TaxSubtotal> <cbc:TaxableAmount currencyID="PEN">100.00</cbc:TaxableAmount> <cbc:TaxAmount currencyID="PEN">18.00</cbc:TaxAmount> <cac:TaxCategory> <cac:TaxScheme> <cbc:ID>1000</cbc:ID> <cbc:Name>IGV</cbc:Name> <cbc:TaxTypeCode>VAT</cbc:TaxTypeCode> </cac:TaxScheme> </cac:TaxCategory> </cac:TaxSubtotal> </cac:TaxTotal> <cac:LegalMonetaryTotal> <cbc:LineExtensionAmount currencyID="PEN">100.00</cbc:LineExtensionAmount> <cbc:TaxInclusiveAmount currencyID="PEN">118.00</cbc:TaxInclusiveAmount> <cbcayableAmount currencyID="PEN">118.00</cbcayableAmount> </cac:LegalMonetaryTotal> <cac:InvoiceLine> <cbc:ID>1</cbc:ID> <cbc:InvoicedQuantity unitCode="NIU">2</cbc:InvoicedQuantity> <cbc:LineExtensionAmount currencyID="PEN">100.00</cbc:LineExtensionAmount> <cacricingReference> <cac:AlternativeConditionPrice> <cbcriceAmount currencyID="PEN">59</cbcriceAmount> <cbcriceTypeCode>01</cbcriceTypeCode> </cac:AlternativeConditionPrice> </cacricingReference> <cac:TaxTotal> <cbc:TaxAmount currencyID="PEN">18.00</cbc:TaxAmount> <cac:TaxSubtotal> <cbc:TaxableAmount currencyID="PEN">100.00</cbc:TaxableAmount> <cbc:TaxAmount currencyID="PEN">18.00</cbc:TaxAmount> <cac:TaxCategory> <cbcercent>18</cbcercent> <cbc:TaxExemptionReasonCode>10</cbc:TaxExemptionReasonCode> <cac:TaxScheme> <cbc:ID>1000</cbc:ID> <cbc:Name>IGV</cbc:Name> <cbc:TaxTypeCode>VAT</cbc:TaxTypeCode> </cac:TaxScheme> </cac:TaxCategory> </cac:TaxSubtotal> </cac:TaxTotal> <cac:Item> <cbcescription><![CDATA[PROD 1]]></cbcescription> <cac:SellersItemIdentification> <cbc:ID>C023</cbc:ID> </cac:SellersItemIdentification> </cac:Item> <cacrice> <cbcriceAmount currencyID="PEN">50</cbcriceAmount> </cacrice> </cac:InvoiceLine> </Invoice> Continue reading...