Академический Документы
Профессиональный Документы
Культура Документы
Catalão – 2010
Rayner de Melo Pires
Catalão – 2010
Pires, Rayner M.
Número de paginas: 48
Agradeço primeiramente aos meus pais Fábio e Jussara por terem me dado a base
familiar que tenho hoje. Quero ser como vocês quando eu crescer!
Aos meus irmãos Plı́nio e Fábio por me darem exemplo de como ser hoje melhor do
que fui ontem.
À minha namorada Taynara por tanto me apoiar, por me dar tantas provas de amor
e por me transmitir tanta paz, mesmo nos momentos mais difı́ceis.
Agradeço a todos que me acompanharam nessa jornada da graduação, aos meus colegas
de sala de aula e aos bons professores que colaboraram todo esse tempo. Vocês todos
fizeram parte de uma fase muito importante na minha vida, e foram fundamentais para
o meu sucesso alcançado hoje.
Agradeço também à minha amiga Gabriela que comigo caminhou até o final, e ao meu
amigo Salviano que me escutou, me apoiou e me divertiu tanto em todos esses cinco anos.
Vocês dois foram “compadres”de verdade!
E agradeço também ao nosso pai lá do ceú por ter me ajudado a enfrentar os meus me-
dos, por ter me carregado até aqui e ter me dado nesse último semestre motivos para tanta
felicidade.
A todos vocês o meu sincero agradecimento. Vocês estarão sempre em minhas lem-
branças!
Por ter acreditado que posso!
RESUMO
i
Sumário
Introdução 2
1 Estado da Arte 3
1.1 Informação e Segurança da Informação . . . . . . . . . . . . . . . . . . . . 3
1.2 Requisitos para a segurança da informação . . . . . . . . . . . . . . . . . . 3
1.3 Soluções para segurança . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.4 Gestão de Identidade . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.5 Defesa contra ataques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.6 Criptografia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
1.7 A história da criptografia atual . . . . . . . . . . . . . . . . . . . . . . . . 6
1.8 Produtos disponı́veis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
2 Tipos de criptografia 12
2.1 Criptografia Simétrica . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
2.2 Criptografia Assimétrica . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
4 Aplicação da técnica 22
4.1 O software TrueCrypt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
4.2 Aplicação da técnica de DH no TrueCrypt . . . . . . . . . . . . . . . . . . 23
4.3 A senha compartilhada . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
ii
5 O Protótipo 26
5.1 O protótipo desenvolvido . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
5.2 A implementação . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
6 Conclusão 32
Referências 34
Apêndices 36
A Códigos Fonte 36
A.1 A classe GeradorDH . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
A.2 A classe Transferencia . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
B Diagrama UML 47
iii
Lista de Figuras
iv
Lista de Códigos
v
Lista de Abreviações
vi
PKC Public Key Cryptography; Criptografia de Chave Pública
TI Tecnologia da Informação
vii
Introdução
1
sempre ocorrerá transformações de um texto legı́vel em um ilegı́vel.
A criptografia pode ser a ferramenta mais importante de segurança, mas de modo
algum resolverá todos os problemas de segurança.
O objetivo deste trabalho foi estudar técnicas de compartilhamento de informações
criptografadas e oferecer uma solução para o problema de troca de chaves existente na
criptografia simétrica. Através da técnica do Acordo de Chaves de Diffie-Hellman – que
não é uma técnica criptográfica mas que utiliza a criptografia – mostrou-se que o problema
pode ser resolvido, e que tal técnica também pode ser empregada no software criptográfico
chamado TrueCrypt, permitindo o compartilhamento dos arquivos criptografados por ele.
Também foi desenvolvido um protótipo de aplicação que implementa tal técnica e que
fornece uma senha em formato de texto puro, que pode ser utilizado para finalidades
outras além da aplicação no TrueCrypt.
O trabalho descrito neste texto foi dividido em 6 capı́tulos.
O Capı́tulo 1 é o estado da arte. Começa definindo os termos Informação e Segu-
rança da Informação. Aborda os requisitos para se garantir a segurança da informação;
define e fala sobre criptografia e faz um apanhado geral da história da criptografia até os
tempos de hoje, ilustrando o cenário de softwares criptográficos disponı́veis atualmente.
O Capı́tulo 2 está mais focado no assunto Criptografia, e resume os tipos de cripto-
grafia: as criptografias de chave secreta, de chave pública, e a criptografia mista.
O Capı́tulo 3 trata do Problema de Distribuição de Chaves, que surge ao comparti-
lhar informações cifradas com terceiros, e ainda trata de duas tecnologias que atacam o
problema: a técnica do Envelope Digital e a técnica do Acordo de Chaves.
Tendo em mente o Problema de Distribuição de Chaves, é apresentado no Capı́tulo
4 uma maneira de aplicar a técnica de acordo de chaves de Diffie-Hellman no software
TrueCrypt. É explicada também a questão da Senha Compartilhada, utilizada para com-
partilhar um volume do TrueCrypt entre partes geograficamente distantes.
No Capı́tulo 5 é mostrado e explicado o protótipo implementado neste trabalho, e
ainda são comentados alguns trechos da implementação.
Por fim, no Capı́tulo 6 há uma conclusão do trabalho realizado, levantando os obje-
tivos alcançados, pontos positivos, pontos negativos, dificuldades encontradas e trabalhos
futuros. Ainda é possı́vel encontrar, no apêndice desta monografia, o diagrama de classes
e o código fonte comentado das principais classes implementadas.
2
Capı́tulo 1
Estado da Arte
3
• A confidencialidade diz que a informação só está disponı́vel para aqueles devida-
mente autorizados;
4
• Identidade;
• Criptografia.
• Proteção de perı́metro;
• Identificação de vulnerabilidades;
• Backup e Recovery.
5
1.6 Criptografia
A criptografia das informações consiste de mecanismos que garantem a confiden-
cialidade da informação em diversas camadas [Promon, 2005], através da aplicação de
algoritmos de criptografia. Esses mecanismos variam desde a criptografia das informações
gravadas em dispositivos de armazenamento (ex.: discos rı́gidos, pen drives) até cripto-
grafia das informações em trânsito visando à comunicação segura.
A criptografia, como forma de implementação de mecanismos de segurança em sistemas
de informação ou dispositivos de armazenamento, é utilizada como prevenção ou solução
para falhas em segurança na ampla maioria dos casos [Marciano, 2006].
6
Entre 1933 e 1945, a máquina Enigma, que havia sido criada por Arthur Scherbius, não
foi um sucesso comercial e foi aperfeiçoada até se transformar na ferramenta criptográfica
mais importante da Alemanha nazista.
No ano de 1943 a máquina Colossus, projetada pela equipe liderada por Alan Turing e
Gordon Welchman em Betcheley Park, um centro de estudos criptológicos da Inglaterra,
foi posta em ação para quebrar códigos.
Em 1969 James Ellis desenvolve um sistema de chaves públicas e chaves privadas
separadas.
Em 1976 Whitfield Diffie e Martin Hellman publicam o livro New Directions in Crypto-
graphy, introduzindo a idéia de uma criptografia de chave pública. Também reforçaram
a concepção da autenticação através de uma função de via única (one way function).
Terminaram o texto do livro com a observação modesta e sábia:
No mesmo ano a IBM apresentou a cifra Lucifer ao NBS (National Bureau of Stan-
dards). Essa cifra sofreu algumas modificações recomendadas pela NSA (National Security
Agency) e a NBS a adotou como padrão de criptografia de dados nos EUA. Hoje a NBS é
conhecida como NIST (National Insitute of Standards and Technology), e a cifra evoluiu
para DES (Data Encryption Standard ).
Em 1977, inspirados no texto publicado por Diffie e Hellman e apenas principiantes na
criptografia, Ronald L. Rivest, Adi Shamir e Leonard M. Adleman começaram a discutir
como poderiam criar um sistema de chave pública que fosse prático. Ron Rivest concebeu
a grande idéia: o sistema era uma cifra de chave pública, assim como tinham pensado,
que servia tanto para confidencialidade quanto para assinaturas digitais, baseada na difi-
culdade da fatoração de números grandes. Essa cifra foi batizada de RSA, de acordo com
as primeiras letras dos sobrenomes dos autores. Confiantes no sistema, em 4 de Abril de
1970 os três entregaram o texto para Martin Gardner, para que fosse publicado na revista
Scientific American. O artigo apareceu na edição de Setembro de 1977 e incluı́a a oferta
de enviar o relatório técnico completo para qualquer um que mandasse um envelope selado
7
com o próprio endereço. Foram recebidos milhares de pedidos provenientes dos quatro
cantos do mundo.
A NSA contestou a distribuição deste relatório para estrangeiros e, durante algum
tempo, os autores suspenderam a correspondência. Como a NSA não se deu ao trabalho
de informar a base legal desta proibição, solicitada pelos autores, os três voltaram a
enviar os relatórios solicitados. Dois jornais internacionais, “Cryptologia”e “The Journal
of Cryptology”, foram fundados logo após esta tentativa da NSA de censurar publicações.
Rivest, Shamir e Adleman não publicaram a cifra antes de patenteá-la. Aliás, foi uma
grande novidade conseguir patentear um algoritmo.
Em 1978 o algoritmo RSA é publicado na “Communication”da ACM (Association for
Computing Machinery).
A partir da década de 90 começam os trabalhos com computação quântica e cripto-
grafia quântica. Também ganham maior força trabalhos com biometria voltada à auten-
ticação [Almeida, 2009].
Em 1990 Xuejia Lai e James Massey publicam na Suı́ça A Proposal for a New Block
Encryption Standard (Uma Proposta para um Novo Padrão de Encriptação de Bloco),
o que viria a ser o IDEA (International Data Encryption Algorithm), para substituir o
DES [Burnett e Paine, 2002]. O IDEA utiliza uma chave de 128 bits e emprega operações
adequadas para computadores de uso geral, tornando as implementações em software mais
eficientes.
Em 1990 Charles H. Bennett, Gilles Brassard e colaboradores publicam seus resultados
experimentais sobre Criptografia Quântica, a qual usa fótons únicos para transmitir um
fluxo de bits chave para uma posterior cifragem da mensagem (no caso a cifragem Vernam).
Considerando as leis que a mecânica quântica possui, a Criptografia Quântica não só
oferece a possibilidade do segredo como também uma indicação positiva caso ocorra uma
interceptação e uma medida do número máximo de bits que possam ter sido interceptados.
Uma limitação é que a Criptologia Quântica necessita de um cabeamento de fibra ótica
entre as partes que se comunicam.
Um ano depois o FBI impõe o direito de acessar qualquer texto claro trocado entre
usuários que conversam por meio de uma rede de comunicação digital. Como resposta
ao FBI, Phil Zimmermann torna pública, no mesmo ano, sua primeira versão do PGP
(Pretty Good Privacy). O PGP oferece alta segurança para o cidadão comum e, como tal,
é um forte concorrente de produtos comerciais pois além de sua notável confiabilidade,
8
ele é distribuı́do como freeware, e como resultado, tornou um padrão mundial.
Em 1993 a criptoanálise diferencial é desenvolvida por Eli Biham e Adi Shamir.
O método utilizado por Biham e Shamir baseia-se em ataque por mensagens escolhi-
das, as quais são tomadas aos pares e cuja diferença entre os respectivos criptogramas
é analisada. O método pode ser estendido para ataque de mensagens conhecidas. A
criptoanálise é realizada sobre pares de criptogramas cifrados com a mesma chave e cujos
textos em claro correspondentes possuem um certo valor particular de diferença. O efeito
desta diferença é analisado através das n iterações do algoritmo resultando em parâmetros
que permitem inferir possı́veis valores da chave utilizada no processo de criptografia.
Em 1994 o professor Ron Rivest, autor dos algoritmos RC2 e RC4 incluı́dos na biblio-
teca de criptografia BSAFE da RSA Data Security Inc., publica a proposta do algoritmo
RC5 na Internet. Este algoritmo usa rotação dependente de dados como sua operação
não linear e é parametrizado de modo que o usuário possa variar o tamanho do bloco, o
número de estágios e o comprimento da chave. Neste mesmo ano o algoritmo Blowfish,
uma cifra de bloco de 64 bits com uma chave de até 448 bits de comprimento, é projetado
por Bruce Schneier [Moreno et al., 2005].
Em 1997 O PGP Freeware é atualizado para a versão 5.0 e é amplamente distribuı́do
para uso não comercial. Ainda em 97 o padrão de encriptação DES de 56 bits, base da
criptografia dos EUA naquele ano, é quebrado por uma rede de 14.000 computadores.
Em 1998 o padrão de encriptação DES de 56 bits é quebrado em 56 horas por
pesquisadores da Electronic Frontier Foundation - EFF do Vale do Silı́cio. No ano seguinte
a EFF quebrou novamente o DES em apenas 22 horas e 15 minutos, juntamente com a
Distributed.Net, reunindo em torno de 100.000 computadores pessoais à máquina DES
Cracker pela internet.
No ano de 2000 o NIST anunciou um novo padrão de uma chave secreta de cifragem,
escolhido entre 15 candidatos. Esse novo padrão foi criado para substituir o algoritmo
DES, cujo tamanho das chaves tornou-se insuficiente para conter ataques de força bruta. O
algoritmo Rijndael, cujo nome é uma abreviação dos nomes dos autores Rijmen e Daemen,
foi escolhido para se tornar o futuro AES (Advanced Encryption Standard )[Tkotz, 2009].
No século 21 muitos professores e profissionais da computação com vı́nculo em centros
de pesquisa, universidades e empresas começam a pesquisar novas formas de implementar
9
algoritmos e soluções de segurança. Nos anos 2000 a criptografia começa a ser encarada
realmente como uma ciência.
Com esse resumo sobre os principais acontecimentos na história da criptografia pode-
mos ressaltar que a maior motivação para todas essas pesquisas, descobertas e atualizações
na criptografia e criptoanálise foi a questão sobre a segurança da informação, ou da segu-
rança de dados.
• EncryptOnClick;
• Kruptos;
• TrueCrypt.
10
da outra. Isso faz com que a primeira unidade proteja ainda mais os dados da segunda
unidade. Caso a proteção da primeira seja violada, a segunda ainda permanecerá intacta.
O Kruptos acrescenta à funcionalidade do EncryptOnClick a facilidade de criar ar-
quivos executáveis (.exe) autodecifráveis, onde recupera-se o arquivo original apenas
fornecendo a senha correta. Acrescenta ainda um poderoso picotador de arquivos, onde
pode-se apagar um arquivo do disco sem deixar rastros para programas de recuperação
de dados apagados.
Dentre os quatros mencionados, o mais amplamente utilizado é o TrueCrypt. A jus-
tificativa se dá pela sua eficiência, pela facilidade de uso, pela documentação abrangente
que possui, pelas atualizações constantes oferecidas pela fundação responsável pelo seu de-
senvolvimento e também por ser de código aberto (open source), possibilitando alterações.
A Figura 1.1 mostra a tela principal deste software.
11
Capı́tulo 2
Tipos de criptografia
12
2.1 Criptografia Simétrica
O algoritmo de criptografia simétrica ou de chave secreta utiliza uma chave para
transformar ou converter uma informação em um conjunto de bits que parecem estar
dispostos de forma aleatória. Nessa abordagem a mesma chave utilizada para cifrar os
dados é utilizada para decifrá-los. Tanto o remetente quanto o destinatário usam a mesma
chave.
Os algoritmos de chave simétrica podem ser divididos em cifras de fluxo (ou contı́nuas)
e em cifras por bloco. As cifras de fluxo cifram os bits da mensagem um a um, enquanto
que as cifras por bloco pegam um número de bits e cifram como uma única unidade.
Tipicamente são utilizados blocos de 128, 192 ou 256 bits e chaves de 128, 192 ou 256 bits.
Alguns exemplos de algoritmos simétricos populares e bem reputados incluem Twofish,
13
Serpent, AES, Blowfish, CAST5, RC4, 3DES, e IDEA.
O “calcanhar de Aquiles”da criptografia simétrica está na distribuição da chave simétrica.
14
Apesar de existirem duas classes de algoritmos para cifrar informações, elas não são
utilizadas com a mesma freqüência. A justificativa está relacionada ao desempenho: algo-
ritmos de chave pública são lentos, ao passo que a criptografia de chave simétrica pode en-
criptar dados em grande quantidade bem mais rapidamente. Dependendo da plataforma,
alguns algoritmos simétricos podem operar em velocidades de 10MBps, 20MBps, 50MBps
(ou mais). Ao contrário, um algoritmo de chave pública provavelmente opera de 20KBps
a 200KBps por segundo, dependendo do algoritmo, da plataforma e de outros fatores
[Burnett e Paine, 2002].
15
Capı́tulo 3
O Problema da Distribuição de
Chaves
A criptografia de chave simétrica pode manter seguros os nossos dados, mas quando
o compartilhamento desses dados secretos com outras pessoas se faz necessário, então,
também será necessário compartilhar a chave utilizada para encriptá-los. Já que o pro-
blema de envio de informações sigilosas pode ser resolvido com a criptografia, fica agora
o problema de envio das chaves com segurança. Como podemos enviar chaves secretas de
maneira segura para outras pessoas? Esse problema é conhecido como o problema de dis-
tribuição de chaves: como duas ou mais pessoas podem, de maneira segura, compartilhar
chaves por vias inseguras?
Para cada classe de criptografia há uma abordagem diferente para solucionar o pro-
blema da distribuição de chaves. A criptografia de chave pública, por princı́pio de fun-
cionamento, já resolve esse problema com a publicação da chave que criptografa. A cripto-
grafia de chave simétrica faz uso da técnica de Acordo de Chaves para distribuir chaves
de criptografia. Há ainda um método conhecido como Envelope Digital. Essa técnica
faz uso das criptografias de chave pública e chave secreta ao mesmo tempo, método mais
conhecido como criptografia mista.
A Seção 3.2 apresenta o esquema conhecido como Envelope Digital, e a Seção 3.3.1
define a técnica criada por Diffie e Hellman, adotada neste trabalho como solução para o
compartilhamento de dados criptografados pelo software TrueCrypt.
16
3.1 Uso de chaves públicas
A criptografia de chave pública, por seu próprio princı́pio de funcionamento, não
utiliza a mesma chave para os processos de cifragem e decifragem de conteúdos. Com
1
a segurança matemática oferecida pela PKC torna-se possı́vel distribuir as chaves de
criptografia com segurança, visto que o processo de decifragem só pode ser feita pela
chave privada correspondente à chave pública que cifrou o conteúdo.
Dessa maneira, utilizando a chave que é pública para cifrar e a chave que é particular
(privada) para decifrar, pode-se contornar o problema da distribuição de chaves.
17
3.3 O Acordo de Chaves
Outra maneira de se resolver o problema da distribuição de chaves é utilizar o Acordo
de Chaves, mais discutido na literatura como Key Agreement, sua forma no idioma Inglês.
Um Acordo de Chaves propõe que duas ou mais partes possam, literalmente, acordar a
respeito de um segredo em comum, não sendo necessário o armazenamento desse segredo
por nenhuma das partes. Um acordo propõe ainda que ambas as partes possam gerar o
segredo compartilhado apenas utilizando um par de chaves correspondentes 3 .
Sua utilização é ideal em cenários onde se compartilha informação cifrada com cripto-
grafia simétrica.
Uma das técnicas que implementa a técnica do Acordo de Chaves é o conhecido “Diffie-
Hellman Key Agreement”, amplamente conhecido pela sua eficiência.
Como já citado na Seção 1.7, em 1976 Whitfield Diffie e Martin Hellman, os cri-
adores do algoritmo, publicaram a técnica em seu livro New Directions in Cryptography,
nomeando-a com suas iniciais.
Em 2002, Hellman sugeriu que o algoritmo Diffie-Hellman fosse chamado de Algoritmo
de Acordo de Chaves Diffie-Hellman-Merkle, em reconhecimento ao antecessor e funda-
mental trabalho feito por Ralph Merkle na invenção da PKC (Public Key Cryptography -
Criptografia de Chave Pública) [WordIq, 2010], [Stewart, 2000].
O algoritmo DH, como também é chamado, não executa nenhuma técnica de cifragem
de dados. Ao invés disso, ele utiliza a tecnologia de chave pública para gerar uma chave
de sessão simétrica [Ide et al., ]. Na realidade, ele só pode ser usado para estabelecer um
segredo compartilhado, que geralmente é usado como uma chave simétrica compartilhada
[Stamp, 2006].
Duas partes podem gerar o mesmo segredo desde que possuam suas próprias chaves
privadas e a chave pública de sua contraparte, que está, obviamente, publicamente disponı́-
vel. Esse é o acordo de chaves. Se combinarmos um valor privado com o outro valor
público, cada indivı́duo gerará o mesmo valor secreto [Burnett e Paine, 2002], como ilustra
a Figura 3.1.
3
O Acordo de Chaves faz uso da PKI (Public Key Infrastructure).
18
Figura 3.1: O funcionamento do algoritmo de Diffie-Hellman.
No envelope digital, uma chave é gerada para cifrar o conteúdo, e depois essa chave é
enviada, dessa vez cifrada, juntamente com o conteúdo.
Com o DH não se faz mais necessário a cifragem de dados utilizando a criptografia
assimétrica. Isto é ideal nos casos onde se tem limitações técnicas ou computacionais e só
está disponı́vel ou só é possı́vel utilizar a criptografia simétrica.
Com essa técnica basta utilizar o valor secreto resultante dos cálculos de DH como
sendo a chave de sessão. Para decifrar, o destinatário precisa apenas gerar o mesmo valor
secreto.
Vantagens da utilização desta técnica:
• Nenhuma chave é enviada e ainda sim pode-se ter um acordo de chaves eficaz e
eficiente;
19
• Solucionamos o problema motivador deste trabalho: o problema da distribuição de
chaves na criptografia simétrica.
2 Alice envia Ya = αXa mod q para Bob e Bob envia Yb = αXb mod q para Alice;
3 A chave secreta K = (Yb )(Xa ) mod q de Alice e a chave secreta K = (Ya )(Xb ) mod q
de Bob são iguais.
Uma pessoa que esteja monitorando o canal de comunicação entre Alice e Bob con-
seguiria obter as chaves públicas geradas, contudo não conseguiria gerar a chave secreta
K, visto que para gerá-la é necessário também uma das chaves privadas Xa ou Xb . Com
a chave secreta K Alice e Bob podem cifrar seus conteúdos e ter confidencialidade nas
suas correspondências.
20
A Figura 3.2 ilustra os valores das chaves secretas K. Com base nela, pode-se concluir
a igualdade αXb (Xa )
mod q = αXa (Xb )
mod q.
Para que os pares de chaves (Xa , Ya ) e (Xb , Yb ) sejam compatı́veis e possam manter um
acordo entre si, eles também precisam compartilhar os mesmos parâmetros α e q. Para
que isso seja garantido, é necessário que ambos os pares tenham sido gerado pela mesma
entidade, e que esta entidade seja de confiança, visto que um ataque “man in the middle”
poderia ocorrer [Burnett e Paine, 2002].
A responsabilidade da entidade geradora dos pares de chaves é garantir que os parâmetros
sejam idênticos para ambos os pares de chaves, e que estes pares sejam entregues corre-
tamente para seus devidos portadores.
Estabelecido o acordo e intercambiadas as chaves públicas, os parceiros participantes
desse acordo já estão prontos para gerar o segredo, cifrar os dados e, então, compartilhá-
los.
21
Capı́tulo 4
Aplicação da técnica
22
cifragem e decifragem do conteúdo armazenado nos volumes criptografados aconteçam em
tempo de execução, isto é, ao mesmo tempo em que os dados são lidos ou escritos pelo
sistema operacional do usuário. Esse recurso é possı́vel através do uso das tecnologias de
pipelining e parallelization. Esse recurso torna transparente, e muito mais simples para o
usuário, o processo de cifragem e decifragem dos dados em um volume.
O TrueCrypt hoje, na sua versão mais atual 7.0, trabalha somente com algoritmos de
encriptação de chave simétrica, não oferecendo os algoritmos de chave assimétrica. Os
algoritmos disponı́veis são: AES, Serpent, Twofish. Para aumentar ainda mais o nı́vel
da complexidade da criptografia aplicada aos seus volumes, o TrueCrypt ainda utiliza de
técnicas de iteração de algoritmos, ou aplicação de algoritmos em cascata, como descrito na
própria documentação do software [TrueCrypt-Foundation, 2009]. Dessa maneira pode-
se utilizar combinações de algoritmos para criptografar os dados, acrescentando à lista
de algoritmos inicial os seguintes: AES-Twofish, AES-Twofish-Serpent, Serpent-AES,
Serpent-Twofish-AES, Twofish-Serpent. Essa aplicação de um algoritmo sobre o outro é
uma tentativa de aumentar a dificuldade de interpretação dos bits do volume, suprindo a
falta de algoritmos assimétricos, mais sofisticados.
Este trabalho propõe a integração entre a técnica do Acordo de Chaves de Diffie-
Hellman e o TrueCrypt, a fim de permitir o compartilhamento do volume criptografado
por esse software entre várias pessoas. Isso é útil quando o acesso ao volume criptografado
precisa ser feito por duas ou mais pessoas, que podem ou não estar separadas geografica-
mente.
23
reforçar o mecanismo de criptografia do volume. Segundo [TrueCrypt-Foundation, 2009],
é recomendável que se use pelo menos uma keyfile, e que esta seja de pelo menos 64bytes.
O próprio TrueCrypt oferece um recurso onde pode ser gerado um arquivo keyfile com
bits aleatórios. Todavia, este recurso não é útil no cenário que propomos – um cenário
onde existe a necessidade de compartilhamento desse volume criado –, visto que o segundo
usuário desse volume criptografado também necessitaria do mesmo arquivo original uti-
lizado como keyfile. Isto se faz necessário pois o software necessita sempre do mesmo
arquivo original. Uma cópia do arquivo original não funciona como keyfile válida.
Uma outra opção disponı́vel é a senha. No TrueCrypt a utilização de senha é obri-
gatória, como mostra a Figura 4.1. Tal obrigatoriedade existe pois o software uti-
liza o método PBKDF2 (Password-Based Key Derivation Function) de criptografia. O
PBKDF2 está especificado na PKCS #5 v2.0 (Public Key Cryptography Standard )
[Laboratories, 2005], e é utilizado no TrueCrypt para gerar as Header Keys, que pro-
tegem as Master Keys, que, por suas vezes, são a chave que cifram os volumes. A PKCS
#5 v2.0 é um Padrão de Criptografia de Chave Pública Baseado em Senha e, conforme
está disposto em [Laboratories, 2005], o método PBKDF2 é uma função de derivação de
chaves utilizada para derivar uma chave de criptografia a partir de uma senha, de um
valor pseudo-aleatório – comumente conhecido como salt – e de um contador de iterações.
Header Keys e Master Key são termos utilizados pelo TrueCrypt para referenciar as chaves
utilizadas por esse software para cifrar os volumes.
É neste ponto que se insere este trabalho. Para que dois parceiros possam compartilhar
e utilizar o mesmo volume é necessário que haja um consenso entre as partes com relação
à senha de proteção do volume. É necessário que a senha seja acordada entre as partes,
e não que ela seja repassada de um lado para outro, colocando em risco a segurança das
informações no volume. Então, trata-se do acordo de chaves! Neste caso, melhor dizendo,
um acordo de senha.
24
4.3 A senha compartilhada
Pensando nisso é que desenvolvemos uma aplicação para gerar essa senha. Se pre-
cisamos de uma senha eficiente para acessar o volume criptografado e não podemos enviá-
la junto com esse volume, então todos os participantes do acordo podem utilizar o algo-
ritmo Diffie-Hellman para gerá-la. Basta que sejam obedecidos os requisitos do algoritmo:
estar de posse da sua chave privada, da chave pública de sua contraparte e ambas as chaves
terem sido geradas com os mesmos parâmetros – o número primo q e α a raiz primitiva
de q, como citado na seção 3.3.1.
É importante enfatizar que no TrueCrypt a senha não funciona como a chave de
criptografia, mas é uma parte do conjunto de todos os itens que compõem o segredo que
cifra os volumes. Caso essa parte esteja ausente (ou seja incorreta), bem como outras
partes (a keyfile, a semente, o hash, as header keys e a master key), os volumes não
podem ser abertos, mantendo, assim, o sigilo dos dados.
O assistente de criação de volumes do TrueCrypt informa que uma senha ideal contém
pelo menos 20 caracteres, e, em [TrueCrypt-Foundation, 2009], consta que a melhor segu-
rança pode ser atingida utilizando-se o tamanho máximo do campo senha: 64 caracteres
alfanuméricos.
25
Capı́tulo 5
O Protótipo
26
parceiro.
Vejamos um exemplo desse processo:
(1) Bob gera seu par de chaves DH, e envia a chave pública para Alice;
(2) Alice, com a chave pública de Bob, gera seu próprio par de chaves DH. Logo
após, envia sua chave pública para Bob;
Como podemos perceber em (2), uma DH Public Key contém informações suficientes
para que um novo par de chaves DH seja criado a partir dela.
O passo (1) é executado no painel ilustrado pela Figura 5.1 abaixo.
Figura 5.1: Painel onde é gerado um par de chaves DH com novos parâmetros.
27
O passo (2) é executado no painel do aplicativo, como ilustrado abaixo pela Figura 5.2.
Figura 5.2: Painel onde é gerado um par de chaves DH, com os mesmos parâmetros da
chave pública fornecida.
As chaves geradas com a aplicação tem 1024 bits de comprimento, e são fornecidas a
partir do provedor SunJCE. As chaves de DH manipuladas e geradas neste protótipo não
necessariamente precisam de uma extensão, visto que este conceito é variável para cada
28
sistema operacional. Entretanto, optamos por definir que as chaves públicas carregam a
extensão .pbk, e as chaves privadas levam a extensão .pvk, para facilitar a identificação
e a localização desses arquivos pelo usuário.
Com o acordo feito a partir dessas chaves, o segredo gerado é um vetor de bytes, que
também terá 1024 bits, ou 128 bytes de comprimento.
Após ser gerado, o segredo é convertido em um vetor de 256 caracteres hexadecimais.
Desse novo vetor de tamanho 256, foram adotados como senha os 64 caracteres do centro.
Melhorando a compreensão, a senha será um subvetor do vetor de 256 posições, que
conterá somente 64 caracteres dos 256 gerados. Esses 64 caracteres estão na região central
do vetor maior, ou seja, foi adotado que são os caracteres da posição 96 até a posição 160.
Depois de gerada a senha, para utilizá-la basta copiá-la para o clipboard e colá-la no
TrueCrypt. A cópia é confirmada com um aviso na tela, como ilustra a Figura 5.5.
Figura 5.5: Cópia da senha gerada para a área de transferência do sistema operacional.
29
Figura 5.6: Inserção de senha para acessar volume do TrueCrypt.
5.2 A implementação
A implementação do programa foi feita em linguagem Java versão 6, utilizando um
computador com Processador AM Dr Turion 64X2, com clock de 1.6GHz por núcleo
e 3GB de memória RAM. Foi utilizado o IDE Netbeans, versão 6.8, para auxiliar a
codificação do protótipo.
Como foi sugerido utilizar os resultados dos cálculos – a senha – no TrueCrypt, que tem
versões para as plataformas Windows, Linux e Mac, então o protótipo foi implementado
em linguagem Java, tornando-o também compatı́vel com estas plataformas.
Foi criada uma classe que implementa os métodos que manipulam o acordo, chamada
GeradorDH.java. Seu código fonte está anexado no Apêndice A.1.
Essa classe pode ser instanciada com um ou sem nenhum argumento. Um argumento
válido deve ser do tipo DHParameterSpec. Se ela for inicializada com nenhum argumento,
então isto indica que um novo conjunto de parâmetros de par de chaves DH deve ser
preparado pelo Provider. Caso um DHParameterSpec seja fornecido como argumento,
30
então isto indica que os parâmetros para gerar um novo par já foram definidos anteri-
ormente, e o novo par a ser gerado deve utilizar estes mesmos parâmetros. Este caso é
aplicado no momento onde um novo par de chaves deve ser criado a partir de uma chave
pública fornecida.
Uma outra função também implementada é a que copia a senha gerada para a área
de transferência do sistema operacional. O intúito aqui foi agilizar o processo de cópia
da senha da aplicação para o TrueCrypt, visto que o protótipo não armazena esta senha
em nenhum arquivo. O botão que implementa esta ação está no painel onde é gerada a
senha (ilustrado pela Figura 5.3).
A classe que copia a senha para a área de transferência do sistema operacional (clip-
board ) é a Transferencia.java. Seu código fonte está anexado no Apêndice A.2.
Também foram implementadas as classes AplicacaoApp.java e AplicacaoView.java
que oferecem a interface gráfica com o usuário. Para implementar este aplicativo como
uma Java Desktop Application, foi utilizada a biblioteca org.jdesktop.application do
Java 6.
1
O Javadoc da aplicação e todos os códigos fonte Java implementados estão contidos
no CD que acompanha este trabalho.
O diagrama de classes UML que auxilia o entendimento da arquitetura desta aplicação
pode ser visto no Apêndice B.1.
1
Documentação de um código fonte Java.
31
Capı́tulo 6
Conclusão
32
dos pares de chaves por entidades externas, eliminando (opcionalmente) a necessidade
de implementar o módulo que gera os pares de chaves. Sugere-se também tornar este
aplicativo um módulo do TrueCrypt, para integrar ainda mais esta solução ao software.
33
Referências
Almeida, G. B. d. C. (2009). Autenticação Segura Baseada em Biometria Voltada para a
Dinâmica da Digitação. Monografia, Universidade Federal de Goiás.
34
Promon (2005). Segurança da informação, um diferencial determinante na com-
petitividade das corporações. Revista, Promon. Online. Disponı́vel em:
http://www.promon.com.br/portugues/noticias/download/Seguranca 4Web.pdf.
Tkotz, V. (2009). História da criptologia. Página na internet, Aldeia Numa Boa, disponı́vel
em: http://www.numaboa.com/criptografia/. Acessada em Junho de 2009.
35
Apêndice A
Códigos Fonte
1 package aplicacao;
2
36
24 import javax.crypto.spec.DHParameterSpec;
25
26 /∗∗
27 ∗
28 ∗ @author Rayner Pires
29 ∗/
30 public class GeradorDH {
31
35 /∗∗
36 ∗ Chama ele próprio, passando como argumento <code>null</code>.
37 ∗/
38 public GeradorDH() {
39 this ( null ) ;
40 }
41
42 /∗∗
43 ∗ Cria um novo objeto para gerar dados sobre um acordo de chaves de DH.
44 ∗ É inicializado com os parâmetros que, provavelmente, vieram de uma outra
45 ∗ chave. Se {@code parametros} não for nulo, então a variável global
46 ∗ {@code paramSpecs} é inicializada.
47 ∗
48 ∗ @param parametros Os parâmetros de inicialização do acordo. Se
49 ∗ {@code parametros} for <i>null</i>, então novos parâmetros são
50 ∗ criados através do método <code>getParametrosDH</code>.
51 ∗ @see GeradorDH#getParametrosDH
52 ∗/
53 public GeradorDH(DHParameterSpec parametros) {
54 if (parametros == null) {
55 this .paramSpecs = getParametrosDH(1024, this.provedor);
56 } else {
57 this .paramSpecs = parametros;
58 }
59 }
60
61 /∗∗
62 ∗ Exporta um par de chaves para um diretório no disco.
37
63 ∗
64 ∗ @param parChaves
65 ∗ @param diretorio
66 ∗ @return {@code true} apenas se o par for salvo com sucesso;
67 ∗ {@code false} caso contrário .
68 ∗/
69 public static boolean exportaPar(KeyPair parChaves, File diretorio) {
70 ObjectOutputStream saida;
71 try {
72 // exporta pública
73 diretorio = new File(diretorio .getPath() + File.separator
74 + "chave publica.pbk");
75 diretorio .createNewFile();
76 saida = new ObjectOutputStream(new FileOutputStream(diretorio));
77 saida.writeObject(parChaves.getPublic());
78 saida. flush () ;
79 saida. close () ;
80
81 //exporta privada
82 diretorio = new File(diretorio .getParent() + File.separator
83 + "chave privada.pvk");
84 diretorio .createNewFile();
85 saida = new ObjectOutputStream(new FileOutputStream(diretorio));
86 saida.writeObject(parChaves.getPrivate());
87 saida. flush () ;
88 saida. close () ;
89 return true;
90 } catch (FileNotFoundException ex) {
91 System.err.println ("Erro: O arquivo " + diretorio.getAbsolutePath()
92 + " n~
ao p^
ode ser encontrado.");
93 } catch (IOException ex) {
94 System.err.println ("Erro ao salvar o par de chaves. I/O error.");
95 }
96 return false ;
97 }
98
99 /∗∗
100 ∗ Importa um par de chaves DH de um arquivo e o retorna.
101 ∗ Opcionalmente pode−se informar strings onde podem ser salvos os caminhos
38
102 ∗ completos de cada chave encontrada.
103 ∗ @param diretorio Onde deve ser feita a busca.
104 ∗ @param caminhoChavePrivada (Opcional) Serve para armazenar o local onde
105 ∗ foi encontrada a chave privada.
106 ∗ @param caminhoChavePublica (Opcional) Serve para armazenar o local onde
107 ∗ foi encontrada a chave pública.
108 ∗ @return O par de chaves encontrado.
109 ∗/
110 public static KeyPair importaPar(File diretorio,
111 String caminhoChavePrivada, String caminhoChavePublica) {
112 File pvk = null, pbk = null;
113 File [] fileList = diretorio . listFiles () ;
114 for ( int i = 0; i < fileList .length; i++) {
115 File tmp = fileList [ i ];
116 if (tmp.isFile () && tmp.canRead()) {
117 if (tmp.getName().endsWith(".pvk")) {
118 pvk = tmp;
119 if (caminhoChavePrivada != null) {
120 caminhoChavePrivada = tmp.getPath();
121 }
122 } else if (tmp.getName().endsWith(".pbk")) {
123 pbk = tmp;
124 if (caminhoChavePublica != null) {
125 caminhoChavePublica = tmp.getPath();
126 }
127 }
128 }
129 if (pvk != null && pbk != null) {
130 break;
131 }
132 }
133 ObjectInputStream ois;
134 KeyPair parChavesDH = null;
135 try {
136 ois = new ObjectInputStream(new FileInputStream(pvk));
137 DHPrivateKey privada = (DHPrivateKey) ois.readObject();
138 ois . close () ;
139
39
141 DHPublicKey publica = (DHPublicKey) ois.readObject();
142 ois . close () ;
143 parChavesDH = new KeyPair(publica, privada);
144 } catch (ClassNotFoundException ex) {
145 System.err.println ("ERRO: O arquivo lido está corrompido ou "
146 + "n~
ao é uma chave privada de DH.");
147 } catch (FileNotFoundException ex) {
148 System.err.println ("ERRO: N~
ao há chave neste diretório.");
149 } catch (IOException ex) {
150 System.err.println ("Erro de I/O!\n"
151 + "\t" + ex.getMessage());
152 }
153 return parChavesDH;
154 }
155
156 /∗∗
157 ∗ Gera um par de chaves com base nos parâmetros contidos na variável global
158 ∗ {@code paramSpecs}.
159 ∗ @return O par de chaves DH gerado.
160 ∗/
161 public KeyPair getNovoParChavesDH() {
162 KeyPair parChaves = null;
163 try {
164 System.out.println("Gerando um par de chaves DH ...");
165 KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("DH");
166 keyPairGen. initialize ( this .paramSpecs);
167 parChaves = keyPairGen.generateKeyPair();
168 } catch (NoSuchAlgorithmException ex) {
169 System.err.println ("ERRO: Algoritmo n~
ao encontrado.");
170 } finally {
171 return parChaves;
172 }
173 }
174
175 /∗∗
176 ∗ Retorna uma string de tamanho 64 a partir do segredo compartilhado gerado
177 ∗ pelo método {@code getEncodedSecret}.
178 ∗ Primeiramente o vetor de bytes retornado pelo método
179 ∗ {@code getEncodedSecret} é convertido em um vetor de caracteres
40
180 ∗ hexadecimais. Por último mantém nesse vetor somente os 64 caracteres
181 ∗ do centro. Desse último vetor de 64 caracteres que é formada a
182 ∗ string que é retornada.
183 ∗
184 ∗ @param dhPrivateKey A chave privada do acordo
185 ∗ @param dhPublicKey A chave pública do acordo
186 ∗ @return uma string de caracteres hexadecimais de tamanho 64.
187 ∗ @throws InvalidParameterSpecException Em caso de as chaves informadas não
188 ∗ serem compatı́veis, ou não fizerem parte do mesmo acordo.
189 ∗/
190 public String getSenha(DHPrivateKey dhPrivateKey,
191 DHPublicKey dhPublicKey) throws InvalidParameterSpecException {
192 byte [] segredo = getEncodedSecret(dhPrivateKey, dhPublicKey);
193 String hexa = toHex(segredo);
194 int comeco = (hexa.length() − 64) / 2;
195 int fim = comeco + 64;
196 hexa = hexa.substring(comeco, fim);
197 return hexa;
198 }
199
200 /∗∗
201 ∗ Gera o segredo compartilhado pelo acordo de DH.
202 ∗ Primeiramente consulta o método {@code comparaParametrosDH}
203 ∗ para verificar se as chaves passadas como argumento são compatı́veis entre
204 ∗ si , ou seja , se foram geradas com os mesmo parâmetros.
205 ∗ Em caso positivo, inicializa o acordo com essas chaves e gera o segredo
206 ∗ compartilhado. Em caso negativo, lança uma
207 ∗ {@code InvalidParameterSpecException}.
208 ∗ @param dhPrivateKey A chave privada de um parceiro do acordo.
209 ∗ @param dhPublicKey A chave pública do outro parceiro do acordo.
210 ∗ @return O segredo compartilhado na forma de um vetor de bytes, cujo
211 ∗ tamanho é 128 (múltiplo de 64).
212 ∗ @throws InvalidParameterSpecException
213 ∗ @see GeradorDH#comparaParametrosDH
214 ∗/
215 private byte [] getEncodedSecret(DHPrivateKey dHPrivateKey,
216 DHPublicKey dHPublicKey) throws InvalidParameterSpecException {
217 byte [] segredo = null;
218 if (comparaParametrosDH(dHPrivateKey, dHPublicKey)) {
41
219 try {
220 KeyAgreement acordo = KeyAgreement.getInstance("DiffieHellman");
221 acordo. init (dHPrivateKey, dHPrivateKey.getParams());
222 acordo.doPhase(dHPublicKey, true);
223 segredo = acordo.generateSecret();
224 } catch (InvalidAlgorithmParameterException ex) {
225 System.err.println ("ERRO: Par^
ametro para algoritmo inválido.");
226 } catch (InvalidKeyException ex) {
227 System.err.println ("ERRO: Chave inválida.");
228 } catch (NoSuchAlgorithmException ex) {
229 System.err.println ("ERRO: Algoritmo n~
ao encontrado.");
230 }
231 } else {
232 throw new InvalidParameterSpecException("Os par^
ametros das chaves "
233 + "n~
ao correspondem. "
234 + "N~
ao é possı́vel gerar um acordo Diffie-Hellman.");
235 }
236 return segredo;
237 }
238
239 /∗∗
240 ∗ Compara os parâmetros DH de duas chaves a fim de verificar se as duas
241 ∗ são aptas a realizarem um acordo de chaves de DH corretamente.
242 ∗ @param dhPrivateKey
243 ∗ @param dhPublicKey
244 ∗ @return {@code true} somente se os parâmetros tiverem os mesmos valores;
245 ∗ {@code false} caso contrário .
246 ∗/
247 private boolean comparaParametrosDH(DHPrivateKey dhPrivateKey,
248 DHPublicKey dhPublicKey) {
249 DHParameterSpec param1 = dhPrivateKey.getParams();
250 DHParameterSpec param2 = dhPublicKey.getParams();
251 if (param1.equals(param2)) {
252 return true;
253 } else {
254 if (param1.getL() == param2.getL()) {
255 if (param1.getG().equals(param2.getG())) {
256 if (param1.getP().equals(param2.getP())) {
257 return true;
42
258 } else if (param1.getP().intValue()
259 == param2.getP().intValue()) {
260 return true;
261 }
262 } else if (param1.getG().intValue()
263 == param2.getG().intValue()) {
264 if (param1.getP().equals(param2.getP())) {
265 return true;
266 } else if (param1.getP().intValue()
267 == param2.getP().intValue()) {
268 return true;
269 }
270 }
271 }
272 }
273 return false ;
274 }
275
276 /∗∗
277 ∗ Gera um conjunto de parâmetros de algoritmo para o par de chaves.
278 ∗ Trata−se do número primo <i>p</i> e de sua raiz primitiva <i>α</i>
279 ∗ e de outros valores , que devem ser o mesmo nos dois pares do acordo,
280 ∗ para que o segredo gerado seja o mesmo para ambos os parceiros.
281 ∗
282 ∗ @param tamanho número de bits
283 ∗ @param provedor o provedor que implementa
284 ∗ <code>{@link AlgorithmParameterGeneratorSpi}</code> e que gerará os
285 ∗ parâmetros.
286 ∗ @return Um objeto <code>{@link DHParameterSpec}</code> contendo as
287 ∗ especificações dos parâmetros comuns às chaves de um acordo de
288 ∗ Diffie −Hellman.
289 ∗/
290 private DHParameterSpec getParametrosDH(int tamanho, Provider provedor) {
291 DHParameterSpec especificacoesDH = null;
292 try {
293 if (tamanho < 0) {
294 throw new IllegalArgumentException("O tamanho da chave deve ser"
295 + " maior que zero e múltiplo de 64. É recomendável que"
296 + " seja, pelo menos, 256.");
43
297 }
298 System.out.println(GeradorDH.class.getName()
299 + ": Criando os par^
ametros Diffie-Hellman "
300 + "(pode demorar bastante) ...");
301 AlgorithmParameterGenerator paramGen =
302 AlgorithmParameterGenerator.getInstance("DH", provedor);
303 paramGen.init(tamanho, new SecureRandom());
304 AlgorithmParameters params = paramGen.generateParameters();
305 especificacoesDH =
306 (DHParameterSpec) params.getParameterSpec(DHParameterSpec.class);
307 } catch (InvalidParameterSpecException ex) {
308 System.err.println ("Os par^
ametros das chaves "
309 + "n~
ao correspondem. "
310 + "N~
ao é possı́vel gerar um acordo Diffie-Hellman.");
311 } catch (NoSuchAlgorithmException ex) {
312 System.err.println ("ERRO: Algoritmo n~
ao encontrado.");
313 }
314 return especificacoesDH;
315 }
316
317 /∗∗
318 ∗ Converte o vetor de bytes fornecido em um vetor de caracteres
319 ∗ hexadecimais, representado por uma string.
320 ∗
321 ∗ @param data o vetor de bytes a ser convertido.
322 ∗ @return uma representação hexadecimal do vetor de bytes {@code data}
323 ∗/
324 private String toHex(byte[] data) {
325 String digits = "0123456789abcdef";
326 StringBuilder buf = new StringBuilder();
327 for ( int i = 0; i != data.length; i++) {
328 int v = data[i] & 0xff;
329 Object o = digits.charAt(v >> 4);
330 buf.append(o);
331 buf.append(digits.charAt(v & 0xf));
332 }
333 return buf.toString() ;
334 }
335 }
44
A.2 A classe Transferencia
1 package aplicacao;
2
3 import java.awt.Toolkit;
4 import java.awt.datatransfer.Clipboard;
5 import java.awt.datatransfer.ClipboardOwner;
6 import java.awt.datatransfer.DataFlavor;
7 import java.awt.datatransfer. StringSelection ;
8 import java.awt.datatransfer.Transferable;
9 import java.awt.datatransfer.UnsupportedFlavorException;
10 import java.io .IOException;
11 import java. util . logging .Level;
12 import java. util . logging .Logger;
13
14 /∗∗
15 ∗ Classe que implementa a manipulação de dados com a área de transferência
16 ∗ do sistema.
17 ∗
18 ∗ @author Rayner de Melo Pires
19 ∗/
20 final class Transferencia implements ClipboardOwner {
21
22 @Override
23 public void lostOwnership(Clipboard clipboard, Transferable contents) {
24 System.out.println("O conteúdo da área de transfer^
encia já n~
ao pertence mais à \n"
25 + "aplicaç~
ao de DH.");
26 }
27
45
37 System.out.println("Erro: Senha n~
ao copiada para a área de transfer^
encia.");
38 return false ;
39 }
40
46
Apêndice B
Diagrama UML
47
Apêndice C
48