A assinatura digital tem como função oferecer a garantia da procedência de um documento e se este documento sofreu alguma alteração não autorizada. Com a ocorrência cada vez maior dos crimes digitais e dada a importância dos documentos e arquivos para pessoas e organizações torna-se cada mais importante a sua segurança.
Devido o grande número de pessoas e empresas que utilizam a assinatura digital foram realizadas formas de organizar e padronizar as operações. Com isso a ICP-Brasil realizou toda essa organização e padronização por meio da certificação digital e das suas autoridades certificadoras. Por isso as assinaturas digitais hoje são consideradas bastante seguras.
Portanto, a assinatura digital garante que um documento não foi modificado e realmente procede a identidade de quem diz ter enviado certo documento.
No restante do artigo veremos como funciona a assinatura digital em Java, o que é o algoritmo DSA utilizado para assinatura digital e por fim faremos um exemplo completo em Java demonstrando como implementa-lo na prática.
Como Funciona a Assinatura Digital em Java
Veremos nesta seção como se dá o suporte provido pela API Java para a criptografia e a verificação de dados através de assinaturas digitais.
Todo o suporte necessário para implementarmos a assinatura digital em Java é provido pela API padrão do Java. Dessa forma, não necessitaremos recorrer a bibliotecas externas.
O Java traz dois métodos para os desenvolvedores que querem trabalhar com assinaturas digitais, são eles: os Message Digest e as Assinaturas digitais. O Message Digest são funções hash que geram código de tamanho fixo e unidirecional, ou seja, a partir de dados de um tamanho qualquer teremos como resultado um hash que não poderá mais ser revertido de volta para a mensagem original. Por isso quando estamos trabalhando com hashs deveremos gerar um hash de uma mensagem e comparar com o hash anterior. A API Java implementa dois algoritmos de Message Digest, são eles o MD5 e a família SHA; O outro método é o de Assinaturas Digitais que serve para autenticar o remetente da informação e oferecer a garantia de que o dado é confiável. As assinaturas digitais trabalham com a assinatura em si, uma chave pública e uma chave privada. O remetente é quem deve gerar a assinatura, a chave pública e a chave privada e, além disso, também deve fornecer a chave pública aos destinatários de suas mensagens.
Quando o remetente deseja enviar um dado ele gera uma assinatura usando a chave privada. O destinatário receberá a mensagem e a assinatura e com isso fará a validação da informação usando a chave pública. Se a validação for realizada com sucesso o destinatário tem a garantia que a mensagem foi enviada por um remetente confiável, possuidor da chave privada.
Para utilizar o Message Digest basta obter uma instância do algoritmo a ser utilizado, passar a informação que desejamos criptografar e por fim realizar a criptografia.
Para obter a instância do algoritmo podemos utilizar o código da Listagem 1.
MessageDigest md5 = MessageDigest.getInstance("MD5");
Após isso, podemos utilizar o método update() passando os dados a serem criptografados. Para o algoritmo voltar ao estado original utilizamos o método reset(). Por fim, utilizamos digest() para gerar a chave criptografada.
Uma assinatura possui duas propriedades: a primeira propriedade diz que através da chave pública (correspondente à chave privada usada para geração da assinatura digital) podemos verificar a autenticidade e a integridade dos dados que estão sendo transmitidos; e a outra propriedade diz que a assinatura digital e a chave pública não revelam a chave privada.
A classe Java responsável por gerar as assinaturas digitais é a Signature. Para criar um objeto Signature utilizamos a chamada presente na Listagem 2.
Signature signature = Signature.getInstance("DSA");
No exemplo acima estamos utilizando o algoritmo DSA.
Para gerar as chaves públicas e privadas utilizamos a classe KeyPairGenerator. KeyPairGenerator também necessita de um algoritmo para gerar as chaves. Segue na Listagem 3 um exemplo de como podemos gerar as chaves:
KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");
Tanto as chaves quanto a assinatura devem ser geradas utilizando-se o mesmo algoritmo (no exemplo o DSA). Após obtermos as chaves do KeyPairGenerator devemos inicializa-lo através do método initialize() que recebe um tamanho de chave e um número aleatório através da classe SecureRandom. Segue na Listagem 4 como podemos gerar as chaves.
KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");
SecureRandom secureRan = new SecureRandom();
kpg.initialize(512, secureRan);
KeyPair kp = keyGen.generateKeyPair();
PublicKey pubKey = kp.getPublic();
PrivateKey priKey = kp.getPrivate();
Agora que temos a nossa chave privada podemos utilizá-la no nosso objeto Signature, segue o código da Listagem 5.
signature.initSign(priKey);
Nesse momento já podemos utilizar o método update() para que a informação possa ser assinada ou verificada, como mostra a Listagem 6.
String mensagem = "Exemplo de mensagem";
signature.update(mensagem.getBytes());
E por fim chamamos o método sign() para a geração da assinatura, conforme mostra o exemplo da Listagem 7.
byte[] assinatura = signature.sign();
Com isso, o remetente agora precisa fornecer a chave pública juntamente com o dado a ser enviado e a assinatura correspondente.
O destinatário receberá a assinatura digital e deve ter acesso à chave pública para então validar a assinatura junto com o dado recebido utilizando a chave pública. Segue na Listagem 8 um exemplo de como isso pode ser feito.
Signature signature = Signature.getInstance("DSA");
signature.initVerify(pubKey);
signature.update(mensagem.getBytes());
if (signature.verify(assinatura)) {
//Mensagem assinada corretamente
} else {
//Mensagem não pode ser validada
}
Algoritmo DSA
A assinatura digital traz ao público de segurança de dados algoritmos como o DSA, que foi proposto pelo National Institute of Standards and Technology (NIST) e mais tarde padronizado. O DSA pode ser combinado com um algoritmo de criptografia, como, por exemplo, o RSA, e com alguma das funções hash como o MD5, o MD2, a família SHA, entre outros.
O DSA (Digital Signature Algorithm) é um algoritmo utilizado para assinatura digital, não sendo utilizado para criptografar dados. O algoritmo DSA foi criado no NSA (National Security Agency), a agência de segurança dos Estados Unidos com funções relacionadas a inteligência de sinais incluindo a interceptação e criptoanálise, e é patenteado pelo governo dos Estados Unidos. O NIST, uma agência governamental não regulatória da administração de tecnologia do Departamento de Comércio dos Estados Unidos que tem como missão promover a inovação e competitividade industrial dos Estados Unidos, aprovou o DSA como um padrão de assinatura digital federal.
Antes de verificarmos melhor o DSA devemos saber a relação entre o DSS e o DSA. O Digital Signature Standard (DSS) ou Padrão de Assinatura Digital é baseado em um método de criptografia com chave pública que utiliza o Digital Signature Algorithm (DSA) ou Algoritmo de Assinatura Digital. DSS é o formato das assinaturas digitais apoiado pelo governo dos Estados Unidos. O algoritmo DSA consiste de uma chave privada que somente o criador do documento (assinante) conhece e de uma chave pública.
Inicialmente quando o DSA foi proposto pela primeira vez, a Indústria RSA de Segurança de Dados e as companhias que já haviam autorizado o algoritmo RSA lançaram uma campanha contra o algoritmo DSA. Essa campanha contra o DSA foi por causa de uma possível "porta de armadilha" que poderia permitir ao governo dos Estados Unidos quebrar o DSA. A insatisfação foi com relação a velocidade do algoritmo e o tamanho da chave.
Quando o padrão foi proposto o tamanho da chave era de 512 bits, que é considerado pequeno. O padrão final permite chaves de até 1025 bits, que é mais do que o suficiente para necessidades de segurança.
As matemáticas são muito diferentes do RSA, mas a segurança é semelhante para chaves de tamanhos semelhantes. Por isso, qualquer grande avanço na quebra do RSA também implicará um avanço semelhante na quebra do RSA e vice-versa.
Em termos de velocidade e eficiência entre o DSA e o RSA as publicações realizadas pela Indústria RSA de Segurança de Dados mostram que o RSA é melhor que o DAS. Já as publicações realizadas pelo NIST mostram que o DSA é melhor que o RSA. Em termos gerais utilizando o RSA temos um tempo maior para assinar uma mensagem do que verificar uma assinatura. Com o DSA tanto a operação de assinatura quanto a operação de verificação levam a mesma quantidade de tempo.
O funcionamento do algoritmo não é muito complicado conforme veremos nos próximos passos a serem seguidos.
A primeira parte do algoritmo DSA é a geração da chave pública e da chave privada, conforme descrito abaixo:
- Escolher um número primo q, que é chamado de divisor primo.
- Escolher outro número primo p, tal que p-1 mod q=0. p é chamado de módulo principal.
- Escolher um inteiro g, tal que 1 < g < p, g**q mod p = 1 e g = h**((p–1)/q) mod p.
- Escolher um inteiro, tal que 0 < x < q.
- Computar y como g**x mod p.
- Empacotar a chave pública como {p, q, g, y}.
- Empacotar a chave privada como {p, q, g, x}.
A segunda parte do algoritmo DSA é a geração da assinatura e a verificação da assinatura, que pode serão descritas em dois passos principais.
Para gerar uma assinatura de uma mensagem, o remetente pode seguir estes passos:
- Gerar o Message Digest h, usando um algoritmo de hash como SHA1.
- Gerar um número aleatório k, tal que 0 < k < q.
- Computar r como (g**k mod p) mod q. Se r = 0, selecionar um k diferente.
- Computar i, tal que k*i mod q = 1.
- Computar s = i*(h+r*x) mod q. Se s=0, selecionar um k diferente.
- Empacotar a assinatura digital como {r,s}.
Para verificar uma assinatura de mensagem, o receptor da mensagem e da assinatura digital pode seguir os seguintes passos:
- Gerar o Message Digest h, usando o mesmo algoritmo de hash.
- Computar w, tal que s*w mod q = 1.
- Computar u1 = h*w mod q.
- Computar u2 = r*w mod q.
- Computar v = (((g**u1)*(y**u2)) mod p) mod q.
- Se v==r, a assinatura digital é válida.
Para demonstrar um exemplo simples do funcionamento do DSA, vamos tentar com o menor divisor primo q = 3 e um módulo primo p = 7.
O processo de geração da chave pública e da chave privada é ilustrada abaixo:
- q = 3, onde o divisor primo é selecionado;
- p = 7, módulo primo computado: (p-1) mod q = 0;
- g = 4, computado através de 1 < g < p, g**q mod p = 1 e g = h**((p–1)/q) mod p, assim 4**3 mod 7 = 1: 64 mod 7 = 1;
- x = 5, selecionado: 0 < x < q;
- y = 2, computado: y = g**x mod p = 4**5 mod 7;
- {7,3,4,2}, a chave pública: {p,q,g,y}.
- {7,3,4,5}, a chave privada: {p,q,g,x}.
Com a chave privada {p,q,g,x}={7,3,4,5}, o processo de geração da assinatura digital de uma mensagem de valor de hash h = 3 pode ser ilustrado como:
- h = 3, valor do hash;
- k = 2, selecionado: 0 < k < q;
- r = 2, onde temos que r = (g**k mod p) mod q = (4**2 mod 7) mod 3;
- i = 5, onde temos que k*i mod q = 1: 2*i mod 3 = 1;
- s = 2, onde temos que s = i*(h+r*x) mod q = 5*(3+2*5) mod 3;
- {2,2}, que é a assinatura digital.
O processo de verificação da assinatura digital {r, s} = {2,2} com a chave pública {p, q, g, y} = {7,3,4,2} pode ser ilustrado como:
- h = 3, o valor do hash;
- w = 5, onde temos que s*w mod q = 1: 2*w mod 3 = 1;
- u1 = 0, onde temos que u1 = h*w mod q = 3*5 mod 3 = 0;
- u2 = 1, onde temos que u2 = r*w mod q = 2*5 mod 3 = 1;
- v = 2, onde temos que v = (((g**u1)*(y**u2)) mod p) mod q que é igual a (((4**0)*(2**1)) mod 7) mod 3 = 2;
- v == r, portanto a verificação está Ok.
Dessa forma, o valor de verificação corresponde ao valor esperado.
O autor William Stallings provou através de um teorema que o DSA é legítimo e tem fundamento, portanto ele realmente funciona. Para quem estiver mais interessado sobre o assunto pode visitar o site e verificar como Stallings provou o correto funcionamento do DSA.
Exemplificando a Assinatura Digital em Java
Primeiramente mostraremos abaixo a parte do remetente que é responsável por gerar uma mensagem e enviar a mensagem com uma assinatura digital e a chave pública para o destinatário. Segue na Listagem 9 o código do remetente.
import java.security.InvalidKeyException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Signature;
import java.security.SignatureException;
public class RemetenteAssiDig {
private PublicKey pubKey;
public PublicKey getPubKey() {
return pubKey;
}
public void setPubKey(PublicKey pubKey) {
this.pubKey = pubKey;
}
public byte[] geraAssinatura(String mensagem) throws NoSuchAlgorithmException,
InvalidKeyException, SignatureException {
Signature sig = Signature.getInstance("DSA");
//Geração das chaves públicas e privadas
KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");
SecureRandom secRan = new SecureRandom();
kpg.initialize(512, secRan);
KeyPair keyP = kpg.generateKeyPair();
this.pubKey = keyP.getPublic();
PrivateKey priKey = keyP.getPrivate();
//Inicializando Obj Signature com a Chave Privada
sig.initSign(priKey);
//Gerar assinatura
sig.update(mensagem.getBytes());
byte[] assinatura = sig.sign();
return assinatura;
}
}
Também devemos criar o destinatário que é quem vai receber a mensagem, a assinatura digital e a chave pública para fazer a validação da mensagem recebida. Segue na Listagem 10 o código do destinatário.
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
public class DestinatarioAssiDig {
public void recebeMensagem(PublicKey pubKey, String mensagem, byte[] assinatura) throws
NoSuchAlgorithmException, InvalidKeyException, SignatureException {
Signature clientSig = Signature.getInstance("DSA");
clientSig.initVerify(pubKey);
clientSig.update(mensagem.getBytes());
if (clientSig.verify(assinatura)) {
//Mensagem corretamente assinada
System.out.println("A Mensagem recebida foi assinada corretamente.");
} else {
//Mensagem não pode ser validada
System.out.println("A Mensagem recebida NÃO pode ser validada.");
}
}
}
Agora podemos testar os códigos através de uma classe principal que gera diferentes tipos de mensagens, assinaturas e chaves para validar a mensagem por um destinatário. Segue na Listagem 11 o código.
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.SignatureException;
public class AssinaturaDigital {
public static void main(String args[]) throws NoSuchAlgorithmException,
InvalidKeyException, SignatureException {
//Remetente Gera Assinatura Digital para uma Mensagem
RemetenteAssiDig remetenteAssiDig = new RemetenteAssiDig();
String mensagem = "Exemplo de mensagem.";
byte[] assinatura = remetenteAssiDig.geraAssinatura(mensagem);
//Guarda Chave Pública para ser Enviada ao Destinatário
PublicKey pubKey = remetenteAssiDig.getPubKey();
//Destinatário recebe dados correto
DestinatarioAssiDig destinatarioAssiDig = new DestinatarioAssiDig();
destinatarioAssiDig.recebeMensagem(pubKey, mensagem, assinatura);
//Destinatário recebe mensagem alterada
String msgAlterada = "Exemplo de mensagem alterada.";
destinatarioAssiDig.recebeMensagem(pubKey, msgAlterada, assinatura);
//Criando outra Assinatura
String mensagem2 = "Exemplo de outra mensagem.";
byte[] assinatura2 = remetenteAssiDig.geraAssinatura(mensagem2);
//Guarda Chave Pública para ser Enviada ao Destinatário
PublicKey pubKey2 = remetenteAssiDig.getPubKey();
//Destinatário recebe outra assinatura
destinatarioAssiDig.recebeMensagem(pubKey, mensagem, assinatura2);
//Destinatário recebe outra chave pública
destinatarioAssiDig.recebeMensagem(pubKey2, mensagem, assinatura);
}
}
Para o código acima temos o resultado abaixo como saída:
A Mensagem recebida foi assinada corretamente.
A Mensagem recebida NÃO pode ser validada.
A Mensagem recebida NÃO pode ser validada.
A Mensagem recebida NÃO pode ser validada.
Neste artigo procuramos focar mais no algoritmo DSA que é bastante utilizado na indústria e conforme havia sido solicitado pelos leitores. Nos próximos artigos sobre o assunto falaremos mais sobre o Elliptic Curve Digital Signature Algorithm (ECDSA) e analisamos algumas das suas vantagens e diferenças em relação ao DSA. Aproveito o espaço para informar que estamos abertos a sugestões dos leitores aqui neste espaço ou por e-mail.
- MessageDigest.
- STALLINGS, William. Criptografia e segurança de redes: Princípios e práticas, 4 ed. São Paulo: Prentice Hall, 2008.