quinta-feira, 16 de julho de 2009

Assinando documentos com CAPICOM

No meu último post, falei sobre a biblioteca criptográfica da Microsoft - o CAPICOM - e como usá-la para acessar o Certificate Store do Windows, recuperando e processando os certificados digitais registrados. Agora, vou mostrar como usar as classes do CAPICOM para assinar documentos e como verificar se a assinatura de um documento é válida.

Pra começar, é preciso ter um Certificado Digital disponível com a chave privada para que se possa assinar documentos. Se não possuir tal certificado, pode gerar um através do programa makecert da Microsoft. Tenha em mente apenas que o certificado assim gerado não pode assinar documentos oficialmente pois não é fornecido por uma CA (Certification Authority) válida. Um exemplo do makecert pode ser visto no quadro abaixo, onde é criado um certificado pessoal ("My") no Store Current User com validade até 1 de Janeiro de 2010:
makecert -r -pe -n "CN=Minha Empresa" -b 01/01/2005 -e 01/01/2010 -sky exchange -ss my

Para assinar um documento, precisaremos trabalhar com 2 classes do CAPICOM:
TSigner que contem as informações sobre quem está assinando um documento, incluindo o Certificado Digital.
TSignedData que permite estabelecer os dados a serem assinados. A verificação de uma assinatura também pode ser feita com essa classe.

O trecho de código abaixo carrega um arquivo, assina-o baseado num Certificado Digital e grava o conteúdo assinado num outro arquivo. Veja o post anterior para saber como gerar os fontes associados ao CAPICOM e como encontrar um Certificado no Store.
procedure TForm1.Assina (cert: TCertificate);
var lSigner : TSigner;
lSignedData : TSignedData;
fs : TFileStream;
qt : integer;
ch : PChar;
msg, content : WideString;
begin
{ Abre o arquivo original para obter dele o conteúdo a ser assinado }
fs := TFileStream.Create (edFileName.Text, fmOpenRead);
New (ch);
repeat
qt := fs.Read(ch^, 1);
if (qt > 0) then
content := content + ch^;
until qt = 0;
fs.Free;
Dispose (ch);

{ Configura o objeto responsável por fazer a assinatura, informando qual é o certificado a ser usado e o conteúdo a ser assinado }
lSigner := TSigner.Create(self);
lSigner.Certificate := cert.DefaultInterface;

lSignedData := TSignedData.Create(self);

lSignedData.Content := content;
{ Efetivamente assina o conteúdo }
msg := lSignedData.Sign(lSigner.DefaultInterface, false, CAPICOM_ENCODE_BASE64);

{ Cria um novo arquivo e grava nele o resultado da assinatura }
fs := TFileStream.Create (edMsgFile.Text, fmCreate);
for qt := 1 to Length (msg) do
fs.Write(msg[qt], 2);

fs.Free;

lSignedData.Free;
lSigner.Free;
end;

Neste código, crio uma instância do TSigner e associo a ele o certificado passado no parâmetro da função. Depois, crio uma instância do TSignedData e alimento sua propriedade Content com o conteúdo lido do arquivo (texto) que será assinado.

Com essa configuração ajustada, posso chamar a função Sign do TSignedData para assinar o conteúdo. O primeiro parâmetro desta função é o TSigner onde informei o certificado digital que tem chave privada. O segundo parâmetro indica se o conteúdo original será incluído na assinatura resultante ou não. Incluir o conteúdo na assinatura resulta uma assinatura muito maior mas o processo de verificação dela permitirá extrair o conteúdo original (desde que, é claro, a assinatura não tenha sido adulterada). Se não incluir o conteúdo na assinatura, o conteúdo terá que estar presente para que se possa executar o processo de validação; em compensação, a assinatura será pequena.

O resultado da função Sign é um WideString contendo a assinatura. No exemplo, pedi que a assinatura inclua o conteúdo original e, então, salvo toda a assinatura num arquivo separado. Como cada caracter do WideString ocupa 2 bytes, o trecho do código que grava em arquivo o resultado da assinatura é obrigado a gravar os 2 bytes por caracter. Mostro no próximo post como validar essa assinatura gerada.

20 comentários:

  1. Olá
    muito bom artigo.
    Gostaria de saber se existe alguma forma de decriptografar dados com a chave publica deum certificado digital utilizando a capicom?

    Ronaldo

    ResponderExcluir
  2. Ronaldo

    Há um texto no blog cujo título é Verificando assinaturas digitais com CAPICOM que trata desse assunto. O título faz parecer que se trata apenas de validar o conteúdo assinado mas um dos resultados da validação pode ser justamente a decriptografia dos dados originais.

    []s

    Luís gustavo

    ResponderExcluir
  3. Olá estou seguindo seu exemplo para assinar PDF apartir de um toquem,a te aí tudo bem, ele reconhece o toquem e pede a senha e grava o arquivo mas ao tentar abrir o arquivo dá o aviso que o ele não está codificado corretamente, voce saberia o por quê desse erro?
    Obrigado
    Carlos Eduardo.

    ResponderExcluir
  4. Carlos

    O PDF é um formato proprietário da Adobe e submeter esses arquivos ao procedimento descrito no post o tornará inválido, isto é, ele deixará de ser reconhecido como um PDF.

    A inclusão da assinatura neste caso deve respeitar o tal formato proprietário de modo que você terá que pesquisar onde incluí-la no arquivo PDF para que seja reconhecida pelo Reader e não invalide o formato.

    Manualmente, a assinatura pode ser feita como descrito no help online da Adode

    ResponderExcluir
  5. ok, uma dúvida o retorno dessa função
    "msg := lSignedData.Sign(lSigner.DefaultInterface, false, CAPICOM_ENCODE_BASE64);", á a assinatura ou todo o arquivo já assinado, pois adiciono o conteudo do meu arquivo aqui "lSignedData.Content := content;", se não for todo o arquivo assinado daí tenho que inserir a "msg" no meu arquivo é isso ? daí terei que descobrir onde inserir e isso em tempo de execução.
    Obrigado pela atenção.
    Carlois Eduardo

    ResponderExcluir
  6. Obrigado pela atenção,
    Estou com uma duvída vc guarda o conteúdo do arquivo PDF na variável "content" e após chama a função "lSignedData.Sign", o retorno dessa função é a "assinatura somente" ou todo o arquivo já assinado ? Se retornar sá a assinatura tenho que inseri-la no arquivo como vc faz "fs.Write(msg[qt], 2);" ?

    ResponderExcluir
  7. Carlos

    O resultado da função Sign pode incluir todo o conteúdo assinado ou trazer apenas a assinatura em si, dependendo do que vc passar no segundo parâmetro. No exemplo, está em false; então, apenas a assinatura será devolvida, sem o conteúdo. É esse valor que vc terá que descobrir como inserir em seu arquivo PDF para que ele seja considerado válido e assinado.

    No entanto, esse processo será árduo se tentar fazê-lo todo na mão já que terá que desvendar como um arquivo PDF é construído.

    Minha sugestão é que tente usar o SDK da própria Adobe para fazer isso ou adote alguma biblioteca gratuita, como a iTextSharp (para C#), que já faz todo o trabalho de manipulação de PDFs, incluindo o tratamento de assinatura digital.

    ResponderExcluir
  8. ok, muito obrigado pelos seus esclarecimentos, foram de suma importância, agora já vislumbro uma luz no fim do tunel.
    Carlos Eduardo

    ResponderExcluir
  9. olá Luíz, estou usando do seu blog para tentar efetuar a assinatura digital de um xml da nfe, porem acredito que preciso de mais informações né? por que do jeito que está no blog não seria exatamente o que eu estou procurando, gostaria de saber se vc sabe ou poderia me indicar algo que pudesse me ajudar a gerar as tag's de Signature da nfe, por que hoje ja consigo no c#, porem onde eu trabalho usamos o pascal e eu queria parar de usar aplicativos externos, mesmo que fossem feitos por mim.
    Obrigado pela atenção.
    Danilo.

    ResponderExcluir
  10. Danilo

    Para assinar o NFe com o CAPICOM siga as instruções do Manual de Integração do Contribuinte, que é uma versão um pouco mais simples da recomendação da W3C (aqui). Esse documento mostra como preparar o XML para assinatura e quais nós devem ser considerados para os vários cálculos necessários - como o hash, por exemplo.

    Depois de ter o XML com os dados da Nota preparado, construa os nós relativos à assinatura com a hierarquia de nós (e seus respectivos valores) conforme consta no documento de integração citado acima. Esta estrutura terá que estar em conformidade com o esquema xmldsig-core-schema_v1.01.xsd.

    Se estiver usando Delphi para .NET, pode usar as classes de criptografia do framework para fazer esse trabalho. SE não, terá que fazer manualmente ou procurar alguma biblioteca pronta.

    ResponderExcluir
  11. Gostaria de saber se posso assinar um campo
    MEMO, para ter validade juridica pois se trata
    de um PRONTUÁRIO ELETRONICO DO PACIENTE.

    ResponderExcluir
  12. Luiz

    É sim possível assinar um campo memo com os métodos descritos no post.

    No entanto, quanto a validade jurídica, vc terá que consultar um especialista em direito pra ter certeza de como funciona.

    []

    ResponderExcluir
  13. Luís,
    qual o algoritmo de criptografia default que o TSignedData usa? tem como setar essa propriedade?

    esse método sign gera o hash do conteúdo e depois criptografa ou só criptografa?

    Victor

    ResponderExcluir
    Respostas
    1. Victor

      De acordo com a documentação da função Sign (veja em http://msdn.microsoft.com/en-us/library/aa387726%28v=vs.85%29.aspx), é calculado um hash do conteúdo com o método SHA1.

      Se você precisa usar um algorítimo diferente para calcular o hash, veja o objeto HashedData.

      []s

      Excluir
  14. Olá ....muito interessante o blog.
    Já vi em um post como gerar o conteúdo da tag X509 através da função "export". Mas e os valores do "DigestValue" e "SignatureValue" ?? Vc teria alguma luz usando Delphi e CAPICOM??

    ResponderExcluir
    Respostas
    1. Não tentei calculalr esses valores com CAPICOM - aqui na ABC71 nós optamos por usar classes prontas do .NET.

      No entanto, creio que o hash possa ser calculado com a classe HashedData do CAPICOM. Quanto ao SignatureValue, acho que basta assinar o hash como mostrado no post.

      Veja também o artigo no endereço http://www.xml.com/pub/a/2001/08/08/xmldsig.html; ele trata da teoria sobre a assinatura de um XML.

      []s.

      Excluir
  15. Tem como diminuir o tamanho da Assinatura?

    ResponderExcluir
    Respostas
    1. O processo de assinatura é um algorítimo bem definido, devendo resultar sempre no mesmo valor. É isso que permite garantir a validade da assinatura.

      No método descrito no post, é possível reduzir a qtde de bytes gerados se vc pedir para não incluir no resultado o conteúdo assinado.

      Excluir
  16. Luiz,

    O meu problema é que exportaram os certificados A1 do pc sem a opção "Exportar chave privada" que estava "apagado", gerando assim um ".CER" e não um ".PFX".

    No sistema pelo "certmgr.msc", lá consta todas as informações do certificado.

    Tem como recuperar a chave privada do certificado?

    ResponderExcluir
    Respostas
    1. Antonio

      Depois que o certificado foi exportado sem a chave privada, não é possível recuperá-la a partir do arquivo. Será necessário exportar novamente com a opção de incluir a chave ou utilizar o arquivo original enviado pela autoridade certificadora.

      []s

      Excluir

OBS: Os comentários enviados a este Blog são submetidos a moderação. Por isso, eles serão publicados somente após aprovação.