Números inteiros e criptograa S. C. Coutinho Este arquivo reúne as atividades de laboratório desenvolvidas para a disciplina inteiros e criptograa números (MAB 624) oferecida pelo Departamento de Ciência da Compu- tação da UFRJ no segundo semestre de 2014. 1 2 Laboratório 1: kRSA 1. Cada objeto do Axiom tem um tipo extremamente bem denido. Verique qual o tipo, e correspondente abreviação, cada um dos seguintes números: 32 3 , Use )show, 0, 1 , 2 7 1−2 , 1+ √ 2, √ 1+ 3 , 2 0.425414, √ −5, √ −5 × 0.876 seguido do tipo, para uma lista das funções relativas àquele tipo, que inclui sua abrevi- ação. Qual a diferença entre PI, NNI e INT? 2. Para declarar o tipo de uma variável usamos dois pontos seguidos de tipo desejado e para atribuir :=. Por inteiro e lhe atribui o valor 2. Os esvaziados usando-se )clear all. um valor à variável usamos exemplo, x:INT := 2 declara a variável x como sendo do tipo valores e tipos de todas as variáveis já utilizadas podem ser É possível declarar o tipo de uma variável sem lhe atribuir nenhum valor e vice-versa. Porém, quando você não declara o tipo da variável, mas apenas lhe atribui um valor, o sistema escolhe o tipo da variável como sendo o mesmo do valor que lhe foi atribuído, o que pode levar a erros na execução de um programa. Por isso é muito importante declarar o tipo das variáveis antes de usá-las. 3. O que vimos já é suciente para para implementarmos o kRSA. Este sistema, como seu irmão mais velho RSA, requer dois programas distintos: um gerador de chaves e um programa que codica e decodica a mensagem dada. Siga o seguinte passo-a-passo para gerar a chave pública e chave secreta a serem usadas na sua versão do kRSA: • • • • • a, b, a0 e b0 inteiros positivos quaisquer > 104 . variável M o valor ab − 1. e o valor a0 M + a e a d o valor b0 M + b. n o valor (ed − 1)/M . Atribua às variáveis Atribua à Atribua a Atribua a Sua chave secreta será (n, d) e sua chave pública será (n, e). 4. A codicação e decodicação no kRSA é feita usando a mesma função, que chamaremos simples- kRSA. Esta função tem como entrada três inteiros: os dois inteiros positivos correspondentes à chave (pública ou secreta) e a mensagem m que suporemos que é um número de cartão de crédito e, portanto, tem 16 algarismos. A função deve retornar o resto, na divisão por n, de cm, em que escolhemos c = e se queremos codicar m e c = d se queremos decodicar m. No Axiom esta função mente de pode ser programada de maneira bastante simples, como: kRSA(n : PI, c : PI, m : NNI) : NNI == rem(c ∗ m, n) 3 :NNI corresponde ao tipo esperado da saída desta função e o símbolo == indica que o que * para indicar a multiplicação de c por m. O segundo vem em seguida são os comandos que denem a função. Observe também o uso de (a) Por que tipamos n e c na entrada como PI e m e a saída como NNI? (b) Escolha um número de cartão de crédito (inteiro com 16 algarismos) e codique-o usando a sua chave pública. (c) O número 472460344996534 é o resultado da codicação kRSA de um número chave que aparece no exercício 6 desta lista. Decodique a mensagem: o que m m com a representa? 5. Para encerrar, escreveremos uma função que gera chaves para o kRSA a partir de inteiro positivos gedit e escreva geraChavesKRSA(a,b,a',b')== na margem esquerda, sem esquecer de tipar as variáveis de entrada. A saída será a lista [n,e,d], que tem tipo LIST(NNI). Abaixo desta linha, escreva, uma abaixo da outra e endentadas relativamente a, b, a0 e b0 dados como entrada. Abra um arquivo no à margem esquerda, as instruções do exercício 3 acima, exceto as atribuições dos valores às variáveis a, b, a0 e b0 , que passaram a ser parte da entrada da função. Ao nal do programa acrescente a linha return([n,e,d]) para que o sistema retorne esta lista de valores. A endentação, que deve ser a mesma para todas estas linhas, é a maneira usada na sintaxe do Axiom para indicar que fazem parte de um único bloco de código. Grave o arquivo com a terminação e leia-o no .input e nome à sua escolha Axiom usando o comando )read seguido do nome do arquivo, que deve vir acompanhado do caminho indicando onde foi gravado. Depois de ler o programa, teste-o chamando-o no por geraChavesKRSA(#,#,#,#), com os # prompt substituídos por inteiros positivos à sua escolha. geraChavesKRSA(a,b,a',b') é fácil, mas incoveniente, porque n, e e d aparecem nesta ordem na lista de saída. Para evitar isto podemos usar um tipo chamado Record. Ao contrário das listas, cujas entradas são numeradas, as entradas de um Record são chamadas por nomes que devem ser declarados quando tipamos a saída de geraChavesKRSA. Por exemplo, ao tipar a saída como Record(n:PI,e:PI,d:PI) e inventar 6. Usar lista como tipo de saída para requer que o usuário lembre que valores para a entrada, obtive como saída [n = 1554916332496649, e = 178479852747, d = 606678245512] Altere, o tipo de saída no seu programa de LIST(NNI) para o sistema deve retornar os valores corretos, mas vai acusar um Record acima e teste-o de novo. O erro de compilação porque n, e e d estão sendo usados simultaneamente, como variáveis internas do programa e nomes de campos no Record. Para evisar isso basta alterar os nomes das variáveis. 7. Acrescente no início do seu arquivo os nomes e DRE dos dois membros da sua dupla, precedidos de - - e a função kRSA e envie o arquivo para criptolab dcc.ufrj.br. Os dois traços avisam ao sistema que o que segue naquela linha são comentários e não códigos de programas. 4 Laboratório 2: algoritmo euclidiano estendido 1. Neste laboratório implementaremos o a dois inteiros positivos tais que d = αa + βb. e b algoritmo euclidiano estendido que, tendo como entrada retornará o máximo divisor comum d de a e b Para construir a função abra um arquivo no esquecer de tipar comum de a e b. a e e β Faremos a implmentação do algoritmo em duas etapas. Primeiro a parte da função que calcula apenas o máximo divisor comum e,em seguida, a parte que calcula 2. α e dois inteiros b gedit e escreva α e β. mdcEstendido(a,b) == sem e a saída que, nesta primeira etapa, correspoderá apenas ao máximo divisor Não esqueca que o arquivo deve ter a terminação .input e que o bloco de código que corresponde às instruções da função deve estar endentado. Como dois restos são usados a cada laço que o programa executa, precisamos inicializar R1:PI := a R2:NNI := b R1 Em cada etapa o algoritmo vai dividir por R2 e obter um novo resto, até que o valor de anule. Quando isto acontecer, o máximo divisor comum de Para pôr este laço em funcionamento, precisamos usar um a e b estará guardado while, que no R2 se R1. na variável Axiom tem a seguinte sintaxe: while Condição repeat Instruções Note que as Instruções devem aparecer endentadas em relação à posição do deve ser vericada para que o laço while. A condição que while continue a ser executado é R2 6= 0 e as instruções a serem executadas são as seguintes: • • calcule o resto guarde o r da divisão de antigo valor de Para que isto possa ser feito R2 R1 por R2 R1. e guarde-o em R2; em sem que nenhum dos valores anteriores das variáveis se perca antes de podermos usá-los, precisaremos de uma variável temporária, que chamaremos de necessidade de inicializar esta variável fora do laço principal mas, por segurança, while seu tipo. Com isto as instruções dentro do • • • R2 em Rtemp; guarde em R2 o resto da divisão de guarde guarde o valor de Rtemp em R1 Não há você deve declarar terão a forma por R2 ; R1. Com isto, ao nal de uma passagem pelo laço, o resto da divisão dos Rtemp. antigos valores de R1 por R2 antigo valor de R2 estará guardado em estará guardado em R2. R1 e o Para retornar o valor de 5 R1 use return(R1), que precisa estar fora do while; isto é, deve ter a mesma endentação do bloco principal do algoritmo . Para calcular o resto e o quociente da divisão de Na linguagem do operador; assim, diferente corresponde a 3. a por b use rem(a,b) e quo(a,b) respectivamente. Axiom a negativa de uma armação é obtida acrescentando um til na frente do =. Guarde o arquivo e carregue a função usando )readsem esquecer o parêntesisseguido do caminho exato do local onde o arquivo foi gravado. Se o sistema acusar um erro de compilação, use a seguinte checklist para achar possíveis erros, antes de pedir ajuda: • • • • • uso de = em vez de :=; endentação incorreta; funções escritas erradas; confusão entre maiúsculas e minúsculas nos nomes das variáveis; multiplicação sem *. Para forçar o sistema a exibir os valores de uma variável z ao longo da execução de uma função use output(z). 4. Para converter o algoritmo que acabamos de programar no algoritmo estendido, precisamos de uma nova variável x. Conforme a descrição apresentada na aula, precisamos lembrar dos valores de x referentes aos dois últimos passos do algoritmo. No livro e na aula, a notação usada para os valores de x na inicialização foram x−1 e x0 . Como o não aceita x−1 como variável, escreveremos xm1 e x0. Inicialize estas variáveis com 1 e 0, antes do laço while, sem esquecer de tipá-las. Dentro do while você precisará acrescentar as seguintes instruções: Axiom • • • cálcule o quociente guarde q de R1 por x−1 − qx0 em x0; xm1 o antigo valor guarde em R2 ; de x0. Para executar estas instruções de maneira correta será necessário criar uma nova variável proceder como zemos com os restos. Não há necessidade de inicializar xtemp xtemp e while, fora do laço mas não esqueça de declarar seu tipo. Como vimos na aula, o cálculo do valor de β pode ser feito ao nal da execução do laço. Finalmente, será necessário alterar o tipo da saída para que a função α e β. Record com três campos chamados de mdc, alfa e beta. retorne o máximo divisor comum e os valores de A melhor maneira de fazer isto é usar um Não esqueça de atualizar o que o algoritmo deve retornar. Acrescente no início do seu arquivo os nomes e DRE dos dois membros da sua dupla, precedidos de - - (sem espaço entre eles) e envie o arquivo para o endereço [email protected]. Nossa função foi construída supondo-se que os valores da entrada satisfazem se isto não for verdade? a > b. O que acontece 6 Laboratório 3: método cíclico para resolver a equação de Pell 1. Neste laboratório implementaremos a função inteiro achaSolucaoPell que, tendo como entrada um d que não é quadrado perfeito, retorna uma solução não trivial da equação de Pell x2 −dy 2 = 1. O algoritmo, como descrito em sala, é o seguinte. Algoritmo (Método cíclico). Dado um inteiro positivo d, que não é um quadrado perfeito, o algo- ritmo retorna uma solução não trivial da equação de Pell x2 − dy 2 = 1. Inicialize um contador k := 0 e P := [x, y]. Inicialize A com 1, B com 0 e C com d. Enquanto k 6= 0, A 6= 1 e k for ímpar repita: Determine o maior inteiro q menor que a raiz positiva da equação At2 − 2Bt − C = 0. A := −(Aq 2 − 2Bq − C), B := Aq − B e C := A. Substitua x por qx + y e y por x em P . Incremente k de uma unidade. Substitua x por 1 e y por 0. Retorne P . Faça 2. Para implementar este algoritmo, você vai precisar das seguintes funções do Axiom: solve(f,0.1): retorna uma aproximação das raízes do polinômio f em ponto wholePart(r): retorna a parte inteira do número real r; L.k: retorna a k-ésima posição de uma lista L; rhs(E): retorna o lado direito de uma expressão E da forma E1 = E2 ; odd?(n): determina se o número inteiro n é ímpar; eval(E,[x=f,y=g]): substitui x por f e y por g em um polinômio E(x, y); Todas as funções do true ou false, Axiom que acabam com uma interrogação (como odd? e utuante; prime?) retornam dependendo da entrada. x e y . Se você deixar a List(OrderedVariableList([x,y])). Entretanto, ao longo da execução do algoritmo, as entradas de P serão polinômios lineares em x e y , que não são variáveis e, por isso, não podem ser atribuídos a P . Para evitar o problema, basta tipar P como LIST(MPOLY([x,y],INT)). Use )show para descobrir o que signica o tipo MPOLY. No algoritmo o ponto P é inicializado como uma lista com duas variáveis escolha do tipo por conta do 3. Axiom, ele atribuirá a P o tipo Escrevendo a Frenicle em 1657, Fermat propôs que ele achasse soluções para as equações de Pell com d = 61 e com d = 109 porque, segundo ele, esses dois exemplos não lhe dariam muito problema. Use o seu algoritmo para determinar uma solução para as equações de Pell para as quais d = 61 e d = 109. Quantos passos o algoritmo executou até encontrar a solução? Acrescente no início do seu arquivo os nomes e DRE dos dois membros da sua dupla, precedidos de entre eles) e envie o arquivo para o endereço [email protected]. - - (sem espaço 7 Laboratório 4: algoritmo de fatoração de Fermat Algoritmo (Algoritmo de Fermat). fator próprio de n, o algoritmo retorna um n ou a mensagem eh primo. Inicialize Se Dado um inteiro positivo ímpar x com a parte inteira da raiz quadrada de n. 2 n = x então retorne([x, x]) senão x := x + 1 Enquanto √ x2 − n não for inteiro repita x := x + 1 Se x = (n + 1)/2 então retorne(eh primo). Senão √ x2 − n. retorne([x − y, x + y]). y := O algoritmo deve ser implementado como a função cuja saída será uma lista ou uma diferentes, o o tipo string. fermat, cuja entrada é um inteiro positivo e Para lidar com saídas que podem ser de vários tipos Axiom tem o tipo Union. Por exemplo, no caso do algoritmo de Fermat, a saída terá Union(List(PI),String). Para alguns dos testes que são realizados ao longo if-then-else que, no Axiom, tem a seguinte estrutura: do algoritmo, precisamos da construção if condição then Instruções else Instruções √ while, para determinar se x2 − n é ou não integer? do Axiom, cuja saída é true ou false. Já o teste feito no laço usando a função inteiro pode implementado Certique-se de que o algoritmo está funcionando corretamente quando a entrada são números inteiros positivos ímpares que sejam quadrados perfeitos, primos ou produtos de dois primos distin- tos. Adicione ao algoritmo um contador para descobrir quantos passos foram executados antes da resposta ser encontrada e use a função do while. Determine, então, quantas vezes o laço é executado quando a entrada é o produto de um primo com do output para retornar o valor do contador ao nal da execução 10 nextPrime maior que m. algarismos pelo seu sucessor. Para achar estes primos use a função Axiom, que tendo como entrada um inteiro positivo m retorna o menor primo Acrescente no início do seu arquivo os nomes e DRE dos dois membros da sua dupla, precedidos de -- (sem espaço entre eles) e envie o arquivo para o endereço [email protected]. 8 Laboratório 5: crivo de Eratóstenes 1. O crivo de Eratóstenes será implementado na função n, inteiro positivo eratostenes que, tendo como entrada um retorna a lista dos números primos positivos menores ou iguais a n. A função será implementada em duas partes. Na primeira efetuamos o risca-risca da lista de ímpares; na segunda, coletamos os números da lista anterior que não foram riscados. Lembre-se que riscar 0 um número neste contexto corresponde a trocar por o valor da posição da lista que corresponde àquele número. 2. Antes de começarmos a descrever o algoritmo, há alguns comandos relativos a listas que você precisa saber. Suponha que L e L' são duas listas de elementos do mesmo tipo, então: L:=[1 for i in 1..m] cria uma lista de m elementos, todos L.k:=a troca por a o valor da k-ésima posição de L; append(L,L') cria a lista obtida acrescentando L' ao nal L. Para acrescentar um elemento iguais a 1; a ao nal de L basta tomar L' como sendo [a] na construção acima. tamanhoDaLista com a parte inteira de n/2 tamanhoDaLista posições, todas preenchidas com 1s. Serão necessários dois laços while aninhados para crivar ListaBase. O laço mais externo percorre a lista a partir do início. Para cada posição k , verica-se primeiramente se L.k é ou não igual a 1. Se for: 3. A primeira etapa começa com a inicialização de e de ListaBase • • • calculamos o primo correspondente a calculamos a posição 2 ` = 2k + 2k k, por 0 termo é nas posições de ` e cuja razão é que é da lista efetuamos o risca-risca dos múltiplos de 1 como uma lista de ListaBase p. L p = 2k + 1; em que p2 está posicionado; p usando um segundo laço while, no qual trocamos que pertencem à progressão aritmética cujo primeiro Não esqueça de inicializar e de incrementar as variáveis que controlam os laços valor de k deve ser incrementado ao nal do if-then que determina se L.k = 1 while. Note que o e que contém o laço que efetua o risca-risca. [2] e um contador com k:PI:=1. Criamos, então, um laço while que varre a lista e, toda vez que ListaBase.k = 1, acrescenta 2k +1 à lista primos. Ao nal deste laço o programa retorna a lista primos. 4. Começamos a segunda etapa inicializando a lista 5. primos com A partir deste laboratório você deve comentar os seus programas, explicando o que cada parte do algoritmo faz. O mesmo se aplica aos laços while aninhados. As linhas de comentário devem ser precedidas de dois travessões. Acrescente nome e DRE dos dois membros da equipe e envie o arquivo para o endereço [email protected].