UNIJUÍ - UNIVERSIDADE REGIONAL DO NOROESTE DO ESTADO DO RIO GRANDE DO SUL DCEEng - DEPARTAMENTO DE CIÊNCIAS EXATAS E ENGENHARIAS CURSO DE GRADUAÇÃO EM CIÊNCIA DA COMPUTAÇÃO COMPLEXIDADE COMPUTACIONAL NO CÁLCULO DE NÚMEROS PRIMOS E PERFEITOS MAURI JOSÉ KLEIN Santa Rosa, RS - Brasil. 2012 UNIJUÍ - UNIVERSIDADE REGIONAL DO NOROESTE DO ESTADO DO RIO GRANDE DO SUL DCEEng - DEPARTAMENTO DE CIÊNCIAS EXATAS E ENGENHARIAS CURSO DE GRADUAÇÃO EM CIÊNCIA DA COMPUTAÇÃO COMPLEXIDADE COMPUTACIONAL NO CÁLCULO DE NÚMEROS PRIMOS E PERFEITOS MAURI JOSÉ KLEIN Trabalho de Conclusão de Curso apresentado ao curso de Ciência da Computação, do Departamento de Ciências Exatas e Engenharias, da Universidade Regional do Noroeste do Estado do Rio Grande do Sul, como requisito parcial para obtenção do grau de Bacharel em Ciência da Computação. Orientador: Professor Doutor GERSON BATTISTI Santa Rosa – RS 2012 COMPLEXIDADE COMPUTACIONAL NO CÁLCULO DE NÚMEROS PRIMOS E PERFEITOS MAURI JOSÉ KLEIN Trabalho de Conclusão de Curso apresentado ao curso de Ciência da Computação, do Departamento de Ciências Exatas e Engenharias, da Universidade Regional do Noroeste do Estado do Rio Grande do Sul, como requisito parcial para obtenção do grau de Bacharel em Ciência da Computação. ______________________________________ Orientador: Prof. (Doutor). GERSON BATTISTI BANCA EXAMINADORA ______________________________________ Prof. (Mestre) VINÍCIUS MARAN Santa Rosa – RS 2012 Os primos são as pérolas que adornam a vastidão infinita do universo de números que os matemáticos exploraram ao longo dos séculos. Eles despertam a admiração dos matemáticos: 2, 3, 5, 7, 11, 13, 17, 19, 23… números eternos que existem em uma espécie de mundo independente de nossa realidade física. São um presente da natureza para o matemático. (MARCUS DU SAUTOY, 2007, p.11). Dedicatória Ao meu pai Arno João Klein, pela inspiração proporcionada para os estudos, principalmente pelo fascínio aos números. Aos meus filhos Keila Caroline e Bruno Daniel e a minha esposa Marlise Inês Seibt, um grande beijo. AGRADECIMENTOS Agradeço ao meu Professor Orientador Gerson Battisti, que foi a primeira pessoa que tive contato na universidade e que desde o início me motivou com os seus desafios. Lembro-me do primeiro dia de aula, no componente “Algoritmos”, o desafio dos “Jesuítas e dos Canibais”. Pois também foi assim que surgiu o interesse pelo assunto do meu TCC, que foi colocado como desafio em uma aula de programação. Obrigado prof. Gerson. Obrigado também a todos os professores que tive durante os 4 anos, que foram todos muito respeitosos e eficientes proporcionando o aprendizado. Especialmente agradeço ao meu filho Bruno Daniel, pela inspiração e pela motivação que me proporcionou ao longo dos quatro anos de curso. A minha filha Keila Caroline, que se fez presente entre nós no decorrer da minha caminhada como universitário. Com ela veio uma nova motivação para que a caminhada fosse concluída com êxito. E a minha esposa Marlise Inês Seibt que por horas assumia todos os compromissos para que pudesse me dedicar aos estudos. RESUMO Fatorar um número de 12 milhões de dígitos ou confirmar sua primalidade não é trivial. Os números primos são conhecidos e estudados há séculos por cientistas e pesquisadores de diversas áreas, que dedicam suas vidas para desvendar alguma particularidade desta classe de números tão singular. Com o advento da computação e a capacidade de processamento das máquinas, os estudos vêm evoluindo com muita rapidez, porém o custo computacional ainda é muito grande, levando-se em consideração, por exemplo, o teste de primalidade de 243112609 - 1. Por esta dificuldade de fatoração, utilizam-se os números primos na criptografia de dados para transmissão pela web, principalmente o Modelo de Criptografia RSA, que consiste basicamente na obtenção de dois números primos enormes como base para criação da chave pública para cifrar os dados. Assim, fica praticamente impossível alguém obter os divisor do número e achar a chave privada para decifrar os dados. Este trabalho visa quantificar e especificar a complexidade destes cálculos, e conhecer mais um pouco destes números especiais. Palavras-Chave: primos; mersenne; perfeitos; criptografia; complexidade; ABSTRACT Factoring a number of 12 million digits or confirm its primality is not trivial. Prime numbers are known and studied for centuries by scientists and researchers from various fields who dedicate their lives to uncover some peculiarity of this class of numbers so unique. With the advent of computing and processing capability of the machines, studies have evolved very quickly, but the computational cost is still too great, taking into account, for example the test of primality 243.112.609 - 1. By factoring this difficulty, we use prime numbers to encrypt data for transmission over the web. The model basically consists RSA Encryption in getting two huge prime numbers as the basis for creating the public key to encrypt the data. Thus, it is virtually impossible for anyone to get the divisor of the number and find the private key to decrypt the data. Quantify and specify the complexity of these calculations is to know a little more of these special numbers. Keywords: prime; Mersenne; perfect; cryptography; complexity; LISTA DE SIGLAS √ ≅ ≡ µs A.C AKS ASCII Cong. FFT Hs. IDE log MDC Min. Mod. O P PE. Pg. PT RSA S WEB Ω Ө - Raíz quadrada; Aproximadamente; Congruência; Milissegundos; Antes de Cristo; Manindra Agrawal, Neeraj Kayal e Nitin Saxena; American Standard Code for Information Interchange Congruência; Transformada Rápida de Fourier; Horas; Integrated Development Environment; Logaritmo; Mínimo Divisor Comum; Minutos; Módulo; Ó Grande; Polinomial; Padre; Página; Pontos; Ronald Rivest, Adi Shamir e Leonard Adleman Segundos; World Wide Web; Ômega; Theta; LISTA DE FIGURAS Figura 1: Pseudocódigo para teste de primalidade; .................................................................. 21 Figura 2: Notação O; g(n) O f(n). ............................................................................................. 23 Figura 3: Notação Ω; g(n) Ω f(n). ............................................................................................ 24 Figura 4: Notação Ө; g(n) Ө f(n). ............................................................................................. 25 Figura 5: Exemplo de Criptografia RSA .................................................................................. 40 Figura 6: Algoritmo Força Bruta em JAVA ............................................................................. 42 Figura 7: Exemplo do Crivo de Erastóstenes até o número 101. ............................................. 44 Figura 8: Algoritmo do crivo de Erastóstenes; ......................................................................... 45 Figura 9: Exemplo do teste de Lucas-Lehmer .......................................................................... 47 Figura 10: Número gerado com p=607: 2607-1. ........................................................................ 47 Figura 11: Algoritmo em Java do teste de Lucas-Lehmer ....................................................... 48 Figura 12: Algoritmo Java do Teorema de Fermat................................................................... 50 Figura 13: Algoritmo para o teste dos números primos de Sophie Germain ........................... 55 Figura 14: Diagrama Conceitual Java ...................................................................................... 57 Figura 15: Congruência Prima mod. 30 x Força Bruta............................................................. 64 Figura 16: Incidência de Falsos Primos em cada intervalo ...................................................... 66 Figura 17: Algoritmo congruência 11, 23, 59 mod. 60. ........................................................... 67 Figura 18: Exemplo de divisibilidade de um número de mersenne. ........................................ 68 Figura 19: Crivo de Erastóstenes x Força Bruta ....................................................................... 70 Figura 20: Lucas-Lehmer X Força Bruta. ................................................................................ 71 Figura 21: Teorema de Fermat x Força Bruta. ......................................................................... 72 LISTA DE QUADROS Quadro 1: Tamanho do Problema x Tempo de Execução .............................................. 18 Quadro 2: Complexidade Melhor Caso para teste de primalidade; ................................ 21 Quadro 3: Complexidade Caso Médio para teste de primalidade .................................. 22 Quadro 4: Complexidade Caso Médio para teste de primalidade .................................. 22 Quadro 5: Probabilidade de incidência de números perfeitos. ....................................... 29 Quadro 6: Lista de números perfeitos............................................................................. 31 Quadro 7: fatores primos de números inteiros positivos ................................................ 33 Quadro 8: Semi-primo e fatores do desafio RSA Laboratories. ..................................... 35 Quadro 9: Exemplos de prova da infinitude dos números primos. ................................ 37 Quadro 10: Tempo de execução do algoritmo de força bruta. ....................................... 43 Quadro 11: Analise do crivo de Erastóstenes ................................................................. 46 Quadro 12: Análise do teste de Lucas-Lehmer .............................................................. 49 Quadro 13: Números primos de Sophie Germain ente 1 e 104. ...................................... 54 Quadro 14: Congruência pelo método binário ............................................................... 61 Quadro 15: Congruência mod. 30 num determinado intervalo de números ................... 63 Quadro 16: Congruência Prima mod. 30 x Força Bruta. ................................................ 64 Quadro 17: Incidência de primos e falsos primos com cong. 11, 23 e 59 mod. 60. ....... 65 Quadro 18: Lista de Falsos Primos com Congruência 11, 23, 59 mod. 60. ................... 66 Quadro 19: Crivo de Erastóstenes x Força Bruta. .......................................................... 70 Quadro 20: Lucas-Lehmer X Força Bruta. ..................................................................... 71 Quadro 21: Teorema de Fermat x Força Bruta. .............................................................. 72 SUMÁRIO INTRODUÇÃO ............................................................................................................ 14 2. COMPLEXIDADE COMPUTACIONAL DE ALGORITMOS .......................... 18 2.1. MEDIDAS DE COMPLEXIDADE .................................................................... 19 2.1.1. Melhor Caso ................................................................................................. 21 2.1.2. Caso Médio................................................................................................... 21 2.1.3. Pior Caso ...................................................................................................... 22 2.2. NOTAÇÕES DE COMPLEXIDADE ................................................................. 23 2.2.1. Notação O ..................................................................................................... 23 2.2.2. Notação Ω ..................................................................................................... 24 2.2.3. Notação Ө ..................................................................................................... 24 3. TEORIA DOS NÚMEROS ..................................................................................... 26 3.1. NÚMEROS PERFEITOS ................................................................................... 26 3.2. NÚMEROS PRIMOS ......................................................................................... 32 3.2.1. Aplicações, Pesquisas e Premiações. .......................................................... 33 3.2.2. Infinitude dos Números Primos ................................................................. 35 3.2.3. Criptografia RSA ........................................................................................ 37 3.3. DERIVAÇÕES DE NÚMEROS PRIMOS ......................................................... 40 3.3.1. Força Bruta .................................................................................................. 41 3.3.2. O Crivo de Erastóstenes ............................................................................. 43 3.3.3. Teste de Lucas – Lehmer ........................................................................... 46 3.3.4. O Teorema de Fermat ................................................................................ 50 3.3.5. Algoritmo AKS ........................................................................................... 51 3.3.6. Primos de Sophie Germain ......................................................................... 52 4. ANÁLISE E AVALIAÇÃO DE ALGORÍTMOS ................................................. 56 4.1. JAVA E IDE NETBEANS .................................................................................. 56 4.1.1 Classe BigInteger .......................................................................................... 57 4.1.2. Principais Métodos Utilizados .................................................................... 58 4.2. MÉTODO BINÁRIO PARA DIVISÃO DE GRANDES NÚMEROS ............... 60 4.3. CONSTATAÇÕES ATRAVÉS DE TESTES OU PROVAS MATEMÁTICAS 61 4.3.1. Congruência prima mod. 30 ....................................................................... 62 4.3.2. Congruência 11, 23 ou 59 mod. 60 ............................................................. 65 4.3.3. Divisores de Números de Mersenne são da Forma 2kp + 1..................... 67 4.3.4. Comparações entre os principais algoritmos: .......................................... 69 5. CONSIDERAÇÕES FINAIS ................................................................................... 73 BIBLIOGRAFIA .......................................................................................................... 76 14 INTRODUÇÃO Enquanto Dario I rei da Pérsia (522 – 486 A.C) contava os dias através de nós em barbantes e outros povos ainda mais antigos controlavam suas informações contábeis representadas em peças de diferentes formatos e armazenadas em urnas de barro (IFRAH, 1947), temos hoje acessibilidade a computadores com grande poder computacional capazes de gerar um número com mais de mil páginas em poucos segundos, especificamente, neste caso, através de uma equação matemática proposta por Marin mersenne para gerar um tipo especial de números – os números de mersenne. Neste contexto podemos dizer que os números primos desafiam a capacidade do ser humano. A singularidade destes números é fascinante. Matemáticos de todas as partes do mundo buscam explicações, novas teorias, conjecturas ou tentam provar algumas já existentes. Este estudo já se estendeu para outras áreas de pesquisa. Cientistas da computação são potencialmente capazes e de fundamental importância para esta pesquisa, devido à complexidade dos cálculos que precisam ser feitos. A hipótese de Riemann contextualizada e exemplificada por Clay Mathematics Institute em seu site foi um passo dado apenas com um pé, e continua sendo um dos principais desafios que os intrigados cientistas e alguns aventureiros tentam provar. Ela consiste, basicamente, na relação dos números primos entre si. Outra classe de números especiais está diretamente ligada com os números primos: os números perfeitos. Os números perfeitos são produto da multiplicação tendo como um dos fatores um número primo gerado pela equação de mersenne. Sabendo-se que um número de mersenne é primo, pode-se obter um número perfeito. O maior número primo, que também é primo de mersenne, encontrado e confirmado até a data deste trabalho possui mais de 12 milhões de dígitos (12.978.189 dígitos), ou mais de 5 mil páginas com letra tamanho 10 PT. Como o número perfeito é gerado pela multiplicação deste número pelo número gerado com o expoente imediatamente inferior, chega-se a um número perfeito de mais de 25 milhões de dígitos (25.956.377 dígitos). Com isto percebemos o grau de complexidade que estes cálculos produzem. Estudar esta complexidade é o objetivo deste trabalho (GIMPS, 2012). 15 Todos estes conceitos podem parecer complexos e matemáticos demais para serem abordados em Ciência da Computação, entretanto, é sabido que a matemática constitui a base de toda computação. Ainda assim podemos questionar a importância de um trabalho que pretende desvendar mais uma fração de um dos problemas mais intrigantes já vistos pela humanidade: os números primos. Em 2002 três cientistas publicaram o trabalho “Primes is in P”, primeiro algoritmo que determina se um número é primo em tempo polinomial. Nos anos seguintes foram feitas melhorias no algoritmo, reduzindo ainda mais o tempo de processamento, porém como a entrada é excessivamente grande, o custo computacional continua elevado, e, sendo assim, ainda não é suficientemente rápido para ameaçar a segurança dos dados da rede mundial de computadores. Como vimos, têm-se motivos suficientes para pesquisar e estudar estas duas classes de números e a sua complexidade computacional e tentar aumentar a eficiência no cálculo destes números, através da implementação de novos algoritmos, utilizando algumas constatações feitas durante o desenvolvimento do trabalho. Além da importância acadêmica e do reconhecimento que este trabalho pode proporcionar, o mesmo ainda envolverá conceitos de segurança eletrônica utilizada na criptografia de dados e a sua relação com os números primos, dando assim aplicação concreta para este trabalho. Sabe-se que a complexidade dos cálculos e a inexistência de uma equação que seja capaz de gerar números primos, foi o motivo pelo qual três cientistas utilizaram os números primos para desenvolver o sistema de criptografia RSA que é utilizado atualmente para transmissão de dados pela WEB (COUTINHO, 2000). Sem a criptografia não seria possível fazer transações bancarias, compras e pagamentos com cartão de crédito, além de coisas mais simples, como acessar sua conta de email. Todavia, existem outros riscos nestas transações que podem nos causar danos, porém não é de interesse deste trabalho estudá-las. Pretende-se, através deste trabalho de conclusão de curso, detalhar todos os atributos e qualidades dos números primos, primos de mersenne e perfeitos; avaliar os resultados já confirmados, prever o tempo para obtenção de novos resultados e 16 eventualmente apresentar um algoritmo capaz de gerar números com estas características. Para tanto, será feito um detalhamento da arquitetura computacional para as quatro operações básicas e o seu respectivo custo computacional, além da estrutura e funcionamento da classe BigInteger da linguagem de programação Java e seus métodos, os quais serão empregados para a resolução dos testes com números grandes, com quantidade de algarismos acima das aceitas por atribuição direta a variáveis comuns, como por exemplo: 2n onde n = 43112609. Ainda será detalhado o uso de números primos para a criptografia de dados relacionando com os resultados obtidos nesta pesquisa, a fim de obter uma dimensão da complexidade empregada na encriptação. E no mesmo contexto apresentar-se-á a prova da infinitude dos números primos, o que significa que, caso não se encontre um algoritmo capaz de fatorar um número grande utilizado na encriptação de dados, sempre haverá disponibilidade de números primos para serem utilizados na Criptografia. Como contribuição acadêmica deste trabalho, serão levados em consideração possíveis derivações ou classes especiais de números que apresentem uma característica em comum, as quais podem ser fortes “candidatas” a números primos ou até podem ser descartadas dos testes por apresentarem características de número composto. Com isso se torna possível uma seleção de números restrita, resultando em algoritmos mais rápidos, inclusive com possibilidade de adaptação em algoritmos de teste de primalidade já existentes. Por fim serão destacados possíveis números primos com uma grande quantidade de algarismos para fazer os testes de primalidade, com o algoritmo mais eficiente encontrado ou construído durante o trabalho de pesquisa e implementação. Portanto, considerando a intensidade das pesquisas que se faz em países europeus, asiáticos e também nos Estados Unidos e uma quase inexistência de interessados no estudo de algo tão singular no Brasil, mostrou-se como uma possibilidade de desenvolver uma linha de pensamento que poderá ser seguida. O trabalho está basicamente dividido em cinco grandes capítulos, os quais são amplamente descritos e com subtítulos intuitivos para melhor compreensão da organização do mesmo. 17 No primeiro capítulo temos a introdução como uma descrição geral do trabalho feito, suas principais características, com os temas abordados e algumas constatações interessantes para aguçar o interesse pelo trabalho. No segundo capítulo será apresentada uma revisão bibliográfica sobre a complexidade computacional de algoritmos, as suas definições, medidas e notações de complexidade, alem da especificação de melhor caso, pior caso e caso médio. No terceiro capítulo teremos toda contextualização da teoria dos números. Serão abordados os números perfeitos como suas características históricas e das novas pesquisas. Os números primos com as suas derivações e aplicações. Serão apresentados alguns algoritmos para testes de primalidade, além da prova da sua infinitude dos números primos. No quarto capítulo será feita uma análise dos algoritmos utilizados e apresentados no trabalho e feita uma avaliação de cada um deles, para apresentar possíveis melhorias através da introdução de alguns cálculos. No quinto e último Capítulo serão feitas as constatações finais, com a apresentação de possíveis linhas de pesquisa que possam ser seguidas em trabalhos futuros. 18 2. COMPLEXIDADE COMPUTACIONAL DE ALGORITMOS Um algoritmo é um procedimento a ser realizado para se obter uma saída satisfatória para qualquer entrada, desde que haja tempo e memória suficientes para tal (TOSCANI E VELOSO, 2002). Cada procedimento é composto de uma regra ou de um conjunto de regras que se comportam de maneira a proporcionar o resultado para o qual o algoritmo foi proposto. Estes conceitos teóricos sobre algoritmos não são obrigatoriamente aceitáveis na prática, já que a solução de um problema pode ser simples do ponto de vista conceitual, e na prática a solução pode não ser alcançável em tempo hábil, devido ao tamanho da entrada. Um algoritmo para o cálculo do determinante de uma matriz, por exemplo, pode variar muito dependendo do método, ou seja, do conjunto de regras utilizadas para obter o resultado. Se utilizado o método de Cramer, podem-se levar séculos para calcular o determinante de uma matriz 20 x 20 (20 linhas e 20 colunas). Já com o método de Gauss o cálculo levará menos de um segundo (TOSCANI E VELOSO, 2002). n Método de Cramer Método de Gauss 2 22 µs 50 µs 3 102 µs 159 µs 4 456 µs 353 µs 5 2,35 ms 666 µs 10 1,19 min. 4,95 ms 20 15225 séculos 38,63 ms 40 5.1033 séculos 0.315 s Quadro 1: Tamanho do Problema x Tempo de Execução Fonte: TOSCANI E VELOSO, 2002; pg. 2. Os números primos também são exemplo de problemas de difícil solução. Com a inexistência de padrão entre os números primos, os mesmos não podem ser representados por equações. Assim o teste de primalidade de números extremamente 19 grandes é feito em tempo polinomial, pelo algoritmo AKS, porém na prática, como a constante, ou seja, o próprio número a ser testado é muito grande, este teste pode levar muitos anos para testar apenas um número dependendo do tamanho do mesmo. Existem vários algoritmos para o teste de primalidade e cada um deles usa um conjunto diferente de regras para chegar ao resultado. Com isto têm-se diferentes desempenhos destes algoritmos, com dispêndio de tempo e memória distintos, alguns com soluções exatas, outros com probabilidades. Constata-se ainda que o mesmo resultado pode ser alcançado com um conjunto de regras diferentes. Pode-se também utilizar o mesmo algoritmo, ou seja, o mesmo conjunto de regras, porém com a implementação diferente de operações. Isto pode proporcionar uma melhora significativa no tempo de execução do algoritmo. Como exemplo disto tem-se o modulo de uma divisão, teste fundamental na primalidade dos números. O módulo é o resto r de uma divisão de um número n por um divisor d. Quando o r for zero, n é um número composto. EXEMPLO: 21023 -1 módulo 2047 é igual a 0: n = 21023 -1 = 898846567431157953864652595394512366808988489471153286367150 405788663379027504815663542386612037680105600569399356966788 293948844072083112464237153197370621888839467124327426381511 098006230470597265414760425028844190753411712314407369565552 704136185816752553422931491199736229692398581524176781648121 12068607 d = 2047 r=0 2.1. MEDIDAS DE COMPLEXIDADE A quantidade de trabalho que um algoritmo requer para apresentar uma solução satisfatória não pode ser representada na forma de um número, ou uma constante, isto por que o número de operações básicas efetuadas pelo algoritmo depende do tamanho da entrada. 20 E mesmo que o tamanho da entrada, ou seja, a quantidade de valores colocados como entrada do algoritmo seja igual, o trabalho do algoritmo pode ser maior ou menor, dependendo de outros aspectos. Por exemplo, para dizer se um número n é primo ou não, podemos ter um custo computacional muito diferente, dependendo do valor de n. Esta variação de complexidade, conforme o tamanho da entrada, pode ser denominada complexidade no pior caso. Já se considerarmos uma solução possível com menos custo, levando em conta a probabilidade de ocorrência de cada entrada de um mesmo valor, a complexidade pode ser definida como complexidade esperada ou média (TOSCANI E VELOSO, 2002). Considerando um algoritmo de força bruta, sem o uso de nenhuma técnica ou função matemática específica para cálculo de números primos e que para um determinado número ser primo ele não pode ter nenhum divisor além de 1 e dele próprio, a complexidade de confirmação de um número como primo sempre é a complexidade do prior caso. Já um número composto vai apresentar características de Melhor Caso e Caso Médio, já que a fatoração do número n se dá antes de serem esgotadas todas as possibilidades. Para termos uma noção mais clara da complexidade computacional para a definição da primalidade de um número primo podemos considerar para cada caso uma entrada n específica e um algoritmo de força bruta que testa todas as possibilidades. Levando em consideração que, caso um número seja composto, ele terá pelo menos um divisor menor que a sua raiz quadrada. Assim, consideram-se esgotadas todas as possibilidades de decomposição de um número, quando testamos todos os valores de 2 até a sua raiz quadrada. 21 Para [i de 2 até √n ] faça: Se (n mod i = 0) então: Retorna COMPOSTO Senão i = i + 1; retorna PRIMO; Figura 1: Pseudocódigo para teste de primalidade; Para efeitos de teste foram utilizados os números 83, 84 e 85 escolhidos por apresentarem as características desejadas para a demonstração dos três casos de complexidade. a) Melhor Caso Para uma entrada n=84; Neste caso o algoritmo somente será executado uma vez, sendo que na divisão de n por 2 já será constatado que se trata de um número composto. Esta é considerada a complexidade de menor custo para o algoritmo em questão; n Operação Operando Resto 84 mod. 2 0 Quadro 2: Complexidade Melhor Caso para teste de primalidade; b) Caso Médio Para uma entrada n=85; Neste caso o algoritmo será executado mais de uma vez, porém menos de 9 vezes (9 é a parte inteira da raiz quadrada de 85). n Operação Operando Resto 85 mod. 2 1 85 mod. 3 1 85 mod. 4 1 22 85 mod. 5 0 Quadro 3: Complexidade Caso Médio para teste de primalidade Percebe-se que na divisão de n por 5 já será constatado que se trata de um número composto. Esta é considerada a complexidade de médio custo para o algoritmo em questão; c) Pior Caso Para uma entrada n=83; Neste caso o algoritmo será executado 9 vezes (9 é a parte inteira da raiz quadrada de 85), ou seja, o máximo de opções a serem testadas, mesmo levando em consideração um n menor que na opção anterior. n Operação Operando Resto 83 mod. 2 1 83 mod. 3 2 83 mod. 4 3 83 mod. 5 3 83 mod. 6 5 83 mod. 7 6 83 mod. 8 3 83 mod. 9 2 Quadro 4: Complexidade Pior Médio para teste de primalidade Percebe-se que na divisão de n por todos os valores possíveis, não é constatada nenhuma divisão exata, o que indica que o n em questão é um número primo e assim sendo, a execução do teste de primalidade é considerado de complexidade de pior caso para o algoritmo em questão; 23 2.2. NOTAÇÕES DE COMPLEXIDADE Para definir o custo computacional de um determinado algoritmo são utilizadas ordens assintóticas, as quais são definidas pelo crescimento da complexidade conforme as entradas. O comportamento assintótico de um algoritmo é muito utilizado pelo fato de tornar possível uma análise da complexidade do mesmo para uma entrada relativamente grande sem ter que executá-lo. Existem várias comparações de complexidade assintótica, mas as mais utilizadas são as definidas pelas notações O (ó grande), Ω (Ômega) e Ө (Theta) (TOSCANI E VELOSO, 2002). Nos cálculos de complexidade, muitas vezes utiliza-se da simplificação, ao passo que avaliar toda a informação de uma função pode ser muito difícil. Assim, no contexto deste trabalho será utilizada a complexidade log n. 2.2.1. Notação O Uma notação O define que o crescimento do custo de uma determinada função é superior à outra função. Considerando as seguintes funções: f(n) = 3n; g(n) = n2; 800 700 600 500 400 f(n) 300 g(n) 200 100 0 1 2 3 4 5 6 Figura 2: Crescimento assintótico O f(n); 24 Neste caso a função f(n) cresce mais rapidamente que a função g(n), por isso pode afirmar que: g(n) O f(n). 2.2.2. Notação Ω Uma notação Ω define que o crescimento do custo de uma determinada função é inferior à outra função. Considerando as seguintes funções: f(n) = 5n; g(n) = 7*n5; 60000 50000 40000 f(n) 30000 g(n) 20000 10000 0 1 2 3 4 5 6 Figura 3: Crescimento assintótico Ω f(n). Neste caso a função g(n) cresce mais rapidamente que a função f(n), por isso pode afirmar que: f(n) Ω g(n). 2.2.3. Notação Ө Uma notação Ө define que o crescimento do custo de uma determinada função é igual à outra função. Considerando as seguintes funções: f(n) = 6*n+4; g(n) = 2*n+2; 25 45 40 35 30 25 f(n) 20 g(n) 15 10 5 0 1 2 3 4 5 6 Figura 4: Crescimento assintótico Ө f(n). Neste caso a função g(n) cresce com a mesma rapidez que a função f(n), por isso pode afirmar que: f(n) Ө g(n). 26 3. TEORIA DOS NÚMEROS Há evidências, em registros dos antigos egípcios, de que eles tiveram conhecimentos sobre número primos. O Papiro egípcio de Ahmes ou de Rhind, de cerca de 1650 A.C, era um papiro onde um escriba de nome Ahmes ensinava as soluções de 85 problemas de aritmética e geometria. Mais tarde, no século XIX, este papiro foi encontrado pelo egiptólogo inglês Rhind. Curiosamente, neste papiro, os números primos foram escritos de forma diferente dos números compostos (IFRAH, 1997). Percebe-se que a história dos números é muito antiga e está contada no livro A História Universal dos Algarismos do Autor Georges Ifrah, e vinha sofrendo alterações e melhorias ao longo dos séculos. A evolução ocorreu lentamente e em épocas permanecia estagnada até o surgimento de uma nova linha de pensamento que trazia novas ideias e teorias. Por volta de 300 A.C. na Grécia Antiga surgiram os primeiros conceitos de números primos. Euclides afirma na definição 11 do Livro VII dos Elementos: “Um número primo é aquele que é medido apenas pela unidade” (EUCLIDES, 1944). Na definição 13 do Livro dos Elementos, Euclides define também os números compostos. Um número composto é aquele que é medido por algum número. Coutinho (2004) traduz estes conceitos para os dias de hoje, e define a expressão ‘medido por’ como ‘divisível por um numero menor do que ele próprio’. Assim podemos definir como primos os números que não são divisíveis por nenhum número menor que ele, exceto o 1. 3.1. NÚMEROS PERFEITOS Os números perfeitos foram caracterizados 300 A.C. por Euclides, que definiu um número como perfeito quando a soma de todos os seus divisores é igual ao próprio número. Por volta do ano 600 o PE. Frances Marin Mersenne juntamente com Pierre de Fermat definiu a classe dos números de mersenne, que são da forma 2n -1. 27 Exemplo 1: Número 6: Divisores: 1 + 2 + 3 = 6; Se aplicarmos a fórmula de mersenne teremos: FM = 2² - 1 = 3; o 2 e o 3 são números primos, portanto: (2² - 1) * (2² - 1) = 3 * 2 = 6. Assim 6 é um número perfeito; Exemplo 2: Número 28: Divisores: 1 + 2 + 4 + 7 + 14 = 28; Se aplicarmos a fórmula de mersenne teremos: FM = 2³ - 1 = 7; o 3 e o 7 são números primos, portanto: ( 2³ - 1 ) * ( 2³ - 1 ) = 7 * 4 = 28. Assim 28 é um número perfeito; Sendo n um número primo e o resultado da equação também for um número primo, podemos gerar um número perfeito, através da multiplicação de ambos. Portanto temos uma relação direta dos números de mersenne com os números perfeitos, que também é objeto de pesquisa deste trabalho. Em artigo publicado em 2010, os pesquisadores chineses: Sibao Zhang, Xiaocheng Ma e Lihang Zhou desenvolveram um estudo sobre a distribuição dos números primos de mersenne, apresentando uma tabela de probabilidade de incidência em um determinado intervalo de números. Nesta tabela, o n é um número sequencial que define a quantidade de números primos de mersenne. Quando o log 2 (n) é igual a n/2, pode-se definir a quantidade de números primos de mersenne para o intervalo de 1 até n, como sendo a diferença entre o n atual e todos os n onde obtivemos a relação de igualdade entre o log 2 (n) e n/2. 28 EXEMPLO 1: Para n=16, temos: Q = (log 2 (n)) – 1 = (log 2 (16)) – 1; Q = 4 – 1 = 3; Q 3 8 R(n) = 22 = 22 = 2 = 256; Log 2 R(n) é igual a n/2 quando: n = 2, 4, 8, 16 (4 valores); número de primos de mersenne = (n – quantidade de valores); número de primos de mersenne = 16 – 4 = 12; Portanto, de 1 até n=16 (para 2R(n) – 1 = 2256 - 1) temos 12 números primos de mersenne. EXEMPLO 2: Para n=32, temos: Q = (log 2 (n)) – 1 = (log 2 (32)) – 1; Q = 5 – 1 = 4; Q 4 16 R(n) = 22 = 22 = 2 = 65536; Log 2 R(n) é igual a n/2 quando: n = 2, 4, 8, 16, 32 (5 valores); número de primos de mersenne = (n – quantidade de valores); número de primos de mersenne = 32 – 5 = 27; Portanto, de 1 até n=32 (para 2R(n) – 1 = 265536 - 1) temos 27 números primos de mersenne. O quadro abaixo apresenta a lista de todos os números primos de mersenne em seus respectivos intervalos, baseados nesta conjectura. 29 n R(n) P, Q log2R(n) n/2 n R(n) P, Q 1 2 P(1) 1.00000 0.50000 27 11213 P(23) 13.45288 13.50000 2 2 Q(0) 1.00000 1.00000 28 19937 P(24) 14.28316 14.00000 3 3 P(2) 1.58496 1.50000 29 21701 P(25) 14.40547 14.50000 4 4 Q(1) 2.00000 2.00000 30 23209 P(26) 14.50240 15.00000 5 5 P(3) 2.32193 2.50000 31 44497 P(27) 15.44142 15.50000 6 7 P(4) 2.80735 3.00000 32 65536 Q(4) 7 13 P(5) 3.70044 3.50000 33 86243 P(28) 16.39612 16.50000 8 16 Q(2) 4.00000 4.00000 34 110503 P(29) 16.75373 17.00000 9 17 P(6) 4.08746 4.50000 35 132049 P(30) 17.01071 17.50000 10 19 P(7) 4.24793 5.00000 36 216091 P(31) 17.72128 18.00000 11 31 P(8) 4.95420 5.50000 37 756839 P(32) 19.52963 18.50000 12 61 P(9) 5.93074 6.00000 38 859433 P(33) 19.71303 19.00000 13 89 P(10) 6.47573 6.50000 39 1257787 P(34) 20.26246 19.50000 14 107 P(11) 6.74147 7.00000 40 1398269 P(35) 20.41521 20.00000 15 127 P(12) 6.89968 7.50000 41 2976221 P(36) 21.50505 20.50000 16 256 Q(3) 8.00000 8.00000 42 3021377 P(37) 21.52677 21.00000 17 521 P(13) 9.02514 8.50000 43 6972593 P(38) 22.73326 21.50000 18 607 P(14) 9.24555 9.00000 44 13466917 P(39) 23.68292 22.00000 19 1279 P(15) 10.32080 9.50000 45 20996011 P(40) 24.32361 22.50000 20 2203 P(16) 11.10525 10.00000 46 24036583 P(41) 24.51873 23.00000 21 2281 P(17) 11.15545 10.50000 47 25964951 P(??) 24.63006 23.50000 22 3217 P(18) 11.65150 11.00000 48 30402457 P(??) 24.85768 24.00000 23 4253 P(19) 12.05427 11.50000 49 32582657 P(??) 24.95760 24.50000 24 4423 P(20) 12.11081 12.00000 50 37156667 P(??) 25.14712 25.00000 25 9689 P(21) 13.24213 12.50000 51 42643801 P(??) 25.34583 25.50000 26 9941 P(22) 13.27918 13.00000 52 43 112 609 P(??) 25.36161 26.00000 (?) Seqüência ainda não provada. Quadro 5: Probabilidade de incidência de números perfeitos. Fonte: (ZHANG, MA, ZHOU, 2010). log2R(n) n/2 16.00000 16.00000 30 Observando o quadro, fica evidente a infinitude dos números primos de mersenne e por consequência os números perfeitos. Porém, considerando que a incidência de números primos de mersenne apresentada no por Sibao Zhang, Xiaocheng Ma e Lihang Zhou é uma conjectura, esta infinitude também não pode ser provada. No entanto, pela conjectura teríamos: Para n=64: Q = (log 2 (n)) – 1 = (log 2 (64)) – 1; Q = 6 – 1 = 5; Q 5 32 R(n) = 22 = 22 = 2 = 4294967296; Log 2 R(n) é igual a n/2 quando: n = 2, 4, 8, 16, 32, 64 (6 valores); número de primos de mersenne = (n – quantidade de valores); número de primos de mersenne = 64 – 6 = 58; 4.294.967.296 Portanto, de 1 até n=64 (para 2R(n) – 1 = 2 - 1) teríamos 58 números primos de mersenne. Atualmente são conhecidos 47 números primos de mersenne e números perfeitos que estão representados no quadro abaixo. 31 Ordem 1 Expoente 2 Díg. Primo Mersenne 1 Díg. Número Perfeito 1 Ano ---- 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 ?? ?? ?? ?? ?? ?? 3 5 7 13 17 19 31 61 89 107 127 521 607 1279 2203 2281 3217 4253 4423 9689 9941 11213 19937 21701 23209 44497 86243 110503 132049 216091 756839 859433 1257787 1398269 2976221 3021377 6972593 13466917 20996011 24036583 25964951 30402457 32582657 37156667 42643801 43112609 1 2 3 4 6 6 10 19 27 33 39 157 183 386 664 687 969 1281 1332 2917 2993 3376 6002 6533 6987 13395 25962 33265 39751 65050 227832 258716 378632 420921 895932 909526 2098960 4053946 6320430 7235733 7816230 9152052 9808358 11185272 12837064 12978189 2 3 4 8 10 12 19 37 54 65 77 314 366 770 1327 1373 1937 2561 2663 5834 5985 6751 12003 13066 13973 26790 51924 66530 79502 130100 455663 517430 757263 841842 1791864 1819050 4197919 8107892 12640858 14471465 15632458 18304103 19616714 22370543 25674127 25956377 ---------1456 1588 1588 1772 1883 1911 1914 1876 1952 1952 1952 1952 1952 1957 1961 1961 1963 1963 1963 1971 1978 1979 1979 1982 1988 1983 1985 1992 1994 1996 1996 1997 1998 1999 2001 2003 2004 2005 2005 2006 2008 2009 2008 (?) Seqüência ainda não provada. Quadro 6: Lista de números perfeitos (GIMPS, 2012) 32 O maior número primo encontrado até hoje tem mais de 12 milhões de dígitos e torna-se obvio que não seria possível armazenar este número em uma variável comum. O fato do maior primo ser um número de mersenne, está diretamente relacionada com o algoritmo de teste de primalidade utilizado para estes números. O teste de primalidade de Lucas-Lehmer é de longe o mais eficiente e com menor custo computacional, mas apenas se aplica aos números de mersenne. Este número “especial” foi encontrado em 2009 por uma rede voluntária através de um sistema de computação distribuída denominada: Great Internet Mersenne Prime Search (GIMPS). Conforme publicou em seu site a Electronic Frontier Foundation, o grupo recebeu o prêmio de $100.000,00 o qual foi distribuído entre vários colaboradores. No Site do GIMPS é possível se cadastrar e participar da pesquisa. Cada membro da rede recebe números para teste de primalidade. Os números são gerados a partir da fórmula de mersenne (2n-1) e caso sejam primos são também geradores de números perfeitos. No mesmo endereço eletrônico o GIMPS também confirma a inexistência de qualquer número primo de mersenne diferente dos que já estão confirmados menores que 224036583. Este número primo de mersenne havia sido descoberto em 2004 e em dezembro de 2011 foi confirmado como 41º número primo de mersenne; A garantia se dá pelo fato do grupo ter testado duas vezes cada número de mersenne menor. Portanto, o “garimpo” continua para valores acima de 224036583. Ainda não é conhecido nenhum número perfeito impar e conjectura-se que eles não existam, porém a prova não é trivial. 3.2. NÚMEROS PRIMOS Uma das definições dos números primos é que todo número inteiro maior que um, e que não seja um número primo, pode ser escrito como um produto de números primos. Desta forma pode-se afirmar que todo inteiro positivo não primo pode ser escrito como um produto de números primos (SAUTOY, 2007). 33 Inteiro positivo Fatores 2 Primo 3 Primo 4 2*2 5 Primo 6 2*3 7 Primo 8 2*2*2 9 3*3 10 2*5 11 Primo 12 2*2*3 13 Primo 14 2*7 15 3*5 Quadro 7: fatores primos de números inteiros positivos É necessário definir um primo como sendo um produto de fator único e assumir que o número 1 é um produto vazio ou sem fator. Cada número inteiro positivo tem uma única representação como produto de números primos e pode também ser identificado unicamente através de seus fatores, ou seja, um inteiro positivo pode ser decomposto de uma única maneira em fatores primos mesmo sem considerarmos a ordem dos fatores. Assim pode-se afirmar que não existem dois números inteiros positivos distintos cujas decomposições em primos sejam equivalentes. Estas afirmações foram provadas pelos antigos gregos. 3.2.1. Aplicações, Pesquisas e Premiações. Já na metade do século XIX, foi feito um grande progresso nas pesquisas relacionadas a teoria dos números. Bernhard Riemann passou a abordar o problema de uma forma completamente nova. Utilizando uma nova perspectiva, começou a compreender parte do padrão responsável pelo caos dos primos. Havia uma harmonia sutil e inesperada 34 escondida sob o ruído externo dos primos. Apesar deste grande salto adiante, a nova música ainda ocultava muitos de seus segredos. Riemann foi audacioso e fez uma previsão ousada sobre a melodia misteriosa que havia descoberto. Essa previsão ficou conhecida como a hipótese de Riemann. Quem conseguir provar que a intuição de Riemann sobre a natureza dessa música estava correta terá explicado por que os primos nos transmitem uma impressão tão convincente de aleatoriedade (SAUTOY, 1965). Riemann desenvolveu sua ideia original após descobrir um espelho matemático através do qual era possível observar os primos. Hoje, o problema proposto em 1859 por Bernhard Riemann faz parte de um seleto grupo de problemas não resolvidos, e para quem solucionar a apresentar a prova da Hipótese de Riemann será pago um prêmio de um milhão de Dólares oferecido em 2000 por Clay Mathematics Institute (CLAY MATHEMATICS INSTITUTE, 2012). Outras pesquisas e as novas descobertas foram feitas com o passar dos anos e com isso, os números primos vieram a ter uma relevante importância para a Ciência da Computação. Alguns algoritmos e estruturas de dados se baseiam nos números primos como é o caso das tabelas Hash (ROBERT, 1990). A partir de 1970, devido ao conceito de criptografia de chave-pública, passaram a formar a base dos primeiros algoritmos de criptografia, como exemplo, podemos citar o algoritmo cryptosystem da RSA (SINGH, 2001). Os números primos passaram a ser tão interessantes que começaram a ser oferecidas premiações para que fizesse alguma nova descoberta. A Eletronic Frontier Foundation (EFF) oferecia US$100,000 para quem encontrasse um primo de 10 milhões de dígitos. Este número primo foi descoberto em 2009 por uma rede voluntária através de um sistema de computação distribuída denominada: Great Internet Mersenne Prime Search, já citada anteriormente (GIMPS, 2012). Atualmente são oferecidos os prêmios de US$150,000 para um número primo com mais de 100 milhões de dígitos e US$250,000 para um primo com mais de um bilhão de dígitos. (EFF, 2012) A RSA Factoring Challenge oferecia até US$200,000 para quem encontrasse os fatores primos de um semi-primo ou pseudo-primo de até 2048 bits. Esse desafio foi encerrado em 2007, e os números semi-primos fatorados podem ser encontrados na 35 página da RSA Factoring Challenge (RSA Laboratories, 2012). Mesmo assim, uma equipe de pesquisa liderada por T. Kleinjung, obteve exito na fatoração do desafio número RSA-768 (768 bits). Enquanto o Desafio Factoring RSA não está mais ativo, o factoring da RSA-768 representa um marco importante para a comunidade. Os fatores foram encontrados em 12 de dezembro de 2009, e foram apresentadaos no artigo Factorization of a 768-bit RSA modulus, de 18 de fevereiro de 2010 (KLEINJUNG, 2010). Fator 1 Fator 2 Número Semi-Primo 33478071698956898 36746043666799590 12301866845301177551304949583849 78604416984821269 42824463379962795 62720772853569595334792197322452 08177047949837137 26322791581643430 15172640050726365751874520219978 68568912431388982 87642676032283815 64693899564749427740638459251925 88379387800228761 73966651127923337 57326303453731548268507917026122 47116525317430877 34171433968102700 14291346167042921431160222124047 37814467999489; 92798736308917; 92747377940806653514195974598569 (116 dígitos). (116 dígitos). 02143413; (232 dígitos). Quadro 8: Semi-primo e fatores do desafio RSA Laboratories. Fonte: (KLEINJUNG, 2010). 3.2.2. Infinitude dos Números Primos Euclides ofereceu uma demonstração em sua obra Os Elementos (Livro IX, Proposição 20) que prova por absurdo que existem infinitos números primos. Esta prova é baseada em alguns conceitos matemáticos. Um desses conceitos e a primalidade entre dois números, ou seja, os “coprimos”. 36 PROVA 1: Coprimos são números que porventura, mesmo sendo compostos, não tenham nenhum divisor em comum (ALENCAR FILHO, 1981). Tendo por exemplo os números 8 e 9: Divisores do número 8: 1, 2, 4, 8; Divisores do número 9: 1, 3, 9; Observa-se no exemplo que MDC de 8 e 9, é o número 1, e portanto os dois números são coprimos. Isto acontece com todos os números sequenciais, ou seja, n e n+1. PROVA 2: Outra prova matemática utilizada para comprovar a primalidade dos números primos é a decomposição de todo e qualquer número por números primos menores, ou seja, qualquer que seja o número ele é composto pelo produto de números primos menores, a não ser que ele próprio seja um número primo. Assim, temos argumentos suficientes para apresentar a prova da infinitude: Suponhamos que os números primos seja finitos e que estão representados na lista L = {2, 3, 5, 7} Multiplicando todos os números da lista, temos: 2*3*5*7 = 210; Pela PROVA 1, sabemos que 211 não tem nenhum divisor em comum com o 210. E pela PROVA 2, sabemos que ele deve ser divisível por qualquer número, caso não seja primo. Assim, conclui-se que, se 211 não é um valor primo, tem-se pelo menos um outro primo além dos que estão na suposta lista “finita” L = {2, 3, 5, 7}, que dividiria o número 211. Caso não haja pelo menos um divisor primo para o número 211, ele próprio é um número primo. Assim, para qualquer lista supostamente finita de números primos, tem-se pelo menos um outro número primo, e portanto, conclui-se que os números primos são infinitos. No quadro podemos observar alguns exemplos desta prova: 37 Lista supostamente finita Produto Coprimo do Fatores do de números Primos dos Fatores produto coprimo 2 2 2+1 = 3 3 2*3 6 6+1 = 7 7 2*3*5 30 30+1 = 31 31 2*3*5*7 210 210+1 = 211 211 2 * 3 * 5 * 7 * 11 2310 2310+1 = 2311 2311 2 * 3 * 5 * 7 * 11 * 13 30030 30030+1 = 30031 59, 509 2 * 3 * 5 * 7 * 11 * 13 * 17 510510 510510+1 = 510511 19, 97, 277 Quadro 9: Exemplos de prova da infinitude dos números primos. Provada a infinitude dos números primos, tem-se a garantida à existência de diferentes primos para a utilização na criptografia de dados, abordada na próxima seção. 3.2.3. Criptografia RSA Encontrar números primos com 100 algarismos parece ser algo inteiramente inútil, embora a maioria das pessoas reconheça que a matemática está envolvida no desenvolvimento de tecnologia de maneira geral, poucos esperam que os números primos possam provocar um grande efeito em suas vidas. Entretanto, os números primos passaram recentemente ao primeiro plano do mundo das pesquisas. Não estão mais confinados apenas aos matemáticos e a alguns aventureiros. Nos anos 1970, os cientistas: Ron Rivest, Adi Shamir e Leonard Adleman revolucionaram a busca por números primos, que deixou de ser uma brincadeira para se tornar uma importante ferramenta de negócios. Explorando uma descoberta feita por Pierre de Fermat no século XVII, os três descobriram um modo de usar os primos para proteger os dados trafegados pela Web através da criptografia denominada RSA, em homenagem aos três autores. Quando a idéia foi lançada, nos anos 1970, ninguém podia imaginar as dimensões que as transações pela WEB alcançariam. Porém, sem a força dos números primos, esse tipo de comércio jamais poderia existir hoje em dia. Sempre que fazemos compras pela internet, nossos computadores utilizam um sistema de segurança que 38 depende da existência de números primos com inúmeras casas decimais (SINGH, 2001). O Livro dos Códigos, pag. 419 (SINGH, 2001), trás um exemplo do funcionamento da criptografia RSA para a transmissão de uma mensagem, no qual podemos observar a cifragem e a decifragem RSA passo a passo: PASSO 1: Escolhe-se dois números primos gigantes p e q. Para o exemplo são escolhidos os números p = 17, e q = 11. PASSO 2: Multiplica-se p x q para encontrar o N. N = 11 x 17 = 187. qq = (p-1)x(q-1) Escolhe-se um primo relativo à qq que pode ser e = 7; Primo relativo à qq é apenas aquele que não possui divisor em comum, ou seja, sendo qq = 160 e possuindo vários divisores (160 possui 8 divisores: 2, 4, 8, 10, 16, 20, 40 e 80) qualquer número que não tenha nenhum destes divisores serve. Na prática se e for também um número primo, ou seja, não possuir divisor algum, ele será com certeza um primo relativo à qq. Assim qualquer número primo serve como e. Atualmente o sistema de criptografia RSA fixa este número para todas as chaves. PASSO 3: Divulgam-se os números e e N; Estes números são a chave pública para a encriptação dos dados e devem estar disponíveis para quem quiser cifrar uma mensagem a ser enviada. PASSO 4: A mensagem a ser enviada deve ser convertida em um número M. O processo de conversão passa pela transformação da mensagem em dígitos binários e depois em decimais, produzindo então o texto cifrado C. C = Me (mod. N); PASSO 5: Para enviar simplesmente a letra X como mensagem. No ASCII isto é representado por 1011000, que equivale a 88 em decimais. Assim, M = 88. PASSO 6: Para cifrar a mensagem, utiliza-se o N = 187, e = 7 e M = 88. Utilizando a fórmula para cifrar, temos: C = 887 (mod. 187). PASSO 7: Efetuando o cálculo da fórmula temos: C = 887 = 40867559636992 = 11 (mod. 187). Envia-se, portanto a mensagem 11. 39 PASSO 8: Para decifrar a mensagem que foi cifrada com o algoritmo de mão única fica praticamente impossível, se levarmos em consideração que são utilizados números primos gigantes e que os desconhecemos. Porém quem gerou a chave pública, possui os fatores do número N, e pode decifrar a mensagem enviada através da chave privada. PASSO 9: A chave privada é calculada a partir da seguinte fórmula. e x d = 1 (mod. qq); 7 x d = 1(mod. 16 x 10); 7 x d = 1(mod. 160); d = 23; Neste passo percebe-se que é necessário sabermos quais foram os números primos utilizados para a geração da chave pública. Assim, se utilizarmos outros valores não chegamos ao resultado esperado, ou seja, não se obtêm a mensagem correta que foi enviada. PASSO 10: Apenas neste momento conseguimos decifrar a mensagem. Com todos os valores obtidos, aplicamos os mesmos na seguinte fórmula: M = cd (mod 187); M = 1123 (mod 187); M = 895430243255237372246531 (mod. 187); M = 88 = X na tabela ASCII. Com este exemplo percebe-se que a função utilizada para encriptar os dados é de mão única, podendo apenas ser revertida por alguém que tenha acesso aos dois números primos que foram utilizados. A função permite, porém, que qualquer um cifre mensagens para qualquer pessoa que divulgue a chave pública, mas apenas tem acesso á mensagem correta, a pessoa que conhece a chave privada. 40 Figura 5: Exemplo de Criptografia RSA (PLACE, 2012) A segurança da criptografia RSA baseia-se na grande dificuldade de fatoração desse produto dos dois números primos gigantes. Mesmo tendo esse produto (que faz parte da chave pública divulgada), a segurança ainda é garantida justamente pela complexidade desta fatoração. Para quantificar esta complexidade, podemos considerar que se um algoritmo de busca de chave através da força bruta (busca exaustiva da chave). Um computador executando um milhão de instruções por segundo durante um ano, levaria 30 mil anos para efetuar a fatoração necessária no pior caso, para uma chave de 512 bits. Uma chave assimétrica de 768 bits demandaria 200 milhões de anos; uma chave assimétrica de 1.024 bits demandaria 300 bilhões; e finalmente, uma chave de 2.048 bits exigiria 300 quinqüilhões de anos para ser quebrada (SINGH, 2001). 3.3. DERIVAÇÕES DE NÚMEROS PRIMOS Como já foi citado na descrição geral das características e na expressão de algumas particularidades dos números primos, obtêm-se através de inúmeras derivações, diferentes classes destes números, cada qual com características especiais. 41 Todos os números têm como característica fundamental para ter a condição de número primo, a divisibilidade apenas pela unidade e por ele próprio. Porém, a partir daí vê-se uma enorme gama de particularidades de cada classe, o que facilmente poderia levar alguém a concluir pela total falta de padrão e relação entre os mesmos. Por isso inúmeros pesquisadores definiram diferentes algoritmos que podem ser aplicados aos números primos, dependendo do resultado que se deseja obter. Neste trabalho serão abordados apenas as principais derivações dos números primos, com suas características, aplicações e algoritmos utilizados na sua determinação, além da complexidade computacional de cada algoritmo. 3.3.1. Força Bruta Um algoritmo de teste de primalidade por força bruta testa todas as possibilidades de divisibilidade de um número até a sua raiz quadrada sem levar em consideração nenhum outro método. Neste trabalho será apresentado um algoritmo de força bruta desconsiderandose apenas os números pares, seguindo o princípio de que o único número primo par é o número 2. Um pseudo código do algoritmo de força bruta pode ser observado a seguir: Para i de 3 até √n faça: Se (n mod. i = 0) então: Retorna composto; Senão: n=n+2; Retorna primo; Para os testes de desempenho e comparação com outros algoritmos foi utilizado o seguinte código JAVA: 42 Figura 6: Algoritmo Força Bruta em JAVA O teste de primalidade por força bruta e um método determinístico, porém sua complexidade é O (2√n), considerando que a complexidade de algoritmos é calculada com base no tamanho da entrada, que neste caso, é a quantidade de dígitos da representação binária do número em questão, e não o próprio número (SAUTOY, 2007). Para o teste de primalidade pelo método de força bruta temos um maior custo computacional se comparado com qualquer outro algoritmo, e o tempo de execução para um determinado intervalo pode ser observado no quadro abaixo. Quantidade de números Tempo de execução primos no intervalo em Milissegundos 0 a 1000000 78498 1690 0 a 2000000 148933 4153 Intervalo 43 0 a 3000000 216816 7577 0 a 4000000 283146 10764 0 a 5000000 348513 14590 0 a 6000000 412849 19035 0 a 7000000 476648 23484 0 a 8000000 539777 28041 0 a 9000000 602489 34213 0 a 10000000 664579 38813 Quadro 10: Tempo de execução do algoritmo de força bruta. 3.3.2. O Crivo de Erastóstenes Por volta de 200 A.C. o grego Erastóstenes criou o “Crivo de Erastóstenes”, o qual consiste basicamente em pegar uma lista de números e pelo método de exclusão retirar os números compostos, até que sobrem somente os números primos. Este algoritmo foi o primeiro algoritmo a ser aplicado para definir os números primos em um determinado intervalo. Dispõe-se os números naturais de 1 a n, onde n é o limite superior até o qual se deseja obter os números primos, procedendo-se da seguinte maneira: - Inicialmente, risca-se o 1, que não é primo. - Em seguida, risca-se todos os múltiplos de 2, exceto o 2 que é primo; - Depois de riscar todos os múltiplos do primeiro primo, passe para o próximo primo, que é o número 3 e risque todos os múltiplos de 3, menos ele próprio. 44 Figura 7: Exemplo do Crivo de Erastóstenes até o número 101. Percebe-se que alguns números são marcados mais de uma vez, pois são múltiplos de mais um primo, como o número 15, múltiplo de 3 e 5. Por isso, é importante saber em que primo p podemos parar, para evitar operações desnecessárias. Para encontrar esse p ideal usa-se o conceito de que, se um divisor d divide um número n, caso ele seja maior que a raiz quadrada de n, logo o seu quociente é menor que a raiz quadrada de n. Assim precisa-se apenas utilizar os números primos menores que a raiz quadrada de n e definir os seus múltiplos. Através desta expressão é possível observar que para n = 50, por exemplo, é possível parar no primo 7, pois √50 ≅ 7, ou seja, para determinar todos os números primos menores que 50 basta riscar os múltiplos de 2, 3, 5 e 7. Um exemplo de código JAVA para o Crivo de Erastóstenes está representado a seguir: 45 Figura 8: Algoritmo do crivo de Erastóstenes; Percebe-se pontos importantes no algoritmo do crivo de Erastóstenes, os quais serão destacados a seguir. O parâmetro recebido pelo método limite indica o limite superior, até o qual serão definidos todos os números primos. No primeiro laço “for” o vetor criado com tamanho igual ao limite superior é preenchido com os valores sequenciais de 1 ao limite. No segundo laço “for” o valor da posição atual do vetor é comparado com o seu valor inicial, e caso o valor permaneça inalterado, significa que é um número primo. Neste momento ele é impresso na console. E ainda dentro da condição temos outra condição a qual limita o trabalho de exclusão dos múltiplos até a raiz quadrada do limite. E dentro desta condição temos o terceiro laço “for”, que zera todos os múltiplos do número primo recém-encontrado, tornando-os todos números compostos. O crivo de Erastóstenes é um algoritmo bem interessante para intervalos de números, porém não é indicado para teste de primalidade de um número específico, com 46 centenas de dígitos, por exemplo, pois a Complexidade de tempo cresce exponencialmente com o número de dígitos. Portanto o Crivo de Erastóstenes tem complexidade exponencial O (2n log n), sendo n o número a ser testado. Abaixo, pode-se observar o tempo de execução com determinados intervalos de números, utilizando o algoritmo proposto neste trabalho. Quantidade de números Tempo de execução Probabilidade de primos no intervalo em Milissegundos ocorrência 0 a 1000000 78498 64 0,0785 0 a 2000000 148933 120 0,0745 0 a 3000000 216816 194 0,0723 0 a 4000000 283146 261 0,0708 0 a 5000000 348513 319 0,0697 0 a 6000000 412849 388 0,0688 0 a 7000000 476648 471 0,0681 0 a 8000000 539777 546 0,0675 0 a 9000000 602489 619 0,0669 0 a 10000000 664579 653 0,0665 Intervalo Quadro 11: Analise do crivo de Erastóstenes Com o Crivo de Erastóstenes fica evidente que, na medida em que os números vão crescendo, a chance de ser um número primo diminui, o que pode ser observado na última coluna do quadro acima. 3.3.3. Teste de Lucas – Lehmer O teste de Lucas-Lehmer é um teste de primalidade determinístico muito eficiente para determinar se um número é primo, porém aplica-se somente aos números primos de mersenne. Uma vez que é conhecido que os números de Mersenne só podem ser primos para subscritos primos, a atenção pode ser limitada a números de Mersenne da forma, em que é um primo ímpar (WOLFRAM MATH WORLD, 2012). 47 Um pseudo código deste algoritmo pode ser visto da seguinte forma (LEHMER, 1981): Teste_LucasLehmer(p): s := 4; PARA i de 3 até p s = s2-2 mod. 2p-1; SE (s = = 0) ENTAO: 2p-1 = PRIMO; SENÃO 2p-1 = COMPOSTO; Como exemplo prático e de fácil compreensão, consideramos p=7: Temos: 2p – 1 = 27-1 = 127; Figura 9: Exemplo do teste de Lucas-Lehmer Percebe-se que temos p - 2 iterações, sendo que p (primo) é a potencia de dois. Com isso, para definir se 2607 – 1 é primo temos apenas 605 iterações, sendo que o número gerado por 2607 – 1 possui 183 dígitos: Figura 10: Número gerado com p=607: 2607-1. 48 Outro fator interessante deste algoritmo é que após cada iteração trabalha-se apenas com o resto da divisão e que possibilita a obtenção do resultado sem que tenhamos números ainda mais colossais. Figura 11: Algoritmo em Java do teste de Lucas-Lehmer Empregando o teste de Lucas-Lehmer implementado em Java para o teste de primalidade dos primeiros 20 números primos de mersenne, pode-se observar claramente o bom desempenho do mesmo, se comparado a algoritmos de força bruta, por exemplo. 49 Quantidade de Tempo de execução em dígitos Milissegundos 2 1 ≅0 2 3 1 ≅0 3 5 2 ≅0 4 7 3 ≅0 5 13 4 ≅0 6 17 6 ≅0 7 19 6 ≅0 8 31 10 2 9 61 19 6 10 89 27 8 11 107 33 9 12 127 39 11 13 521 157 114 14 607 183 178 15 1279 386 1931 (1,9 seg.) 16 2203 664 9717 (9,7 seg.) 17 2281 687 10642 (10,6 seg.) 18 3217 969 29838 (29,8 seg.) 19 4253 1281 69649 (1 min. 9,6 seg.) 20 4423 1332 79166 (1 min. 19,1 seg.) Ordem Expoente 1 Quadro 12: Análise do teste de Lucas-Lehmer Utilizando algumas otimizações como por exemplo a Transformada Rápida de Fourier (FFT), o custo deste algoritmo é de Õ = (p2 log p) (MERSENNE PRIMES, 2012). Este baixo custo em comparação a outros algoritmos de teste de primalidade, juntamente às características dos números de Mersenne são os principais responsáveis pelo fato de que os maiores primos conhecidos atualmente são primos de Mersenne. 50 3.3.4. O Teorema de Fermat O pequeno Teorema de Fermat é um teorema relacionado aos números primos e tem sido utilizado por outros algoritmos de descoberta de primalidade como o Monte-Carlo e o AKS. Este teorema faz uma análise de relação entre um número primo p e um número qualquer a. O teorema consiste de forma resumida em verificar a congruência entre da seguinte equação: a p - a ≡ 0 (mod. p) ou 2 p ≡ 2 (mod. p). Um algoritmo em Java pode ser observado na figura abaixo: Figura 12: Algoritmo Java do Teorema de Fermat. 51 Caso a congruência seja satisfeita o número definido por p = primo1 é um provável primo, já que esta congruência também é satisfeita por números não primos, o que torna este algoritmo probabilístico. Mesmo assim ele é muito utilizado pela sua eficiência para números grandes. A probabilidade de erro do algoritmo, para um número aleatoriamente escolhido de 1024 bits é de apenas uma em 10-41, sendo um dos principais motivos para a adoção deste teste em vários programas é que juntamente ao bom nível de confiança nos seus resultados, este teste possui um tempo de execução polinomial de Õ (k.log 2 n) usando exponenciação modular (SINGH, 1998). Juntamente com outros testes o algoritmo é uma opção interessante. 3.3.5. Algoritmo AKS Em seu artigo PRIMES is in P, Manindra Agrawal, Neeraj Kayal e Nitin Saxena preocuparam a humanidade, supostamente pondo em risco todo e qualquer tipo de criptografia RSA utilizado na transmissão de dados seguros pela internet. Com o teste de primalidade de um número sendo executado em tempo polinomial, facilmente seriam quebradas as chaves de criptografia (AGRAVAL, KAYAL e SAXENA, 2000). Porém com um detalhamento melhor das funções que o algoritmo executa e através de um estudo mais aprofundado deste algoritmo, viu-se que na prática ele não é tão eficiente assim. Mesmo com o seu custo computacional polinomial o algoritmo trabalha com uma constante expressivamente grande, o que o torna lento na prática. A ideia do algoritmo pode ser apresentada de forma resumida em alguns passos conforme o artigo: - Para a verificação da primalidade de um número n: - Decidir se n é uma potência perfeita de um número natural, ou seja, ver se n tem raiz quadrada exata. Caso tenha então n é composto. - Encontrar um fator primo onde este fator primo compreende um intervalo onde certamente haverá um fator primo para n caso n seja composto; Calcular o máximo divisor comum de a e n, para a ≤ r, se for maior do que 1 então n é composto; Neste caso o ‘a’ é incrementado para fatores primos até que seja igual ao intervalo definido no passo anterior. - Se n for menor ou igual a r, n é primo. 52 Por último o AKS verifica a congruência (x – a)n ≡ (xn – a) (mod. xr – 1, n), para todo a que satisfaça a condição da estrutura de controle do laço de repetição, se existir a congruência, n é composto. Caso contrário n primo. Este algoritmo apresenta complexidade computacional Õ (log6 n), na sua primeira versão (AGRAVAL, KAYAL e SAXENA, 2000). Atualmente são encontradas outras versões com melhorias na implementação do algoritmo reduzindo a sua complexidade. 3.3.6. Primos de Sophie Germain Outra classe de números primos são os “primos de Sophie Germain”, que se tornaram conhecidos pelo fato de estar provado que o teorema de Fermat é aplicável para esta classe de números. Um número é caracterizado como primo de Sophie Germain quando p é um número primo e 2 * p + 1 também é primo (SOPHIE GERMAIN PRIMES: BOTH P AND 2P+1 ARE PRIME). Todos os números primos com exceção dos números primos 2, 3 e 5, são congruentes 1, 7, 11, 13, 17, 19, 23, 29 mod. 30, ou seja, qualquer número com resto diferente é divisível por 2 ou 3 ou 5, ou ainda por 2 destes ou até pelos 3. Fazendo uma analise das possibilidades: Resto 1 módulo 30: 31 * 2 + 1 = 63 não é primo (divisível por 3); 61 * 2 + 1 = 123 não é primo (divisível por 3); Resto 7 módulo 30: 37 * 2 + 1 = 75 não é primo (divisível por 5); 67 * 2 + 1 = 135 não é primo (divisível por 5); Resto 13 módulo 30: 43 * 2 + 1 = 87 não é primo (divisível por 3); 73 * 2 + 1 = 147 não é primo (divisível por 3); Resto 17 módulo 30: 47 * 2 + 1 = 95 não é primo (divisível por 5); 107 * 2 + 1 = 205 não é primo (divisível por 5); 53 Resto 19 módulo 30: 79 * 2 + 1 = 159 não é primo (divisível por 3); 109 * 2 + 1 = 219 não é primo (divisível por 3); Resto 11 módulo 30: 11 * 2 + 1 = 23 41 * 2 + 1 = 83 Resto 23 módulo 30: 23 * 2 + 1 = 47 53 * 2 + 1 = 107 Resto 29 módulo 30: 59 * 2 + 1 = 119 89 * 2 + 1 = 179 Observando os exemplos acima podemos concluir que os únicos números capazes de gerar números primos de Sophie Germain são: n ≡ 11 (mod. 30) - (11, 41, 71, 101, 131, ... n); n ≡ 23 (mod. 30) - (23, 53, 83, 113, 143, ... n); n ≡ 29 (mod. 30) - (29, 59, 89, 119, 149, ... n); Obviamente que nem todos os números com resto 11, 23 e 29 geram números primos de Sophie Germain, apenas a prova de que os outros restos NUNCA o farão. No quadro a seguir tem-se a representação dos números primos de Sophie Germain do intervalo de 1 até 104. 54 2 113 359 683 1049 1511 2003 2399 2939 3491 3911 4733 5231 5849 6329 7043 7643 8243 9221 3 131 419 719 1103 1559 2039 2459 2963 3539 4019 4793 5279 5903 6449 7079 7649 8273 9293 5 173 431 743 1223 1583 2063 2543 2969 3593 4073 4871 5303 6053 6491 7103 7691 8513 9371 11 179 443 761 1229 1601 2069 2549 3023 3623 4211 4919 5333 6101 6521 7121 7823 8663 9419 23 191 491 809 1289 1733 2129 2693 3299 3761 4271 4943 5399 6113 6551 7151 7841 8693 9473 29 233 509 911 1409 1811 2141 2699 3329 3779 4349 5003 5441 6131 6563 7193 7883 8741 9479 41 239 593 953 1439 1889 2273 2741 3359 3803 4373 5039 5501 6173 6581 7211 7901 8951 9539 53 251 641 1013 1451 1901 2339 2753 3389 3821 4391 5051 5639 6263 6761 7349 8069 8969 9629 83 281 653 1019 1481 1931 2351 2819 3413 3851 4409 5081 5711 6269 6899 7433 8093 9029 9689 89 293 659 1031 1499 1973 2393 2903 3449 3863 4481 5171 5741 6323 6983 7541 8111 9059 9791 Quadro 13: Números primos de Sophie Germain ente 1 e 10 4. Para definir um número como primo de Sophie Germain, precisa-se fazer o teste de primalidade de dois números. Neste trabalho foi utilizado o seguinte algoritmo em JAVA: 55 Figura 13: Algoritmo para o teste dos números primos de Sophie Germain 56 4. ANÁLISE E AVALIAÇÃO DE ALGORÍTMOS Existem inúmeros algoritmos para teste de primalidade de números em específico, ou para geração de uma lista de números primos em um determinado intervalo. Também existem cálculos aproximados da incidência de números primos. Porém, neste trabalho foram apresentados os cinco principais algoritmos, levando em consideração o contexto histórico e também a aplicabilidade: Crivo de Erastóstenes, Lucas-Lehmer, Teorema de Fermat, AKS e os primos de Sophie Germain. Serão apresentadas algumas constatações a partir das pesquisas e testes realizados durante todo período de estudos. Estas observações poderão porventura fazer parte dos algoritmos existentes, visando à melhora do desempenho dos mesmos. 4.1. JAVA E IDE NETBEANS Para o desenvolvimento deste trabalho, mas precisamente para a construção e codificação dos algoritmos, foi utilizado a linguagem de programação Java e o Ambiente de Desenvolvimento Integrado (IDE) NetBeans 7.2, desde a fase de desenvolvimento dos algoritmos geradores dos números primos e perfeitos, até os testes de primalidade e os algoritmos para a análise dos dados obtidos. A linguagem Java foi utilizada por possuir suporte a números muito grandes através da abstração BigInteger e por ser a linguagem utilizada pela universidade em seu currículo acadêmico. Na figura abaixo observamos o diagrama conceitual da linguagem JAVA, contendo o pacote java.math e todos os métodos e funções matemáticas necessárias para o desenvolvimento deste trabalho, inclusos na Classe BigInteger. 57 Figura 14: Diagrama Conceitual Java Fonte: http://docs.oracle.com/javase/7/docs 4.1.1 Classe BigInteger Todas as operações da Classe BigIntegers recebem tratamento como primitivos de Java do tipo inteiros. BigInteger, portanto, fornece suporte a todos os operadores primitivos Java de número inteiro, e todos os métodos de java.lang.Math. Além disso, BigInteger fornece operações de aritmética modular, testes de primalidade (probabilísticos), manipulação de bits, e algumas outras operações diversas. A semântica de operações aritméticas desta classe é exatamente igual a dos operadores inteiros Java aritméticas, como definido na especificação de linguagem Java. Por exemplo, uma divisão por zero gera uma “ArithmeticException” e divisão de um negativo por um positivo resulta um resto (ou zero) negativo. Os “BigIntegers” ocupam o espaço necessário para acomodar os resultados de uma operação. Semântica de operações lógicas bit a bit exatamente iguais aos dos operadores de Java bit a bit inteiros. Os operadores binários (e, ou, xor) implicitamente realizam a extensão de sinal no mais curto dos dois operandos antes de realizar a operação. 58 Realizar operações de comparação conforme comparações inteiras, análogas às realizadas por Java relacional e operadores de igualdade. Operações aritméticas modulares são fornecidos para calcular resíduos, executar exponenciação, e calcular inversos multiplicativos. Estes métodos sempre retornam um resultado não negativo. Operações de bits operam sobre um único bit da representação de complemento de seus operandos. Se necessário, o operando é sinal estendido para que ele conténha o bit designado. Nenhuma das operações de um único bit pode produzir um BigInteger com um sinal diferente do BigInteger em questão. Como eles afetam apenas um único bit, o infinito tamanho da palavra fornece abstração para esta classe, a qual garante que existem infinitos bits que precedem cada BigInteger. Todos os métodos e construtores desta classe apresentam como saída a expressão: “NullPointerException” quando passa-se uma referência de objeto nula para qualquer parâmetro de entrada (JAVA SE DOCUMENTATION, 2012) . 4.1.2. Principais Métodos Utilizados Além das operações aritméticas básicas, como: adição, subtração, multiplicação e divisão; serão utilizadas algumas operações adicionais, as quais estão detalhadas a seguir. Porém, todos os métodos estão baseados e caracterizados pela soma e subtração, que são as operações possíveis computacionalmente, não considerando a forma como as quais serão feitas pelo Java. a) Soma de dois valores public BigInteger add (BigInteger val) Parâmetro: Valor ao qual o atual valor BigInteger será acrescido. Retorno: BigInteger somado ao valor do parâmetro (this + val). 59 b) Subtração de dois valores public BigInteger subtract (BigInteger val) Parâmetro: Valor ao qual o atual valor BigInteger será subraído. Retorno: BigInteger subtraído do valor do parâmetro (this - val). c) Multiplicação de dois valores public BigInteger multiply (BigInteger val) Parâmetro: Valor ao qual o atual valor BigInteger será multiplicado. Retorno: BigInteger multiplicado ao valor do parâmetro (this * val). d) Divisão de dois valores public BigInteger divide (BigInteger val) Parâmetro: Valor pelo qual o atual valor BigInteger será divido. Retorno: Quociente da divisão do atual BigInteger pelo valor do parâmetro (this / val). Exceção: Val igual a 0. e) Exponenciação public BigInteger pow (int exponent) Parâmetro: Expoente ao qual a base BigInteger está elevada. Retorno: BigInteger elevado ao expoente (this exponent ). Exceção: Expoente negativo. 60 f) Módulo de dois valores public BigInteger remainder (BigInteger val) Parâmetro: Valor pelo qual o atual valor BigInteger será divido Retorno: Resto da divisão do atual BigInteger pelo valor do parâmetro (this / val). Exceção: Expoente negativo. 4.2. MÉTODO BINÁRIO PARA DIVISÃO DE GRANDES NÚMEROS Para a divisão de números expressivamente grandes, com grande quantidade de dígitos, formados a partir da exponenciação de base dois, foi utilizado o método de divisão apresentado a seguir descrito por ATKIN e BERNSTEIN em 2004. Por exemplo, para chegar-se ao resto da divisão de 2p por p*2+1, temos: 1º PASSO: Transformar o p para binário; 2º PASSO: Considerar resto inicial igual a 1; 3º PASSO: Remover o primeiro bit de p. 4º PASSO: Caso o bit removido seja “1”, multiplica-se o valor do resto2 por 2. Caso o bit removido seja “0” mantêm o valor do resto2. 5º PASSO: Efetua-se a congruência do resto2 por p*2+1, atribuindo este valor ao resto; Este procedimento deve ser efetuado até que não tenha mais bits a serem removidos, isto é, a quantidade de bits define a quantidade de iterações. Caso a congruência seja 1, após a realização do “loop”, o número em questão é um número COMPOSTO, pois ele tem divisão exata. 61 Considerando p=23 para 2p - 1: resto2 Bits Restantes Remover bit Multiplicação por 2 Mod. 47 1 x 1=1 * 10111 1 1x2=2 2 2 x 2=4 0111 0 - 4 4 x 4=16 111 1 16 x 2 = 32 32 32 x 32=1024 11 1 1024 x 2 = 2048 27 27 x 27=729 1 1 729 x 2 = 1458 1 Obs.: Inicia-se com resto 1. Quadro 14: Congruência pelo método binário Este método é bastante útil para o teste de primalidade através do teorema de Fermat e também para encontrar pequenos fatores dos números de mersenne. Se observarmos no quadro acima, podemos ver que temos números de tamanho reduzido, ou seja, como é efetuada a congruência após cada iteração, podemos concluir que este número jamais será maior que ((p*2)2)*2. Ao passo que uma exponenciação simples geraria um valor com grandes dimensões. Considerando o exemplo acima, com p = 23: Valor máximo possível pelo método Binário: 4232. Valor máximo pela exponenciação simples: 8388608; Esta diferença se torna ainda mais visível considerando p = 607: Valor máximo possível pelo método Binário: 2937888. Valor máximo pela exponenciação simples: 5311379928167670986895882065524686273295931177270319231994441382004035 5986085224273916250226522928566888932948624650101534657933765270723940 9519978766587351943831270835393219031728127(183 dígitos); 4.3. CONSTATAÇÕES ATRAVÉS DE TESTES OU PROVAS MATEMÁTICAS Os números primos apresentam inúmeras particularidades e por este motivo não existem algoritmos capazes de determinar se um número expressivamente grande é primo ou não em um tempo razoavelmente baixo, ou seja, não tem como calcular o próximo 62 número primo através de uma equação ou fórmula, mas sim, temos que testar cada número pra comprovar sua primalidade. A algoritmo “crivo de Erastóstenes” apresenta um resultado satisfatório se considerada a intenção de apresentar todos os números primos em um determinado intervalo de valores. Este algoritmo, pela sua forma de efetuar a seleção dos primos, não apresenta possibilidade de melhora. Isto é, ele puramente faz as multiplicações e elimina os produtos, e apresenta os valores que sobraram, ou seja, que não possuem divisores, e portanto, são primos. O algoritmo de Lucas-Lehmer é um teste determinístico muito eficiente, mas apenas aplicável aos números de mersenne. Os maiores números primos conhecidos são primos de mersenne justamente pelo fato de utilizar este algoritmo para o teste de primalidade. Porém, como já foi descrito neste trabalho, o maior número primo possui mais de 12 milhões de dígitos (GIMPS, 2012), e mesmo com o uso do algoritmo de Lucas-Lehmer, o teste de primalidade leva muito tempo. O único algoritmo determinístico e polinomial, que funciona para o teste de primalidade de qualquer número, é o AKS. Porém, como já foi dito anteriormente, na prática ele é muito lento, devido ao valor de entrada que é muito grande. O Teorema de Fermat, como já evidencia o nome, era apenas um teorema e que já foi comprovado que o teste de primalidade por este método não é garantido, ou seja, nem todos os números de Fermat são primos, e portanto, é um algoritmo probabilístico. Outra derivação dos números primos apresentada no trabalho, são os primos de Sophie Germain, que propriamente não é um teste de primalidade, mas sim uma definição de uma classe específica de números primos. Os números primos de Sophie Germain apresentam uma característica interessante que foi utilizada para apresentar algumas constatações que serão apresentadas no próximo subcapítulo. 4.3.1. Congruência prima mod. 30 Um teste básico que pode ser feito no início de cada algoritmo apresentado, é verificar a congruência do número a ser testado pelo produto dos 3 primeiros números primos. Isto se torna possível, pois uma congruência diferente, sempre será divisível por 2, 3 ou 5, já que o próximo número primo é o 7 o primeiro número não primo por sua causa é o 49 que é maior que 30. 63 Podemos tomar como referência os três primeiros números primos: 2, 3 e 5. O produto destes números é igual a: 2 x 3 x 5 = 30. Desta forma para que um número seja um possível número primo ele deve apresentar congruência 1, 7, 11, 13, 17, 19, 23, 29 mod. 30. Ou seja, a congruência deve ser um número primo, do contrário pode-se retornar “composto” para o número em questão. N 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 mod. 30 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 Possível Primo Composto x x x x x x x x x x x x x x Primo x x Quadro 15: Congruência mod. 30 num determinado intervalo de números 64 Portanto percebemos que num intervalo de 30 valores, apenas 8 precisam ser testados, e destes 8 apenas 3 são realmente primos. Esta ocorrência de primos diminui na medida em que o tamanho dos números vai aumentando. Se compararmos o tempo de execução de dois algoritmos de força bruta, e introduzirmos este teste de congruência em um deles pode-se observar um ganho considerável no teste de primalidade para um determinado intervalo. Para o teste foram considerados intervalos de 30 números impares consecutivos a partir do valor de n. n Congruência Prima mod. 30 (segundos) Força Bruta (segundos) Diferença (segundos) Custo em Relação a Força Bruta (%) 1010 1011 1012 1013 1014 0,110 1,139 4,939 16,05 52,112 0,094 2,028 8,738 40,01 98,167 -0,016 0,889 3,799 23,96 46,055 117,0212766 56,16370809 56,52323186 40,11497126 53,08504895 Quadro 16: Congruência Prima mod. 30 x Força Bruta. O gráfico apresenta uma visualização da eficiência deste algoritmo de força bruta com a inclusão da congruência prima mod. 30. Ao passo que os números vão aumentando a diferença entre os mesmos se torna cada vez mais visível. Comparação de Desempenho Congruência Prima mod 30 Força Bruta Tempo em Segundos 200 150 100 50 0 10 11 12 13 Potência de Base 10 (Intervalo de 30 valores) Figura 15: Congruência Prima mod. 30 x Força Bruta. 14 65 4.3.2. Congruência 11, 23 ou 59 mod. 60 Caso um número tenha congruência 11, 23 ou 59 mod. 60, ele pode ser testado pelo algoritmo do teorema de Fermat, e apresentará uma grande possibilidade de ser um número primo, caso apresente as características desejadas, isto porque, são estas congruências que também satisfazem as características apresentadas para os primos de Sophie Germain. O teste consiste basicamente em: n = número a ser testado; SE n ≡ primo (mod. 30) então: SE (2(n-1)/2 – 1 ≡ 0 (mod. n)) então: Retorna PROVÁVEL PRIMO; SENÃO Retorna COMPOSTO; Este teste é bastante eficiente, porém é um teste probabilístico. No quadro abaixo consta a incidência de primos e falsos primos em um determinado intervalo proposto. Intervalo 0 a 1000000 0 a 2000000 0 a 3000000 0 a 4000000 0 a 5000000 0 a 6000000 0 a 7000000 0 a 8000000 0 a 9000000 0 a 10000000 Quant. total Quant. de Primos Incidência Falsos Incidência de primos 11, 23, 59 mod. 60 (%) Primos (%) 78498 148933 216816 283146 348513 412849 476648 539777 602489 664579 14762 18,81 0 0 27948 18,77 2 0,007156 40727 18,78 3 0,007366 53108 18,76 3 0,005649 65349 18,75 4 0,006121 77461 18,76 5 0,006455 89403 18,76 5 0,005593 101292 18,77 5 0,004936 113026 18,76 5 0,004424 124682 18,76 5 0,00401 Quadro 17: Incidência de primos e falsos primos com congruência 11, 23 e 59 mod. 60. O quadro abaixo apresenta todos os falsos primos menores que 108 (100.000.000), com os seus respectivos divisores que os tornam números compostos. 66 Congruência mod. 60 23 11 23 11 11 23 59 11 59 23 11 11 23 23 23 Falso Primo 1325843 1441091 2748023 4469471 5016191 10610063 11081459 13338371 15139199 17134043 27271151 27380831 27808463 31436123 79786523 Divisor 499 347 479 1831 647 2351 227 3163 2383 1097 503 1511 4567 1619 2579 Quadro 18: Lista de Falsos Primos com Congruência 11, 23, 59 mod. 60. Pode-se claramente observar na medida em que os números vão aumentando de proporção, a probabilidade do teste falhar é cada vez menor. Na figura abaixo é apresentada esta constatação. Falsos Primos 90 a 100 80 a 90 Intervalo ( x 106) 70 a 80 60 a 70 50 a 60 40 a 50 Falsos Primos 30 a 40 20 a 30 10 a 20 0 a 10 0 1 2 3 4 5 Quantidade de Falsos Primos Figura 16: Incidência de Falsos Primos em cada intervalo 67 O algoritmo utilizado para efetuar os testes de primalidade está representado abaixo. No mesmo foram efetuadas as congruências por 60 e posteriormente o cálculo para verificação da primalidade utilizando o método binário para obtenção da congruência. Figura 17: Algoritmo congruência 11, 23, 59 mod. 60. 4.3.3. Divisores de Números de Mersenne são da Forma 2kp + 1 Uma propriedade muito interessante dos números de Mersenne é que qualquer fator q de 2p-1 deve ser da forma 2kp + 1. Além disto, caso 2kp+1 seja um número primo, ele sempre divide 2p-1(GIMPS, 2012). 68 Para se fazer o teste de primalidade de um número de mersene utiliza-se o algoritmo de Lucas-Lehmer. Porém para números expressivamente grandes este algorítmo também tem seu custo computacional e pode levar dias, meses ou anos para testar um único número. Por esta razão pode-se incluir um teste básico no início do algoritmo de LucasLehmer, fazendo com que se deixe de testar inúmeros expoentes. Considerando o expoente p=23, tem-se; SUGESTÃO DE TESTE INICIAL: SE (p*2+1 = primo) ENTÃO 223-1 É COMPOSTO; SENÃO TESTE DE LUCAS-LEHMER; O teste de primalidade de (p*2+1) deve ser feito por qualquer método determinístico, já que o segundo teste, o teste de Lucas-Lehmer será feito para todos os números compostos. Logo, um falso primo poderia determinar um número como sendo COMPOSTO, mesmo sendo um número PRIMO. Com isto pode-se, por exemplo, afirmar que: Figura 18: Exemplo de divisibilidade de um número de mersenne. O exemplo acima gera um número extremamente grande, ou seja, o expoente p = (2(43112609-1)-1)/2 possui mais de 12 milhões de dígitos, e a 243112609-1 é o primeiro divisor exato deste número, isto é, gera congruência 0, considerando que o divisor é um número primo(GIMPS, 2012). 69 4.3.4. Comparações entre os principais algoritmos: Comparar o tempo de execução entre os algoritmos apresentados neste trabalho é uma forma de quantificar e de evidenciar a evolução que vem ocorrendo com as pesquisas e novas intervenções nos mesmos. Temos algoritmos distintos que se aplicam a alguma classe de números primos e, portanto, não podem ser comparados com outros que não apresentam esta característica. Desta forma serão apresentadas algumas comparações entre algoritmos que possuam características em comum. Para todos os testes realizados neste trabalho, foi utilizado um computador com as seguintes configurações: Intel Core 2 Duo T5550 (1.83 Ghz, 667 Mhz FSB, 2 MB L2 Cache), 2 GB DDR2. 70 a) Crivo de Erastóstenes x Força Bruta: Intervalo Crivo de Força Bruta Diferença Erastóstenes (milissegundos) (milissegundos) (milissegundos) Custo em Relação à Força Bruta (%) 1690 1626 3,786982 4153 4033 2,889477 7577 7383 2,56038 10764 10503 2,424749 14590 14271 2,186429 19035 18647 2,03835 23484 23013 2,005621 28041 27495 1,947149 34213 33594 1,809254 38813 38160 1,682426 0 a 1000000 0 a 2000000 0 a 3000000 0 a 4000000 0 a 5000000 0 a 6000000 0 a 7000000 0 a 8000000 0 a 9000000 0 a 10000000 64 120 194 261 319 388 471 546 619 653 Quadro 19: Crivo de Erastóstenes x Força Bruta. Comparação de Desempenho Tempo em milissegundos FORÇA BRUTA CRIVO DE ERATÓSTENES 45000 40000 35000 30000 25000 20000 15000 10000 5000 0 1 2 3 4 5 6 7 intervalo de números ( x 1000000 ) Figura 19: Crivo de Erastóstenes x Força Bruta 8 9 10 71 b) Lucas-Lehmer x Força Bruta: n para (2n - 1) Lucas-Lehmer (milissegundos) Força Bruta (milissegundos) 2 3 5 7 13 17 19 31 61 89 1 1 1 1 1 1 1 2 6 8 1 1 1 1 1 1 1 40 796799 13716714000* Diferença Custo em Relação (milissegundos) à Força Bruta (%) 0 0 0 0 0 0 0 38 796994 13716713992 100 100 100 100 100 100 100 5 0,000752823 0,0000000583 * Estimativa de tempo de execução (158,75 dias); Quadro 20: Lucas-Lehmer X Força Bruta. Comparação de Desempenho LUCAS-LEHMER FORÇA BRUTA 45 Tempo em milissegundos 40 35 30 25 20 15 10 5 0 2 3 5 7 13 17 Expoente OBS.: Expoentes 61 e 89 não estão no gráfico, para melhor visualização; Figura 20: Lucas-Lehmer X Força Bruta. 19 31 72 c) Teorema de Fermat x Força Bruta: n Teorema de Fermat (milissegundos) 1014 2 3 5 9 14 10 15 1016 1017 3162 16869 50234 195602 537574 Custo em Relação à Força Bruta (%) 3160 0,063251107 16866 0,017784101 50229 0,009953418 195593 0,00460118 537560 0,002604293 Quadro 21: Teorema de Fermat x Força Bruta. Comparação de Desempenho Teorema de Fermat Força Bruta 600000 500000 Tempo em Milissegundos 10 18 Força Bruta Diferença (milissegundos) (milissegundos) 400000 300000 200000 100000 0 14 15 16 Potência de Base 10 Figura 21: Teorema de Fermat x Força Bruta. 17 18 73 5. CONSIDERAÇÕES FINAIS Descrever a singularidade dos números primos parece pouco diante da importância que representam para os matemáticos em relação à teoria dos números e para a segurança computacional em relação à criptografia de dados. Porém, precisamos conhecê-los para entendê-los, e estudá-los para conseguir saborear a sua peculiaridade. Integrando uma seleta série de sete problemas denominados como desafios do milênio, anunciados no dia 24 de maio de 2000, a hipótese de Riemann simboliza e confirma esta importância. A busca para uma explicação e a prova de uma fórmula para os números primos já dura dois mil anos e os matemáticos estão aflitos para encontrar a resposta desse enigma, que parece resistir a qualquer tentativa de encaixá-los em um padrão reconhecível. Pode-se imaginar a euforia que uma descoberta ocasionaria. Para quem aprecia números, gosta de matemática e sabe utilizar o poder de processamento dos computadores atuais, os números primos se tornam um ótimo ramo de pesquisa. Além disto, existem inúmeras premiações - citadas neste trabalho – para entusiasmar ainda mais os pesquisadores. Contudo, a complexidade do problema faz com que muitos pesquisadores desistam sem ter ao menos conhecido toda a sua extraordinária beleza, e trabalhos como este são escassos e geralmente são feitos sobre uma classe de números primos em específico. Neste trabalho, porém, abordou-se os números primos de uma forma mais ampla, relatando suas principais aplicações práticas e de pesquisa, com comparações entre os principais algoritmos estudados, inclusive com uma abordagem ampla dos números perfeitos. Os números perfeitos possuem uma relação estreita com os números primos de mersenne, e o seu detalhamento se deu por este fato, além da sua magnitude e da sua representatividade no mundo das pesquisas. Calculados pelo algoritmo de LucasLehmer, são objeto de pesquisa de inúmeros matemáticos, intrigados com as suas particularidades. O teste de primalidade de Lucas-Lehmer é o mais eficiente de todos os algoritmos determinísticos existentes, porém só se aplica aos números de mersenne, 74 porém utilizando algumas técnicas é possível definir um número como primo em um número reduzido de iterações. Já o algoritmo para o teste de primalidade pelo teorema de Fermat se aplica a todos os números e é mais eficiente que o teste de Lucas-Lehmer, porém é probabilístico. A frequência com que define um número como sendo primo mesmo que seja composto é bastante baixa, o que justifica a sua utilização para encontrar números primos grandes, especialmente para a utilização nos algoritmos de criptografia de dados. O algoritmo AKS tem complexidade computacional polinomial, é determinístico, aplica-se a todos os números, mas na prática ele se torna lento pela utilização de uma constante muito elevada. Este algoritmo é de difícil implementação e não foi feita a comparação de desempenho neste trabalho. Outra classe de números primos detalhada neste trabalho são os primos de Sophie Germain, que foram utilizados nos testes desenvolvidos com mod 60. Sua complexidade não foi avaliada, pois não se trata de um teste de primalidade, apenas é uma derivação dos números primos. O Crivo de Erastóstenes foi detalhado por ser o primeiro algoritmo utilizado para a definição de uma série de números primos em um intervalo de valores. A sua importância é apenas histórica e a utilização deste algoritmo para teste de primalidade de um número, não é viável. O algoritmo de força bruta é o mais utilizado didaticamente e o de mais fácil compreensão e entendimento. A sua prova é evidente e a sua aplicabilidade é para todos os valores. Levando em conta estes fatores, o algoritmo de força bruta serviu de base de comparação de desempenho entre os algoritmos. Com as comparações, percebeu-se que, na medida em que os números crescem, o ganho no desempenho dos algoritmos específicos em relação ao de força bruta fica cada vez mais evidente, sendo que a utilização do mesmo é inviável para teste de números de mais de 12 milhões de dígitos, por exemplo. Algumas constatações importantes foram feitas e detalhadas no decorrer deste trabalho, e ficam como sugestão de adaptação para os algoritmos existentes: Para os números de mersenne, observou-se que o teste de Lucas-Lehmer é muito eficiente, porém pode-se levar em consideração, que os seus divisores são da forma 2kp + 1 (item 4.3.1) e sendo assim, caso 2kp + 1 seja um número primo, 2p-1 75 jamais será um número primo, pelos seguintes fatos: Ou p é composto, logo 2p-1 também o será; ou p é primo de Sophie Germain, e neste caso aplica-se o teorema de Fermat. Portanto, antes da aplicação do teste de Lucas-Lehmer poderia ser verificada a primalidade de 2kp+1, sendo k=1 e p o expoente a ser testado. Assim para saber se 223-1 é primo, bastaria testar se o 23*1*2+1 = 47 é primo. Esta sugestão carece de maiores estudos e uma análise. Outra observação importante que pode ser empregada no algoritmo de teste de primalidade, é a verificação da congruência módulo 30. Caso a congruência seja um número composto, consequentemente o número em questão será composto. Caso contrário, pode-se aplicar o teste de Fermat. Assim, serão testados poucos números compostos, ou seja, grande parte dos números compostos serão descartados com estes dois testes iniciais. Cada detalhe pode ser muito importante para uma nova descoberta, e cada descoberta pode significar um avanço na busca incansável da solução deste incrível problema matemático. Cabe aos novos pesquisadores, desenvolver técnicas e pesquisas nesta área, pois há muito por ser feito. Apenas uma classe de números primos é vasto o suficiente para um estudo aprofundado e o desenvolvimento de uma investigação de cada detalhe em busca de um aperfeiçoamento do mesmo. Como sugestão de trabalhos futuros: provar a infinitude dos números primos de mersenne; analisar o algoritmo proposto nesta conclusão referente ao teste de LucasLehmer; provar a inexistência de números perfeitos impares. 76 BIBLIOGRAFIA AGRAVAL, Manindra; KAYAL, Neeraj; SAXENA, Nitin. Primes is in P. Indian Institute of Technology Kanpur, Índia, Disponível em: <http://www.cse.iitk.ac.in/users/manindra/algebra/primality_v6.pdf> Acesso em: 27 de outubro de 2012 ALENCAR FILHO, Edgard de - Teoria Elementar dos Números - Livraria Nobel S.A. - 1981. ATKIN, A.; BERNSTEIN, D. Prime Sieves using Binary Quadratic Forms.Mathematics of Computation, Providence - Rhode Island - USA, v. 73, p. 1023-1030, 2004. CLAY MATHEMATICS INSTITUTE. Riemann Hypothesis. Disponível em: <http://www.claymath.org/millennium/Riemann_Hypothesis/> Acesso em: 27 de março de 2012. COUTINHO, S. C. Números inteiros e criptografia RSA. Segunda edição, IMPA, 2000. 213 págs. COUTINHO, S. C. Primalidade em tempo polinomial – Uma introdução ao algoritmo AKS. Universidade Federal do Rio de Janeiro, 2004. CRANDALL, R; MAYER, E; Papadopoulos, J. The Twenty Fourth Fermat Number is Composite. 2003. Criptografia assimétrica. Disponível em: <http://www.gta.ufrj.br/grad/07_2/delio/Criptografiaassimtrica.html> Acesso em: 11 de setembro de 2012. DEITEL, Paul; DEITEL, Harvey. Java: Como Programar. Traduzido por Edson Furmankiewicz. São Paulo: Pearson Prentice Hall, 2010. 8ª Ed.Tradução de: Java: how to program. DOMINGUES, Hygino H. - Fundamentos de Aritmética - Atual - 1991. EUCLIDES. Elementos de Geometria. Versão Latina de Frederico Commandino. 1944. EFF. Record 12-Million-Digit Prime Number Nets $100,000 Prize. Disponível em: <https://www.eff.org/press/archives/2009/10/14-0> Acesso em: 27 de março de 2012 GERSTING, Judith L. Fundamentos Matemáticos para a Ciência da Computação: um tratamento moderno de matemática discreta. Traduzido por Valéria de Magalhães Iorio. Rio de Janeiro: LTC, 2008. Tradução de: Mathematical structures for computer science, 5 th Ed. 77 GIMPS - Great Internet Mersenne Prime Search. <http://www.mersenne.org> Acesso em: 27 de março de 2012 Disponível em: IFRAH, Georges. História Universal dos Algarismos: a inteligência dos homens contada pelos números e pelo cálculo. Traduzido por Alberto Muñoz e Ana Beatriz Katinsky. Rio de Janeiro: Nova Fronteira, 1997 – 2V. Tradução de: Histoire universalle dês chiffres. KLEINJUNG, Thorsten; et al. Factorization of a 768-bit RSA modulus. Fevereiro de 2010. LEHMER, D. N. "An extended theory of Lucas' functions," Ann. Math., 31 (1930) 419448. Reprinted in Selected Papers, D. McCarthy editor, v. 1, Ch. Babbage Res. Center, St. Pierre, Manitoba Canada, pp. 11-48 (1981). MERSENNE PRIMES: History, Theorems and Lists. Disponível <http://primes.utm.edu/mersenne/index.html> Acesso em: 27 de março de 2012. em: ORACLE AND/OR ITS AFFILIATES. Java SE Documentation. Disponível em: <http://docs.oracle.com/javase/7/docs> Acesso em: 24 de agosto de 2012. Perfect numbers. Disponível em: < http://www.gap-system.org/~history/HistTopics/ Perfect_numbers.html> Acesso em: 27 de março de 2012. PLACE, Ricardo Leocádio. Criptografia, assinatura digital e alguns outros conceitos. Disponível em: <http://eltiger.wordpress.com/2008/10/12/ciptografiaassinatura-digital-e-alguns-outros-conceitos/> Acesso em: 15 de outubro de 2012. ROBERT, Sedgewick. Algorithms in C. Addison-Wesley, 1990. RSA Laboratories. Cryptographic Challenges. Disponível <http://www.rsa.com/rsalabs> Acesso em: 15 de outubro de 2012. em: SAUTOY, du Marcus. A musica dos números primos. A história de um problema não resolvido na matemática. Traduzido por Diego Alfaro. Rio de Janeiro: Jorge Zahar Ed., 2007. SINGH, Simon. O Livro dos Códigos. A Ciência do sigilo – do antigo Egito à criptografia quântica. Tradução de Jorge Calife. – Rio de Janeiro: Record, 2001. SINGH, Simon. O Último Teorema de Fermat. Rio de Janeiro: Record, 1998. Sophie Germain Primes: both p and 2p+1 are prime. Disponível em: <http://www.primesdemystified.com/sophiegermainprimes.html> Acesso em: 29 de março de 2012 78 TOSCANI, Laira Vieira; VELOSO, Paulo A. S.. Complexidade de Algoritmos. Instituto de Informática da UFRGS: Editora Sagra Luzzatto, 2002. UCLA - University of California, Los Angeles. Departament of Mathematics. Disponível em: <http://www.math.ucla.edu> Acesso em: 27 de março de 2012 WOLFRAM MATH WORLD, Lucas-Lehmer test. Disponível em: <http://mathworld.wolfram.com/Lucas-LehmerTest.html> Acessado em: 15 de outubro de 2012. ZHANG, Sibao; MA, Xiaocheng; ZHOU, Lihang. Some Notes on the Distribution of Mersenne Primes. Applied Mathematics, 2010, 1, 312-315. doi:10.4236/am.2010.14041 Published Online October 2010 (http://www.SciRP.org/journal/am)