I'm using the XMLSec library to sign a file. I need to use the specific algorithms listed below and include the invoice identification number in the reference URI. However, when the URI parameter is filled, the document is not signed, but if I leave it empty, the signature is generated successfully. I've already checked the ID, and everything seems correct.
Example ID: #NFCom43253123827184000163620017659731731076965182
Problem?
ref = xmlsec.template.add_reference(
signature, xmlsec.constants.TransformSha1, uri=f"#{id_nfcom}"
)
def assinar_xml(xml_tree, certificado_pem):
root = xml_tree.getroot()
inf_nfcom = root.find(".//{}infNFCom")
if inf_nfcom is None:
raise ValueError("Elemento <infNFCom> não encontrado no XML.")
id_nfcom = inf_nfcom.get("Id")
if not id_nfcom:
raise ValueError("Atributo 'Id' não encontrado no elemento <infNFCom>.")
signature = xmlsec.template.create(
root, xmlsec.Transform.C14N, xmlsec.Transform.RSA_SHA1
)
**ref = xmlsec.template.add_reference(
signature, xmlsec.constants.TransformSha1, uri=f"#{id_nfcom}"
)**
xmlsec.template.add_transform(ref, xmlsec.Transform.ENVELOPED)
xmlsec.template.add_transform(ref, xmlsec.Transform.C14N)
key_info = xmlsec.template.ensure_key_info(signature)
x509_data = xmlsec.template.add_x509_data(key_info)
xmlsec.template.x509_data_add_certificate(x509_data)
root.append(signature)
ctx = xmlsec.SignatureContext()
key = None
try:
key = xmlsec.Key.from_file(certificado_pem, xmlsec.constants.KeyDataFormatPem)
print("Chave carregada com sucesso!")
except Exception as e:
print(f"Erro ao carregar a chave: {e}")
if key is None:
raise ValueError("A chave não foi carregada corretamente.")
ctx.key = key
ctx.sign(signature)
return xml_tree
# xml BEFORE SIGNATURE #
<NFCom xmlns=";>
<infNFCom Id="NFCom43253123827184000163620017659731731076965182" versao="1.00">
<ide>
<cUF>43</cUF>
<tpAmb>2</tpAmb>
<mod>62</mod>
<serie>1</serie>
<nNF>765973173</nNF>
<cNF>7696518</cNF>
<cDV>2</cDV>
<dhEmi>2025-01-31T11:56:18-03:00</dhEmi>
<tpEmis>1</tpEmis>
<nSiteAutoriz>0</nSiteAutoriz>
<cMunFG>3205309</cMunFG>
<finNFCom>0</finNFCom>
<tpFat>0</tpFat>
<verProc>1.00</verProc>
</ide>
<emit>
<CNPJ>23827184005463</CNPJ>
<IE>9071339235</IE>
<IEUFDest>321</IEUFDest>
<CRT>1</CRT>
<xNome>TELECOM LTDA ME</xNome>
<xFant>Telecom</xFant>
<enderEmit>
<xLgr>AV. PRESIDENTE KENNEDY</xLgr>
<nro>845</nro>
<xBairro>Centro</xBairro>
<cMun>4117909</cMun>
<xMun>Palotina</xMun>
<CEP>85950000</CEP>
<UF>PR</UF>
</enderEmit>
</emit>
<dest>
<xNome>Cliente de Telecomunicações Ltda</xNome>
<CNPJ>98765432000187</CNPJ>
<indIEDest>1</indIEDest>
<IE></IE>
<IM>1</IM>
<enderDest>
<xLgr>Rua das Comunicações</xLgr>
<nro>123</nro>
<xBairro>Centro</xBairro>
<cMun>3205309</cMun>
<xMun>Vitória</xMun>
<CEP>85950000</CEP>
<UF>ES</UF>
</enderDest>
</dest>
<assinante>
<iCodAssinante>01</iCodAssinante>
<tpAssinante>1</tpAssinante>
<tpServUtil>4</tpServUtil>
</assinante>
<det nItem="1">
<prod>
<cProd>001</cProd>
<xProd>Serviço de Internet Banda Larga</xProd>
<cClass>0400401</cClass>
<CFOP>5303</CFOP>
<uMed>2</uMed>
<qFaturada>100.00</qFaturada>
<vItem>100.00</vItem>
<vProd>100.00</vProd>
</prod>
<imposto>
<ICMS00>
<CST>00</CST>
<vBC>10.00</vBC>
<pICMS>18.00</pICMS>
<vICMS>1.80</vICMS>
</ICMS00>
</imposto>
</det>
<total>
<vProd>100.00</vProd>
<ICMSTot>
<vBC>10.00</vBC>
<vICMS>1.80</vICMS>
<vICMSDeson>1.00</vICMSDeson>
<vFCP>1.00</vFCP>
</ICMSTot>
<vCOFINS>1.00</vCOFINS>
<vPIS>1.00</vPIS>
<vFUNTTEL>1.00</vFUNTTEL>
<vFUST>1.00</vFUST>
<vRetTribTot>
<vRetPIS>1.00</vRetPIS>
<vRetCofins>1.00</vRetCofins>
<vRetCSLL>1.00</vRetCSLL>
<vIRRF>1.00</vIRRF>
</vRetTribTot>
<vDesc>0.00</vDesc>
<vOutro>1.00</vOutro>
<vNF>100.00</vNF>
</total>
</infNFCom>
<infNFComSupl>
<qrCodNFCom>;amp;tpAmb=2</qrCodNFCom>
</infNFComSupl>
</NFCom>
I'm using the XMLSec library to sign a file. I need to use the specific algorithms listed below and include the invoice identification number in the reference URI. However, when the URI parameter is filled, the document is not signed, but if I leave it empty, the signature is generated successfully. I've already checked the ID, and everything seems correct.
Example ID: #NFCom43253123827184000163620017659731731076965182
Problem?
ref = xmlsec.template.add_reference(
signature, xmlsec.constants.TransformSha1, uri=f"#{id_nfcom}"
)
def assinar_xml(xml_tree, certificado_pem):
root = xml_tree.getroot()
inf_nfcom = root.find(".//{http://www.portalfiscal.inf.br/NFCom}infNFCom")
if inf_nfcom is None:
raise ValueError("Elemento <infNFCom> não encontrado no XML.")
id_nfcom = inf_nfcom.get("Id")
if not id_nfcom:
raise ValueError("Atributo 'Id' não encontrado no elemento <infNFCom>.")
signature = xmlsec.template.create(
root, xmlsec.Transform.C14N, xmlsec.Transform.RSA_SHA1
)
**ref = xmlsec.template.add_reference(
signature, xmlsec.constants.TransformSha1, uri=f"#{id_nfcom}"
)**
xmlsec.template.add_transform(ref, xmlsec.Transform.ENVELOPED)
xmlsec.template.add_transform(ref, xmlsec.Transform.C14N)
key_info = xmlsec.template.ensure_key_info(signature)
x509_data = xmlsec.template.add_x509_data(key_info)
xmlsec.template.x509_data_add_certificate(x509_data)
root.append(signature)
ctx = xmlsec.SignatureContext()
key = None
try:
key = xmlsec.Key.from_file(certificado_pem, xmlsec.constants.KeyDataFormatPem)
print("Chave carregada com sucesso!")
except Exception as e:
print(f"Erro ao carregar a chave: {e}")
if key is None:
raise ValueError("A chave não foi carregada corretamente.")
ctx.key = key
ctx.sign(signature)
return xml_tree
# xml BEFORE SIGNATURE #
<NFCom xmlns="http://www.portalfiscal.inf.br/NFCom">
<infNFCom Id="NFCom43253123827184000163620017659731731076965182" versao="1.00">
<ide>
<cUF>43</cUF>
<tpAmb>2</tpAmb>
<mod>62</mod>
<serie>1</serie>
<nNF>765973173</nNF>
<cNF>7696518</cNF>
<cDV>2</cDV>
<dhEmi>2025-01-31T11:56:18-03:00</dhEmi>
<tpEmis>1</tpEmis>
<nSiteAutoriz>0</nSiteAutoriz>
<cMunFG>3205309</cMunFG>
<finNFCom>0</finNFCom>
<tpFat>0</tpFat>
<verProc>1.00</verProc>
</ide>
<emit>
<CNPJ>23827184005463</CNPJ>
<IE>9071339235</IE>
<IEUFDest>321</IEUFDest>
<CRT>1</CRT>
<xNome>TELECOM LTDA ME</xNome>
<xFant>Telecom</xFant>
<enderEmit>
<xLgr>AV. PRESIDENTE KENNEDY</xLgr>
<nro>845</nro>
<xBairro>Centro</xBairro>
<cMun>4117909</cMun>
<xMun>Palotina</xMun>
<CEP>85950000</CEP>
<UF>PR</UF>
</enderEmit>
</emit>
<dest>
<xNome>Cliente de Telecomunicações Ltda</xNome>
<CNPJ>98765432000187</CNPJ>
<indIEDest>1</indIEDest>
<IE></IE>
<IM>1</IM>
<enderDest>
<xLgr>Rua das Comunicações</xLgr>
<nro>123</nro>
<xBairro>Centro</xBairro>
<cMun>3205309</cMun>
<xMun>Vitória</xMun>
<CEP>85950000</CEP>
<UF>ES</UF>
</enderDest>
</dest>
<assinante>
<iCodAssinante>01</iCodAssinante>
<tpAssinante>1</tpAssinante>
<tpServUtil>4</tpServUtil>
</assinante>
<det nItem="1">
<prod>
<cProd>001</cProd>
<xProd>Serviço de Internet Banda Larga</xProd>
<cClass>0400401</cClass>
<CFOP>5303</CFOP>
<uMed>2</uMed>
<qFaturada>100.00</qFaturada>
<vItem>100.00</vItem>
<vProd>100.00</vProd>
</prod>
<imposto>
<ICMS00>
<CST>00</CST>
<vBC>10.00</vBC>
<pICMS>18.00</pICMS>
<vICMS>1.80</vICMS>
</ICMS00>
</imposto>
</det>
<total>
<vProd>100.00</vProd>
<ICMSTot>
<vBC>10.00</vBC>
<vICMS>1.80</vICMS>
<vICMSDeson>1.00</vICMSDeson>
<vFCP>1.00</vFCP>
</ICMSTot>
<vCOFINS>1.00</vCOFINS>
<vPIS>1.00</vPIS>
<vFUNTTEL>1.00</vFUNTTEL>
<vFUST>1.00</vFUST>
<vRetTribTot>
<vRetPIS>1.00</vRetPIS>
<vRetCofins>1.00</vRetCofins>
<vRetCSLL>1.00</vRetCSLL>
<vIRRF>1.00</vIRRF>
</vRetTribTot>
<vDesc>0.00</vDesc>
<vOutro>1.00</vOutro>
<vNF>100.00</vNF>
</total>
</infNFCom>
<infNFComSupl>
<qrCodNFCom>http://dfe-portal.svrs.rs.gov.br/NFCom/QRCode?chNFCom=43120201234567890123456789012345678901234567&tpAmb=2</qrCodNFCom>
</infNFComSupl>
</NFCom>
If you have such an XML template:
<?xml version="1.0" encoding="utf-8"?>
<NFCom xmlns="http://www.portalfiscal.inf.br/NFCom" xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<infNFCom Id="NFCom43253123827184000163620017659731731076965182" versao="1.00">
...
</infNFCom>
<ds:Signature />
<infNFComSupl>
<qrCodNFCom>http://dfe-portal.svrs.rs.gov.br/NFCom/QRCode?chNFCom=43120201234567890123456789012345678901234567&tpAmb=2</qrCodNFCom>
</infNFComSupl>
</NFCom>
And you use xmlsec
with pip install xmlsec
and OpenSSL
with pip install pyOpenSSL
you can sign <infNFCom>
part with:
import os
from lxml import etree
import xmlsec
from OpenSSL import crypto
import random
PRIVATE_KEY_FILE = "private_key.pem"
PUBLIC_KEY_FILE = "public_key.pem"
CERTIFICATE_FILE = "certificate.pem"
XML_FILE_TO_SIGN = "nfcom_to_sign.xml"
SIGNED_XML_FILE = "signed_invoice.xml"
NS = {
"nfcom": "http://www.portalfiscal.inf.br/NFCom",
"ds": "http://www.w3.org/2000/09/xmldsig#"
}
def generate_rsa_key_and_certificate():
if not os.path.exists(PRIVATE_KEY_FILE) or not os.path.exists(CERTIFICATE_FILE):
private_key = crypto.PKey()
private_key.generate_key(crypto.TYPE_RSA, 2048)
cert = crypto.X509()
cert.set_version(2)
cert.set_serial_number(random.getrandbits(64))
subject = cert.get_subject()
subject.CN = "example.com"
subject.O = "Example"
subject.L = "Los Angeles"
subject.ST = "California"
subject.C = "US"
cert.set_issuer(subject)
cert.set_notBefore(b'20250301000000Z')
cert.set_notAfter(b'20260301000000Z')
cert.set_pubkey(private_key)
cert.sign(private_key, "sha256")
with open(PRIVATE_KEY_FILE, "wb") as f:
f.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, private_key))
with open(PUBLIC_KEY_FILE, "wb") as f:
f.write(crypto.dump_publickey(crypto.FILETYPE_PEM, private_key))
with open(CERTIFICATE_FILE, "wb") as f:
f.write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert))
print(f"Keys and certificate generated: {PRIVATE_KEY_FILE}, {PUBLIC_KEY_FILE}, {CERTIFICATE_FILE}")
else:
print("Keys and certificate already exist.")
def load_private_key():
with open(PRIVATE_KEY_FILE, "rb") as f:
return xmlsec.Key.from_file(f, xmlsec.KeyFormat.PEM)
def load_certificate():
with open(CERTIFICATE_FILE, "rb") as f:
cert_data = f.read().decode("utf-8")
return "".join(cert_data.splitlines()[1:-1]) # Remove BEGIN/END lines
def sign_xml(xml_string, add_x509=False):
etree.register_namespace("ds", NS["ds"]) # Register namespace globally
parser = etree.XMLParser(remove_blank_text=True)
root = etree.XML(xml_string.encode("utf-8"), parser)
inf_nfcom = root.find(".//nfcom:infNFCom", namespaces=NS)
if inf_nfcom is None:
raise ValueError("Error: <infNFCom> element not found.")
# Create Signature template
signature = xmlsec.template.create(
root, xmlsec.Transform.EXCL_C14N, xmlsec.Transform.RSA_SHA256, ns="ds"
)
inf_nfcom.append(signature)
ref = xmlsec.template.add_reference(signature, xmlsec.Transform.SHA256, uri="")
xmlsec.template.add_transform(ref, xmlsec.Transform.ENVELOPED)
xmlsec.template.add_transform(ref, xmlsec.Transform.EXCL_C14N)
key_info = xmlsec.template.ensure_key_info(signature)
if add_x509:
x509_data = xmlsec.template.add_x509_data(key_info)
cert_element = etree.SubElement(x509_data, "{http://www.w3.org/2000/09/xmldsig#}X509Certificate")
cert_element.text = load_certificate()
# Sign the XML
signature_context = xmlsec.SignatureContext()
signature_context.key = load_private_key()
signature_context.sign(signature)
return etree.tostring(root, pretty_print=True, xml_declaration=True, encoding="utf-8").decode()
if __name__ == "__main__":
generate_rsa_key_and_certificate()
ADD_X509 = True
if not os.path.exists(XML_FILE_TO_SIGN):
print(f"Error: XML file '{XML_FILE_TO_SIGN}' not found.")
exit(1)
with open(XML_FILE_TO_SIGN, "r", encoding="utf-8") as f:
xml_to_sign = f.read()
try:
signed_xml = sign_xml(xml_to_sign, ADD_X509)
with open(SIGNED_XML_FILE, "w", encoding="utf-8") as f:
f.write(signed_xml)
print(f"Signed XML saved as '{SIGNED_XML_FILE}'")
except Exception as e:
print(f"Signing failed: {e}")