Assinando digitalmente um XML usando C#

A introdução da Nota Fiscal Eletrônica e da Escrituração Digital está dando um grande impulso no uso de Certificados Digitais, já que praticamente todas as empresas terão que adotá-los para assinar os documentos eletrônicos exigidos pela Receita Federal. O formato adotado pela Receita para seus documentos eletrônicos é o XML – daí a importância de conhecer o processo de assiná-lo.

Depois de adquirir um Certificado Digital (nos Correios ou no Serasa, por exemplo), o primeiro passo para a assinatura é saber como localizar esse Certificado. Mas, o que é na prática um Certificado Digital? Como é que o Windows trabalha com isso?

O Certificado em si, a grosso modo, é apenas um arquivo onde são armazenadas informações sobre quem outorgou o certificado (uma CA, ou Certificate Authority) e a quem é que foi outorgado o certificado (uma pessoa física ou jurídica ou ainda um computador) bem como um código público (a chave). O Certificado, portanto, associa uma chave pública a uma entidade (empresa, pessoa ou computador). Há um artigo no Wikipedia mais detalhado sobre esse assunto.

Se você (ou sua empresa ou seu computador) é quem recebeu o certificado, você também possui a chave privada, isto é, o código que só você conhece e que será usado para assinar um documento digital, garantindo-lhe a integridade, a autenticidade e conferindo validade jurídica ao documento.

No Windows, o acesso aos Certificados disponíveis se dá de forma centralizada, através do Armazém de Certificados Digitais (ou Certificate Store) . Isto é, uma vez que o certificado foi registrado no computador, não importa onde ele está armazenado : o acesso deve ser feito a partir do store. Para acessá-lo a partir do Windows, vá em Iniciar -> Executar e digite

mmc windowssystem32certmgr.msc

Esse programa mostra de forma estruturada quais são os certificados presentes em seu computador: aqueles de uso exclusivo de seu usuário (Personal), as Autoridadades Certificadoras (tanto as de nível mais alto na hierarquia certificadora – as root ou raíz – quanto as intermediárias, que dependem da root), etc.

Via programação, o framework do .NET também fornece uma forma estruturada de acessar o Store, através do namespace System.Security.Cryptography.X509Certificates. São basicamente 3 classes para acessar os certificados :

  • X509Store : Representa o acesso ao Store. O construtor dela exige que se estipule qual parte da estrutura deve ser acessada. Use as definições na classe StoreName para identificar, por exemplo, os certificados particulares (“My”) ou as Autoridades Certificadoras (“CA”). Pode optar também por informar apenas a localização, através da classe StoreLocation (somente os meus certicados ou todos os certificados presentes no computador).
  • X509Certificate2Collection : Uma lista dos certificados armazenados no Store aberto com a classe anterior.
  • X509Certificate2: Representa um certificado na coleção.


O exemplo abaixo em C# percorre a lista de certificados na Store “My”, procurando os válidos e que possuam chave privada.

X509Certificate2Collection lcerts;
X509Store lStore = new X509Store (StoreName.My, StoreLocation.CurrentUser);

// Abre o Store
lStore.Open(OpenFlags.ReadOnly);

// Lista os certificados
lcerts = lStore.Certificates;

foreach (X509Certificate2 cert in lcerts)
{
if (cert.HasPrivateKey && cert.NotAfter > DateTime.Now && cert.NotBefore < DateTime.Now)
{
// Faz o uso do certificado….
// Por exemplo, assinar um docto.
}
}
lStore.Close();

Agora que sabemos localizar um Certificado válido, podemos partir para a assinatura propriamente dita. Para facilitar, reproduzo abaixo um excerto do XML que representa uma Nota Fiscal Eletrônica:

<?xml version=”1.0″ encoding=”UTF-8″ ?>
<enviNFe versao=”1.10 xmlns=”http://www.portalfiscal.inf.br/nfe“>
<idLote>71</idLote>
<NFe>
<infNFe Id=”NFe3508059978versao=”1.10“>
<cUF>35</cUF>
<cNF>518005127</cNF>
<natOp>Venda a vista</natOp>
<mod>55</mod>
<serie>1</serie>
<dEmi>2008-05-06</dEmi>
<tpAmb>2</tpAmb>
</infNFe>
</NFe>
</enviNFe>

A Receita Federal usou um subconjunto do padrão do W3C para estipular as regras de assinatura digital da NFe. Devem ser assinados todos os elementos infNFe de um lote XML de Notas Fiscais segundo essas regras. Considerando uma variável do tipo XmlDocument de nome doc, já montada com a estrutura correta da NFe, segue um exemplo em C# de como chegar a esse nó:

XmlNodeList ListInfNFe = doc.GetElementsByTagName(“infNFe”);

Há uma classe no namespace System.Security.Cryptography.Xml do framework .NET chamada SignedXml, que implementa o padrão W3C para assinatura de documentos e verificação de documentos assinados. O trecho de código abaixo exemplifica a configuração básica desta classe:

foreach (XmlElement infNFe in ListInfNFe)
{
string id = infNFe.Attributes.GetNamedItem(“Id”).Value;
signedXml = new SignedXml(infNFe);
signedXml.SigningKey = ObtemCertificado().PrivateKey;

O id obtido é um código calculado segundo regras estipuladas pela Receita Federal e serve para identificar cada Nota. Esse id é inserido como um atributo no elemento infNFe. Ele será usado mais adiante, quando for preciso referenciar o elemento infNFe na assinatura. A função ObtemCertificado é minha: ela usa os conceitos descritos no início desse artigo. A propriedade SigningKey é a chave quer será usada para calcular a assinatura; por isso, é atribuido a ela a chave privada do certificado obtido.

Parte da assinatura é dedicada a descrever o nó a partir do qual a assinatura foi feita e quais transformações esse nó e seus filhos sofreram antes da assinatura ser efetivamente calculada. De acordo com a documentação da Receita Federal, as transformações a serem aplicadas são duas:

  1. a que indica que a assinatura é “envelopada”, ou seja, o trecho assinado é incluído na assinatura. Aqui cabe um esclarecimento: o elemento infNFe não é o elemento assinado; ele na verdade é usado para calcular um valor chamado de DigestValue, este sim inserido no trecho assinado, de forma que qualquer alteração no conteúdo de infNFe ou de seus filhos resultará num DigestValue diferente e, por fim, a assinatura também não será válida.
  2. a que indica que o XML deve ser colocado na forma canônica antes do processamento.

A classe Reference cuida dessa parte do processo, incluindo a identificação do nó infNFe e as transformações exigidas:

// Transformações p/ DigestValue da Nota
Reference reference = new Reference(“#” + id);
reference.AddTransform(new XmlDsigEnvelopedSignatureTransform());
reference.AddTransform(new XmlDsigC14NTransform());
signedXml.AddReference(reference);

Antes de computar a assinatura, falta configurar o tratamento das informações a respeito do certificado digital utilizado. É com base nesses dados que a Receita Federal consegue validar a assinatura e atestar que nenhuma informação foi modificada depois que o emissor da Nota Fiscal a assinou. Devemos incluir uma cláusula com os dados do certificado:

KeyInfo keyInfo = new KeyInfo();
keyInfo.AddClause(new KeyInfoX509Data(ObtemCertificado()));
signedXml.KeyInfo = keyInfo;

Agora, só falta computar a assinatura:

signedXml.ComputeSignature();

Neste ponto, a variável signedXml contém todos os dados para criar o elemento Signature (a assinatura) que deve ser incluído na NFe antes do envio à Receita Federal. Esse elemento deve ser incluído como um nó filho do elemento NFe, no mesmo nível do nó infNFe

Considere a variável xmlNFe do tipo XmlElement criada de acordo com o descrito no post sobre criação de nós no XML. Considere também uma instância da classe SignedXml chamada signedXml e preparada da forma descrita acima. Vou usá-las no exemplo a seguir.

Primeiro, criarei um nó para conter o elemento Signature. Depois, vou usar as propriedades SignedInfo e KeyInfo do signedXml para obter nós XML contendo respectivamente dados sobre a informação que foi assinada e sobre a chave usada nessa assinatura:

XmlElement xmlSignature = doc.CreateElement(“Signature”, “http://www.w3.org/2000/09/xmldsig#”);
XmlElement xmlSignedInfo = signedXml.SignedInfo.GetXml();
XmlElement xmlKeyInfo = signedXml.KeyInfo.GetXml();

Note que o namespace passado para a criação do nó Signature é diferente daquele usado para criar as informações da Receita Federal que usei no post sobre a criação de nós. O namespace “http://www.w3.org/2000/09/xmldsig#” indica que vamos usar a sintaxe de assinatura descrita no link do W3C, conforme especificado na documentação da Receita Federal.

Além dos dados que já obtivemos, é necessário ainda acrescentar a assinatura em si como nó filho do elemento Signature. De acordo com a documentação, a assinatura – que é uma sequência de bytes – deve ser representada como um texto utilizando a codificação Base 64. A criação do nó SignatureValue, sua conversão para Base 64 e a adição do nó ao elemento Signature estão no trecho de código abaixo:

XmlElement xmlSignatureValue = doc.CreateElement(“SignatureValue”, xmlSignature.NamespaceURI);
string signBase64 = Convert.ToBase64String(signedXml.Signature.SignatureValue);
XmlText text = doc.CreateTextNode(signBase64);
xmlSignatureValue.AppendChild(text);
xmlSignature.AppendChild(xmlSignatureValue);

O trecho signedXml.Signature.SignatureValue é onde se recupera o valor da assinatura computada.

Agora, só falta importar os dados contidos nos elementos xmlSignedInfo e xmlKeyInfo obtidos no primeiro passo, acrescentado-os ao elemento NFe:

xmlSignature.AppendChild(doc.ImportNode(xmlSignedInfo, true));
xmlSignature.AppendChild (doc.ImportNode(xmlKeyInfo, true));
xmlNfe.AppendChild(xmlSignature);

Depois de acrescentar o xmlNfe ao documento XML final, deve-se transformar o XmlDocument doc em texto para ser enviado à Receita Federal.

Referência: Blog Balaio Tecnológico

Luís Gustavo Fabbro

Mais artigos deste autor »

Bacharel em Ciências da Computação, formado pela Unesp Bauru. Foi responsável pelos módulos da área industrial do ERP da ABC71 Soluções em Informática entre 1993 e 1998. Em 2002, passou a ser responsável pela então nascente área de Pesquisa & Desenvolvimento da empresa. É profissional certificado pela Microsoft, com especialização na arquitetura do Sistema Operacional Windows e mantem o blog Balaio Tecnológico, dedicado a tecnologia.


28 Comentários

anderson
1

Ao enviar a NF-e para a “fazenda” ela retorna com o erro 404 – rejeição uso de prefixo de namespace não permitido.
…???
O que eu faço para resolver este problema.
OBS: estou usando o emissor gratuito (fornecido pela sefaz)

Luís Gustavo Fabbro
2

Anderson
Essa mensagem do SEFAZ indica que o seu XML foi criado com namespace num nó em que ele não é esperado. Reveja a criação do XML, verificando onde está sendo inserido namespaces e remova os sobressalentes.
Se não conseguir, poste uma parte do XML que você criou para que possamos analisá-lo ou envie-o para o email do meu blog :

Marcílio Jr.
3

Estou encontrando o mesmo problema que o anderson relatou: erro 404 – rejeição uso de prefixo de namespace não permitido. Estou tentando usar a versão 2.0 do Webservice. Alguém poderia me orientar no que pode ser esse problema? Isso só acontece quando faço a consulta de um processamento de lote. No envio está tudo ok.

Thiago
5

Ótimo artigo!
Me ajudou muito, eu estava com alguns problemas na rotina de assinatura. Após seguir o exemplo passo-a-passo ficou tudo ok.
Abraços!

Fábio Freitas
6

Olá Luís Gustavo,
Esse exemplo só funciona em aplicações desktop correto? E se a aplicação for web?
Um abraço

Luís Gustavo Fabbro
7

Fábio
O código que está no post não está restrito a aplicações desktop – ele funciona para aplicações web também. Aqui na ABC71 nós construímos um serviço centralizado via web para processar as notas fiscais, aí incluindo a assinatura digital nos mesmos moldes do post.
A única ressalva é a forma de localizar o certificado digital. O código do post procura apenas no store do usuário atual mas um serviço ou uma aplicação ASP/ASP.Net normalmente se logam no Windows com o usuário System. Portanto, você teria que instalar o certificado no store do LocalMachine e buscá-lo aí ao invés do StoreLocation.CurrentUser.
Se essa explicação não ficou clara, dê uma olhada no post Funcionamento do Certificate Store do Windows para entender melhor esse recurso do Windows.

Cesar
8

Opa, fiz um esquema de XML Hierarquico, mas estou com um pequeno erro será que vc pode me ajudar?
– 71 – – 35 aaa 518005127 -518005127010101019000152XXXXl LTDA – MEXXXXX-R. XX Poa3011116983411411117417
Como vc pode ver o valor 518005127 deveria esta na Tag , mas esta na Tag EMIT perdido.

Danilo
12

Pessoal meu XML está com um erro que está me assombrando ah dias!
297 – [Simulacao] Rejeicao: Assinatura difere do calculado
Estou fazendo os testes aqui: https://www.sefaz.rs.gov.br/NFE/NFE-VAL.aspx
Já fiz de tudo! verifiquei caracteres especiais, limpei o arquivo, verifiquei as tags, eu já não sei mais o que fazer.
Se alguém estiver disposto a ajudar, eu mando o XML por e-mail, obg.

Luís Gustavo Fabbro
13

Danilo
O erro “Assinatura difere do calculado” pode ocorrer quando a tag XML passada para a função de assinatura não é a esperada. Também pode ocorrer se as “transformações” exigidas não forem respeitadas (como a canonicalização e o envelopamento descritos no post). Chegou a depurar esses pontos para garantir que as tags usadas são as corretas e estão montadas conforme o manual ?
Se está certo de ter usado as tags corretas, poste um exemplo de XML assinado por seu programa, junto com o trecho de código que faz a assinatura ou envie email com esses dados para meu blog :
balaiotecnologico@gmail.com
[]s

Luis Bizzan
15

Bom dia,
Gostaria de saber como eu gero o código calculado que vai no atributo ID da Tag infNFe ?
Estou gerando a nota sem essa informação e na hora da transmissão ele retorna assinatura inválida.
Aguardo.

Leandro
16

Bom dia, estou tentando assinar uma nota eletrônica de serviço (Municipal) e a tag que preciso assinar, não tem o atributo “id”, como faço pra alterar o assinador para considerar essa diferença?

Garibaldo Luis Guerreiro Chaves
17

Boa tarde Luis Gustavo.
Estou utilizando uma rotina que você postou para validação de um XML de um CTe.
na linha
settings.Schemas.Add(edNamespace.Text, edXSD.Text); está dando um erro:
Acesso ao Caminho ‘C:\GLSISTEM\Schemas’, foi negado.
O que pode está acontecendo. Já estou executando como Administrador.
Garibaldo

João Paulo
18

Boa tarde,
Alguém poderia me informar um modelo de xml para envio de solicitação para o Webservice da Sefaz (nfeDistDFeInteresse AN) ?

Alessandro Silvestre
19

Estou desenvolvendo software para emitir NF-e, porem consigo o certificado. Como eu faço para ter um certificado de homologação. Este tipo de certificado também é pago. Existe algum lugar na WEB que possa me gerar este tipo de certificado para instalar nas maquinas, apenas para DESENVOLVIMENTO ou seja para ser usado no ambiente de homologação da receita.

Lineu
20

Muito obrigado, era isso que eu precisava. Apanhei um pouco mas consegui assinar um documento eletrônicamente. Valeu!

Everton
21

Boa noite.
Estou com o problema comum para quem precisa assinar o documento XML com certificado A3 para envio da nota fiscal eletrônica em sistema ASP.net Web.
Estava estudando instalar uma aplicação no cliente e executar pelo browser no momento da solicitação do envio… mas tem vários problemas de permissão e o controle ActiveX pode não atender a todos os browsers.
Como o Java Applet está se tornando obsoleto, não podemos utilizá-lo, seria uma boa opção.
Que tipo de solução estão implementando para fazer a assinatura do XML, nem que seja no lado cliente, e posteriormente o envio da nota ao Sefaz?

Marco Antonio
22

Olá, Estou desenvolvendo xml para envio do SEFAZ SP , segui todas as orientações postadas, quando envio o xml para receita ele é rejeitado por assinatura invalida . Podem me ajudar ? Estou vários dias tentado achar o problema . O cerificado esta dentro da validade , consigo vê-lo através do debug. As tags de assinatura foram todas criadas corretamente no xml.

Marcelo de Oliveira
23

Olá Luís.
Obrigado por compartilhar o seu conhecimento, e parabéns pelo post.
Até esse momento eu exportava o xml que foi gerado pelo meu programa, para o sistema da receita, e de lá os usuários faziam toda a parte de comunicação com a receita.
Porém, a partir de agora, eu terei que fazer tudo.
Por favor, se não for incomodo, há algum lugar onde você postou todo o código (C#) do artigo, para que eu possa estudar como se faz esse processo?
Novamente obrigado e um forte abraço!

Giancarlo Andreoli Peccin
24

Olá. Estou com um problema. Sigo os passos para fazer o DigestValue, mas na hora de executar o signedXml.ComputeSignature(); ocorre um erro:Malforme
d reference element. Quando assino um Lote de notas fiscais, ai funciona.
Ao assinar notas individuais, se eu colocar o id como “#lote” ao invés do id da nota, ele gera o digestvalue. Alguma idéia do que possa ser o problema? Qualquer ajuda eu agradeço. Parabéns pelo Post.
System.Security.Cryptography.CryptographicException: Malforme
d reference element.
at System.Security.Cryptography.Xml.Reference.CalculateHashValue(XmlDocument
document, CanonicalXmlNodeList refList)
at System.Security.Cryptography.Xml.SignedXml.BuildDigestedReferences()
at System.Security.Cryptography.Xml.SignedXml.ComputeSignature()
at escolhercertificadosimples.Program.Main(String[] args) in C:\Users\user\Do
cuments\Visual Studio 2015\Projects\assinaturalote\assinaturalote\Program.cs:lin
e 143

LUIS ALBUQUERQUE DE ARAUJO
25

Luis, trabalho em uma software house, com uma solução em asp.net(c#) , transmitimos as notas para Sefaz, quando o certificado do cliente e a1 ele fica em nosso servidor, quando é a3 instalamos um aplicação nossa desktop para ler, assinar e transmitir o arquivo pela máquina do cliente.
Me falaram que é possível assinar e transmitir direto pela minha aplicação web acessando o certificado local A3 do cliente.
Tentamos de tudo e nunca conseguimos.
Você sabe como fazer, estamos dispostos a pagar por tal informação.
Desde já agradeço.
Se for sim por favor enviar um e-mail.
[email protected]

Ronald
26

Boa Tarde !
Preciso de um assinador que funcione com certificado A1 e A3 (toquem e smartcard) para assinar um xml, sera que vocês tem um fonte funcionando para eu utilizar em C# ou Java ?
Obrigado.

Deixe seu comentário

Seu endereço de e-mail não será publicado. Campos com * são obrigatórios!