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.

24 comentários :

Anônimo disse...

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

Luís Gustavo Fabbro disse...

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

Carlos disse...

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.

Luís Gustavo Fabbro disse...

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

Carlos disse...

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

Carlos disse...

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);" ?

Luís Gustavo Fabbro disse...

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.

Carlos disse...

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

Anônimo disse...

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.

Luís Gustavo Fabbro disse...

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.

Luiz disse...

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

Luís Gustavo Fabbro disse...

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.

[]

Anônimo disse...

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

Luís Gustavo Fabbro disse...

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

Anônimo disse...

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??

Luís Gustavo Fabbro disse...

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.

Anônimo disse...

Tem como diminuir o tamanho da Assinatura?

Luís Gustavo Fabbro disse...

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.

Antonio Caser disse...

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?

Luís Gustavo Fabbro disse...

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

Unknown disse...

Boa tarde Luís;

Você saberia me informar como faço para colocar mais de uma assinatura digital no mesmo arquivo?

Luís Gustavo Fabbro disse...

Henrique

Que tipo de problema vc está tendo? Com que tipo de arquivo vc está lidando? Em tese, com arquivos texto como no exemplo, bastaria calcular a assinatura uma vez com cada certificado; dependendo da necessidade, pode aplicar ambas as assinaturas ao conteúdo original ou encadeá-las, com a 2ª sendo aplicada sobre o conteúdo assinado com o 1º.

[]s

NETPOINT Informática disse...

Não tem um exemplo de utilização da procedure??

Luís Gustavo Fabbro disse...

Basta chamar a procedure passando como parâmetro um certificado digital válido. Veja o post sobre o acesso ao Certificate Store do Windows para mais informações sobre como obter um certificado instalado em seu computador.

[]s

Postar um comentário

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.

Observação: somente um membro deste blog pode postar um comentário.