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.
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
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.
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.
LISTA DE SIGLAS
√ - Raíz quadrada; ≅ - Aproximadamente; ≡ - Congruência; µs - Milissegundos; A.C - Antes de Cristo;
AKS - Manindra Agrawal, Neeraj Kayal e Nitin Saxena; ASCII - American Standard Code for Information Interchange Cong. - Congruência;
FFT - Transformada Rápida de Fourier; Hs. - Horas;
IDE - Integrated Development Environment; log - Logaritmo;
MDC - Mínimo Divisor Comum; Min. - Minutos; Mod. - Módulo; O - Ó Grande; P - Polinomial; PE. - Padre; Pg. - Página; PT - Pontos;
RSA - Ronald Rivest, Adi Shamir e Leonard Adleman
S - Segundos;
WEB - 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
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
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.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
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).
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
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.
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.
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
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.
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.
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
Para [i de 2 até √n ] faça: Se (n mod i = 0) então:
Retorna COMPOSTO Senão
i = i + 1; retorna PRIMO;
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;
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;
Figura 2: Crescimento assintótico O f(n); 0 100 200 300 400 500 600 700 800 1 2 3 4 5 6 f(n) g(n)
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;
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; 0 10000 20000 30000 40000 50000 60000 1 2 3 4 5 6 f(n) g(n)
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). 0 5 10 15 20 25 30 35 40 45 1 2 3 4 5 6 f(n) g(n)
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.
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.
EXEMPLO 1:
Para n=16, temos:
Q = (log 2 (n)) – 1 = (log 2 (16)) – 1;
Q = 4 – 1 = 3;
R(n) = 22Q = 223 = 28 = 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; R(n) = 22Q = 224 = 216 = 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.
n R(n) P, Q log2R(n) n/2 n R(n) P, Q log2R(n) n/2 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) 16.00000 16.00000 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).
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;
R(n) = 22Q = 225 = 232 = 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;
Portanto, de 1 até n=64 (para 2R(n) – 1 = 24.294.967.296 - 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.
Ordem Expoente Díg. Primo Mersenne Díg. Número Perfeito Ano 1 2 1 1 ---- 2 3 1 2 ----3 5 2 3 ----4 7 3 4 ----5 13 4 8 1456 6 17 6 10 1588 7 19 6 12 1588 8 31 10 19 1772 9 61 19 37 1883 10 89 27 54 1911 11 107 33 65 1914 12 127 39 77 1876 13 521 157 314 1952 14 607 183 366 1952 15 1279 386 770 1952 16 2203 664 1327 1952 17 2281 687 1373 1952 18 3217 969 1937 1957 19 4253 1281 2561 1961 20 4423 1332 2663 1961 21 9689 2917 5834 1963 22 9941 2993 5985 1963 23 11213 3376 6751 1963 24 19937 6002 12003 1971 25 21701 6533 13066 1978 26 23209 6987 13973 1979 27 44497 13395 26790 1979 28 86243 25962 51924 1982 29 110503 33265 66530 1988 30 132049 39751 79502 1983 31 216091 65050 130100 1985 32 756839 227832 455663 1992 33 859433 258716 517430 1994 34 1257787 378632 757263 1996 35 1398269 420921 841842 1996 36 2976221 895932 1791864 1997 37 3021377 909526 1819050 1998 38 6972593 2098960 4197919 1999 39 13466917 4053946 8107892 2001 40 20996011 6320430 12640858 2003 41 24036583 7235733 14471465 2004 ?? 25964951 7816230 15632458 2005 ?? 30402457 9152052 18304103 2005 ?? 32582657 9808358 19616714 2006 ?? 37156667 11185272 22370543 2008 ?? 42643801 12837064 25674127 2009 ?? 43112609 12978189 25956377 2008
(?) Seqüência ainda não provada.
Quadro 6: Lista de números perfeitos (GIMPS, 2012)
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).
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
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
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 78604416984821269 08177047949837137 68568912431388982 88379387800228761 47116525317430877 37814467999489; (116 dígitos). 36746043666799590 42824463379962795 26322791581643430 87642676032283815 73966651127923337 34171433968102700 92798736308917; (116 dígitos). 12301866845301177551304949583849 62720772853569595334792197322452 15172640050726365751874520219978 64693899564749427740638459251925 57326303453731548268507917026122 14291346167042921431160222124047 92747377940806653514195974598569 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”.
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:
Lista supostamente finita de números Primos Produto dos Fatores Coprimo do produto Fatores do 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
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.
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.
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.
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 desconsiderando-se apenas os números pares, desconsiderando-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:
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.
Intervalo Quantidade de números primos no intervalo
Tempo de execução em Milissegundos
0 a 1000000 78498 1690
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.
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:
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
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.
Intervalo Quantidade de números primos no intervalo Tempo de execução em Milissegundos Probabilidade de 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
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).
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:
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.
Ordem Expoente Quantidade de dígitos Tempo de execução em Milissegundos 1 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.)
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.
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:
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.
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);