• Nenhum resultado encontrado

Desenvolvimento e optimização de back-end de compilador criptográfico para plataformas ARM

N/A
N/A
Protected

Academic year: 2020

Share "Desenvolvimento e optimização de back-end de compilador criptográfico para plataformas ARM"

Copied!
133
0
0

Texto

(1)

Universidade do Minho

Escola de Engenharia

Rui Pedro Araújo Vilas Boas

Desenvolvimento e optimização de

back-end de compilador criptográfico para

plataformas ARM

(2)

Universidade do Minho

Dissertação de Mestrado

Escola de Engenharia

Departamento de Informática

Rui Pedro Araújo Vilas Boas

Desenvolvimento e optimização de

back-end de compilador criptográfico para

plataformas ARM

Mestrado em Engenharia Informática

Trabalho realizado sob orientação de

Professor Manuel Bernardo Barbosa

(3)

Agradecimentos

Finda esta dissertação, gostaria de agradecer, em primeiro lugar, ao meu orientador Manuel Bernardo Barbosa, pela sua orientação, paciência, disponibilidade e boa disposição. Gostava também de agradecer ao Tiago Oliveira, pelas discussões de ideias que se revelaram muito importantes no desenvolvimento deste trabalho. Agradeço a todos os meus amigos, que fazem parte do meu dia-a-dia e que sempre me incentivaram a ser o meu melhor. Por fim, mas garantidamente não menos importante, agradeço aos meus pais que sempre acreditaram em mim e me apoiaram, foram eles que me possibilitaram ser tudo o que sou hoje.

Este trabalho foi apoiado pela seguinte bolsa de investigação.

Network Sensing for Critical Systems Monitoring (NORTE -01 -0124 -FEDER- 000058)

No âmbito do ON.2 IC&DT Programa Integrado “BEST CASE – Better Science Through Coo-perative Advanced Synergetic Efforts” Refª BI 2014_BestCase_RL3.2_UMINHO

(4)
(5)

Resumo

Devido à elevada proliferação tecnológica, existesoftwarecriptográfico implementado numa miríade de

plataformas. Plataformas essas que podem ter características de computação bastante diferentes. No

entanto, osoftwarecriptográfico deverá ser imperceptível ao utilizador final, uma vez que deve funcionar

como uma camada de protecção às tarefas que o utilizador possa estar a realizar e não aumentar a sua

pegada computacional. Essa exigência leva muitas vezes a que osoftware criptográfico tenha que ser

re-implementado de acordo com as capacidades computacionais da plataforma/dispositivo-alvo (sendo até

comum recorrer-se a assembly para atingir tal objectivo). O desenvolvimento de software criptográfico

também exige que o programador seja versado em diversas áreas da ciência, devendo assim ser realizado por criptógrafos especializados.

O CAO é uma DSL imperativa para a área da criptografia. Munido de um compilador, interpretador e

uma ferramenta de verificação formal, o CAO permite a passagem de conhecimento criptográfico para um programador com menos experiência na área da criptografia através da automatização da validação formal

das implementações. O compilador CAO possui umback-endaltamente configurável que permite geração

de executáveis dedicados às plataformas destino.

Com a grande utilização actual de processadores ARM, torna-se interessante estudar formas de explorar

as características destes processadores e desenvolver umback-endque as implemente com o objectivo de

obter melhor desempenho dosoftwarecriptográfico. Nesta dissertação explorou-se a utilização dos tipos

de dados vectoriais e instruções do co-processador NEON de forma a obter paralelismo ao nível dos dados. Também foi estudada a possibilidade de inclusão de paralelismo nativo ao nível das tarefas no CAO, de forma a tirar partido das arquitecturasmulti-core.

(6)
(7)

Abstract

Development and optimization of a cryptographic compiler

Back-end devised for ARM platforms

With the current technological proliferation, cryptography started to be incorporated in wide range of devices, from embedded processors to high-end servers. As cryptographic software needs to be as close to invisible as possible in terms of computational footprint, it must be highly optimised for each specific platform/device. It is not uncommon to see cryptographic code written in assembly to achieve such goal. Furthermore, the development of cryptographic software needs the programmer to be well-versed in various domains of science as mathematics, computer science and electrical engineering.

With that in mind, the CAO language was developed. The CAO language is a imperativeDSLtailored for the

cryptographic domain. Backed-up by a compiler, interactive interpreter and a formal verification tool, the CAO language is able to help an inexperienced programmer in the development of cryptographic software with the automisation of the formal validation of the implementations.

The CAO compiler has a highly-tunable back-end that can be optimized to deliver executables tailored for specific platforms/devices.

Since the ARM architectures are one of the most common processor architectures used in embedded devices, the main goal of this dissertation is the development of a CAO back-end that takes advantage of the characteristics of these processors in order to generate executables of higher performance for cryptographic software. This dissertation explores the usage of the NEON co-processor’s instructions and datatypes as means to attain data-level parallelism. It also studies the possibility of enabling the use of task-level parallelism as a feature of the CAO language, in order to better explore multi-core architectures.

(8)
(9)

Conteúdo

Conteúdo ix

Lista de Figuras xiii

Lista de Tabelas xv

Lista de Códigos-fonte xvii

1 Introdução 1 1.1 Motivação . . . 2 1.2 Principais resultados . . . 3 1.3 Estrutura do documento. . . 4 2 Revisão da Literatura 5 2.1 Linguagens Criptográficas . . . 5 2.2 Técnicas de optimização . . . 12 2.3 Computação algébrica . . . 17 3 Compilador CAO 21 3.1 Arquitectura do compilador . . . 21

3.2 Tipos de dados CAO . . . 22

3.3 Exemplos de código CAO . . . 24

3.4 Exemplo de compilação . . . 26

3.5 Estrutura da bibliotecaback-endgenérica . . . 28

4 Desenvolvimento de um back-end optimizado 31 4.1 Descrição da arquitectura-alvo . . . 31

(10)

4.3 Introdução do GMP . . . 37

4.4 Algumas considerações sobre as alterações . . . 38

4.5 ParalelismoThread-level . . . 39

5 Validação Experimental 45 5.1 Metodologia de obtenção dos resultados . . . 46

5.2 Plataformas utilizadas. . . 48

5.3 Funções de teste . . . 49

5.4 Novas implementações . . . 55

5.5 Testesmulti-threadem processadormulti-core . . . 75

6 Conclusões e trabalho futuro 77 6.1 Trabalho futuro . . . 78

Bibliografia 81 A Ficheiro.plat 85 B Código das funções de teste 87 B.1 Código CAO AES . . . 87

B.2 Código CAO Salsa20 . . . 99

B.3 Código CAO SHA-1 . . . 101

C Tabelas dos resultados obtidos (back-ends) 105 C.1 AES . . . 105

C.2 Salsa20 . . . 106

C.3 SHA-1 . . . 107

D Gráficos dos resultados obtidos (back-ends) 109 D.1 AES . . . 109

D.2 Salsa20 . . . 111

D.3 SHA-1 . . . 112

E O que são custos inclusivos e exclusivos (callgrind) 115

(11)

Siglas

AES Advanced Encryption Standard API Application Programming Interface AST Abstract Syntax Tree

CFG Control Flow Graph

CISC Complex Instruction Set Computer CPU Central Processing Unit

DES Data Encryption Standard DLP Data-level Parallelism DPA Differential Power Analysis DSL Domain-Specific Language ECB Electronic Codebook GCC GNU Compiler Collection

GMP GNU Multiple Precision Arithmetic Library GPL General-Purpose Language

(12)

ILP Instruction-level Parallelism IoT Internet of Things

ISA Instruction Set Architecture

MIMD Multiple Instruction Multiple Data

NIST National Institute of Standards and Technology NSA National Security Agency

NTL Number Theory Library OpenMP Open Multi-Processing

PAPI Performance Application Programming Interface PRF Pseudorandom function

RISC Reduced Instruction Set Computer SHA-1 Secure Hash Algorithm 1

SIMD Single Instruction Multiple Data SMT Simultaneous Multithreading SSA Static Single Assignment TAC Three-Address Code TLP Thread-level Parallelism

(13)

Lista de Figuras

3.1 Arquitectura do Compilador CAO . . . 21

4.1 Cifra por blocos emcounter mode . . . 41

5.1 Custos e chamadas das funções do módulo CAO_ubits na função AES com oback-end

vec32 (O3) . . . 59

5.2 Custos e chamadas das funções do módulo CAO_ubits na função AES com oback-end

original (O2) . . . 59

5.3 Custos e chamadas das funções do ficheiro arm_neon.h (O3) . . . 60

5.4 Custos e chamadas das funções do módulo CAO_ubits na função Salsa20 com o

back-endvec32 (O2) . . . 63

5.5 Custos e chamadas das funções do módulo CAO_ubits na função Salsa20 com o

back-endoriginal (O2) . . . 63

5.6 Custos e chamadas das funções do ficheiro arm_neon.h (O2) . . . 64

5.7 Custos e chamadas das funções do módulo CAO_int na função Salsa20 com oback-end

vec32 (O2) . . . 65

5.8 Custos e chamadas das funções do módulo CAO_int na função Salsa20 com oback-end

original (O2) . . . 65

5.9 Custos e chamadas das funções do módulo CAO_ubits na função SHA-1 com o

back-endvec32 (O3) . . . 70

5.10 Custos e chamadas das funções do módulo CAO_ubits na função SHA-1 com o

back-endoriginal (O2) . . . 70

5.11 Custos e chamadas das funções do ficheiro arm_neon.h (O3) . . . 70

5.12 Custos e chamadas das funções do módulo CAO_int na função SHA-1 com oback-end

vec32 (O3) . . . 71

5.13 Custos e chamadas das funções do módulo CAO_int na função SHA-1 com oback-end

(14)

5.14 Custos e chamadas das funções do módulo CAO_mod na função SHA-1 com oback-end

vec32 (O3) . . . 72

5.15 Custos e chamadas das funções do módulo CAO_mod na função SHA-1 com oback-end

original (O2) . . . 72

(15)

Lista de Tabelas

5.1 Resultados da função AES em milissegundos (ms) . . . 56

5.2 Rácios entre original em O2 e novas implementações em O3 para o AES . . . 56

5.3 Percentagem de utilização dossource files na função AES compilada em O0 com a

im-plementação vec32 e cominputde 4096bytes . . . 58

5.4 Percentagem de utilização dossource files na função AES compilada em O3 com a

im-plementação vec32 e cominputde 4096bytes . . . 58

5.5 Resultados da função Salsa20 em milissegundos (ms) . . . 61

5.6 Rácios entre original em O2 e novas implementações em O2 para o Salsa20 . . . 61

5.7 Percentagem de utilização dossource filesna função Salsa20 compilada em O0 com a

implementação vec32 e cominputde 4096bytes . . . 62

5.8 Percentagem de utilização dossource filesna função Salsa20 compilada em O2 com a

implementação vec32 e cominputde 4096bytes . . . 62

5.9 Resultados da função SHA-1 em milissegundos (ms) . . . 66

5.10 Rácios entre original em O2 e novas implementações em O3 para o SHA-1 . . . 67

5.11 Percentagem de utilização dos source files na função SHA-1 compilada em O0 com a

implementação vec32 e cominputde 4096bytes . . . 68

5.12 Percentagem de utilização dos source files na função SHA-1 compilada em O3 com a

implementação vec32 e cominputde 4096bytes . . . 68

5.13 Rácios médios da optimização obtida utilizando a implementação vec32 . . . 73

(16)
(17)

Lista de Códigos-fonte

1 Inicialização de matrizes, vectores e estruturas . . . 23

2 Inicialização de mods . . . 24

3 Implementação CAO da função curve25519 . . . 25

4 Implementação CAO da funçãobubble sort . . . 26

5 Código C gerado da funçãobubble sort . . . 27

6 Exemplo de utilização de parallel sections . . . 40

7 Exemplo sequencial do AES emcounter mode . . . 41

8 Exemplo paralelo do AES emcounter mode . . . 42

9 Excerto do código C gerado pela implementação AES com adição de directivas OpenMP 43 10 Sugestão de sintaxe CAO para paralelismo recorrendo a parallel sections . . . . 44

(18)
(19)

Capítulo 1

Introdução

Anteriormente ao século XX, a criptografia apenas se dedicava ao problema da comunicação secreta, ou seja, o conteúdo das mensagens comunicadas apenas deveria ser perceptível ao emissor e receptor, não vazando para terceiros. Nessa altura, a criptografia era vista como uma forma de arte uma vez que a criação de cifras apenas se apoiava no engenho e criatividade do seu criador, pois não havia conhecimento teórico que a pudesse sustentar. Porém, nos finais do século XX a criptografia passou a ser classificada como ciência devido ao aparecimento de teoria matemática que possibilitava a sua análise rigorosa. Assim sendo, na criptografia actual, à qual se chama criptografia moderna, a matemática assume um papel central na construção de criptossistemas seguros. A segurança destes sistemas passou a ser baseada em problemas matemáticos que se acreditam ser difíceis de resolver dado o contexto computacional existente. Enquanto que anteriormente a criptografia apenas tinha lugar dentro do contexto militar (a conhecida máquina Enigma utilizada na segunda guerra mundial é um exemplo disso), actualmente a criptografia pode ser encontrada em todo o lado, onde os seus utilizadores muitas vezes nem se apercebem que estão

dependentes dela, como por exemplo na comunicação segura no acesso a um website. Para além da

alteração do contexto da sua utilização, a sua extensão também se alargou. A criptografia deixou de cuidar apenas de confidencialidade para tratar também de outras propriedades de segurança como autenticidade, integridade, identificação, anonimato, não-repúdio,etc.

O desenvolvimento desoftwarecriptográfico é claramente distinto das outras áreas da engenharia de

software, uma vez que o desenho e implementação desoftwarecriptográfico implica o domínio de diversas áreas da ciência como a matemática, ciência da computação e engenharia electrónica. A isto acresce que a camada criptográfica deve ser imperceptível ao utilizador o que obriga a que osoftwareseja agressivamente

(20)

CAPÍTULO 1. INTRODUÇÃO

optimizado sem alterar as suas propriedades de segurança, o que muitas vezes pode implicar a obscuração do protocolo original e redução da sua genericidade. Actualmente, com a proliferação tecnológica existente,

a emergência da era da Internet of Things (IoT), a criptografia começou a ser introduzida em todo o tipo

de dispositivos de forma a garantir as suas propriedades de segurança. No entanto, esta vasta gama dispositivos pode apresentar características díspares, dado que estes podem variar entre processadores

embebidos com pouca capacidade computacional até servidores de alto-desempenho, tendo o software

criptográfico que ser adaptado ao ambiente onde vai ser executado.

A linguagem CAO, uma linguagem imperativa destinada à implementação de software criptográfico,

procura dar resposta a estes problemas levantados através da disponibilização de um compiladorsecurity–

awareque analisa, transforma e optimiza o código implementado nesta linguagem. A linguagem está

dese-nhada de forma a disponibilizar um conjunto de conceitos matemáticos como tiposfirst-class. Através da

inclusão destas noções matemáticas como tipos de dados, aliada a uma sintaxe imperativa é possibilitada ao programador comum a fácil implementação de componentes criptográficos. Por outro lado, a

lingua-gem possui um back-end altamente configurável que pode ser modificado de forma a gerar executáveis

optimizados para diferentes dispositivos-alvo. Desta forma elimina-se a necessidade de re-implementação do componente criptográfico original para optimizá-lo para cada dispositivo.

O mercado actual encontra-se inundado por dispositivos de pequeno porte como os smartphonese

tablets, entre outros. Comparativamente aos anos iniciais da informática, estes dispositivos já possuem uma grande capacidade de processamento tendo em conta o seu tamanho. Esta capacidade de proces-samento advém das evoluções ocorridas na área da arquitectura de processadores. Exemplos disso são a

inclusão de processamento vectorial dedicado à multimédia, através de instruçõesSIMDe as arquitecturas

multi-core. A grande maioria destes equipamentos utiliza processadores baseados em arquitecturasRISC

onde as arquitecturas ARM são líderes de mercado.

1.1 Motivação

Actualmente, existem doisback-ends desenvolvidos para o CAO. Um deles foi desenvolvido

especifi-camente para um processador proprietário, estando altamente optimizado para as suas propriedades. O

outro é umback-endgenérico de código-aberto. Dada a extrema necessidade de que osoftware

criptográ-fico seja o mais optimizado possível, e que as arquitecturas ARM estejam tão difundidas, é interessante

desenvolver um novoback-endque tire partido das propriedades destes processadores.

(21)

1.2. PRINCIPAIS RESULTADOS

Assim sendo, pretende-se estudar diferentes formas de tirar partido de funcionalidades específicas das

arquitecturas de processadores ARM, nomeadamente instruçõesSingle Instruction Multiple Data (SIMD)de

modo a obter benefícios do ponto de vista do desempenho. A utilização deste tipo de instruções também

se crê benéfica na protecção contra ataques side-channel. Os ataques side-channel são ataques que

tentam quebrar a segurança dos sistemas criptográficos não através de ataques força–bruta ou a possíveis fraquezas teóricas mas sim através da exploração de factores físicos das implementações. Exemplos disso

são os ataques por análise de tempo (timing attacks), por análises de consumo de energia e análises das

emissões de energia electromagnética. As instruçõesSIMDapresentam maior resiliência contra ataques

side-channel uma vez que o processamento simultâneo de diversos valores reduz a correlação entre o comportamento físico do processador e os valores individuais computados.

O objectivo final é obter implementações optimizadas para diferentes plataformas a partir do mesmo programa CAO e, para isso, é muito importante perceber como é que diferentes técnicas de optimização se comportam para diferentes programas/implementações CAO.

1.2 Principais resultados

Durante o desenvolvimento desta dissertação, foram criadas diversas implementações alternativas do

back-enddo compilador CAO. Tendo como ponto de partida oback-endgenérico, inicialmente foram utiliza-dos os tipos de dautiliza-dos vectoriais disponibilizautiliza-dos pelo co-processador NEON (que pode ser encontrado nos processadores ARM a partir da arquitectura ARMv7) para re-implementar os tipos de dados representativos dasbitstringssem sinal da linguagem CAO. Esta implementação foi replicada para os diferentes tamanhos de vector que este co-processador disponibiliza, por forma a encontrar qual o tamanho de dados mais eficiente. De seguida, foi trocada a biblioteca que implementa os inteiros CAO por uma com velocidades de processamento superiores. Por fim, foi re-implementado o tipo de dados que lida com inteiros módulo para que também possa fazer uso dos tipos de dados vectoriais NEON.

Posteriormente, cada uma destas implementações foi testada com diferentes casos representativos,

nomeadamente a cifra por blocos AES, a cifra sequencial Salsa20 e a função dehashSHA-1, com diferentes

tamanhos deinpute utilizando diferentes níveis de optimização do compilador C. Os dados destes testes

foram recolhidos e analisados de modo a que se possa identificar qual das implementações e nível de optimização é mais vantajosa, em termos de desempenho, para cada uma destas funções estudadas. No final, a partir das conclusões retiradas das execuções das diferentes funções, são apresentadas algumas

(22)

CAPÍTULO 1. INTRODUÇÃO

conclusões mais gerais.

Actualmente, a linguagem CAO não disponibiliza formas de explorar paralelismo. Assim sendo, são es-tudadas nesta dissertação formas introduzir formas paralelismo nativo na linguagem, mais concretamente paralelismo ao nível das tarefas. A partir do caso de estudo da paralelização de uma cifra por blocos, é apresentada uma sugestão de extensão à linguagem CAO para que seja incluída uma forma de criar

threads, para que se possa tirar partido das potencialidades das arquitecturasmulti-core.

1.3 Estrutura do documento

O restante desta dissertação está estruturado da seguinte forma:

Capítulo2 Neste capítulo é feita a revisão da literatura. Na primera parte, são aprofundados os

moti-vos para a existência deDSLs criptográficas e apresentadas duas linguagens deste tipo, o CAO e o

Cryptol. Na segunda parte, são estudadas e apresentadas diversas formas de obter melhor

desem-penho recorrendo a diferentes formas de paralelismo, com especial incidência nas instruçõesSIMD

e arquitecturasmulti-core. Finalmente, são apresentadas duas bibliotecas de computação algébrica.

Capítulo3 Neste capítulo é apresentada a estrutura do compilador CAO, os tipos de dados da linguagem

e a implementação genérica do back-end do compilador. É também apresentado um exemplo de

código CAO, o processo de compilação e o respectivo ficheiro gerado.

Capítulo4 É o capítulo onde são apresentadas as alterações realizadas noback-enddo compilador CAO

por forma a tirar partido das propriedades dos processadores ARM, na procura por melhor desem-penho. É também apresentado um teste simples de execução de código paralelo em processadores

multi-coree propostas alterações ao compilador CAO de forma a suportar este tipo de paralelismo de forma nativa.

Capítulo5 Neste capítulo são estudados os resultados obtidos da execução de três funções de teste

uti-lizando as novas implementações deback-endem diversos níveis de optimização, assim como do

testemulti-coreapresentado em4.

Capítulo6 Último capítulo onde são apresentadas as conclusões finais desta dissertação e propostas de

trabalho futuro.

(23)

Capítulo 2

Revisão da Literatura

2.1 Linguagens Criptográficas

UmaDomain-Specific Language(DSL) é uma linguagem de programação criada especificamente para

um campo científico. Estas linguagens contrastam com as sobejamente utilizadasGeneral-Purpose

Lan-guages (GPLs), como o C e o Java, que podem ser aplicadas a inúmeros domínios da ciência. Assim sendo,

umaDomain-Specific Language (DSL)permite utilização de conceitos abstractos pertencentes a esses

do-mínios científicos facilitando dessa forma a implementação de soluções, não havendo assim a necessidade

de ter em conta as limitações (técnicas) que umaGPLpossa ter. A existência de umaDSLpropicia também

a criação de ferramentas de suporte à ciência em questão, como por exemplo ferramentas de validação de correcção formal.

Desenvolversoftwarecriptográfico não é uma tarefa simples. Em primeiro lugar, o programador deve

ser versado em várias áreas da ciência, como a matemática, a computação, a engenharia electrónica,etc.,

pois a mais pequena falha em qualquer uma destas áreas pode ser explorada para quebrar a segurança de um sistema, e por essa razão, este trabalho deve ser apenas confiado a pessoas especializadas. Por

outro lado, o desempenho é fundamental uma vez que a camada criptográfica do software se deseja

totalmente invisível ao utilizador. Para atender a essa exigência, o programador é forçado a optimizar o seu

software. Na verdade, é bastante comum encontrar código implementado emassemblypara garantir tal

requerimento, o que exige ainda mais conhecimento do programador. No entanto, utilizarassemblypara

optimizar o código pode restringir o número de máquinas em que osoftwarepode executar, o que reduz

(24)

CAPÍTULO 2. REVISÃO DA LITERATURA

com a utilização deGPLspara desenvolversoftwarecriptográfico.

De seguida aprofundam-se as razões para a necessidade de umaDSLcriptográfica, abordando a

ques-tão sob diferentes perspectivas: desempenho, segurança e manutenção. Desempenho

Sob um prisma de abstracção, as linguagens de programação podem ser divididas em dois grupos: as linguagens de alto-nível (high-level) e as de baixo-nível (low-level). Esta abstracção é feita relativamente

ao Instruction Set Architecture (ISA), i.e., ao conjunto de instruções reconhecidas pelo processador. Isto

significa que quanto mais alto for o nível de abstracção, mais se aproxima da linguagem humana e se afasta da linguagem inteligível à máquina. Posto isto, facilmente se depreende que é mais fácil para o programador trabalhar sobre uma linguagem de alto-nível, que é o que acontece na grande maioria dos casos. Assim sendo, é necessário que esta abstracção seja eliminada para que o código possa ser executado pela máquina. Este é o trabalho do compilador.

Esta conversão é possível porque o compilador consegue associar correctamente as operações com o devido efeito sobre as variáveis, permitindo manipular o código e gerar um executável optimizado. Desta forma, é possibilitada a existência da especificação do programa tanto em linguagem perceptível à máquina como em linguagem perceptível ao programador.

No entanto, quando se escrevesoftwarecriptográfico, é frequentemente necessário recorrer a noções

matemáticas abstractas (e.g. corpos finitos) que, programando com umaGPL, requerem a utilização de

bibliotecas externas para mais facilmente as manipular. A utilização deste tipo de bibliotecas levanta uma série de problemas de optimização, como por exemplo:

1. As variáveis não podem ser automaticamente alocadas a registos ou a registos temporários visto não serem de tipos de dados nativos e necessitam de ser alocadas manualmente para reduzir o espaço ocupado;

2. Não podem ser aplicadas técnicas de strength reductione é necessário trocar multiplicações por

constantes por cadeias de adição;

3. Eliminação de sub-expressões comuns (common sub-expression elimination) não pode ser realizada

e é necessário manualmente detectar e partilhar resultados intermédios.

(25)

2.1. LINGUAGENS CRIPTOGRÁFICAS

Portanto, sendo estas bibliotecas externas à linguagem, e por consequência, os seus tipos de dados não-nativos, o compilador não tem conhecimento sobre as suas semânticas operacionais, o que leva a que as optimizações que seriam expectáveis por parte deste não possam ocorrer.

Tudo isto representa um claro problema sob a perspectiva de desempenho. Se o programador pre-tender minorar os efeitos causados será sobrecarregado com trabalho extra e possivelmente ofuscará a implementação, pois deixará de representar o algoritmo original em favor de uma outra forma necessária para que se torne mais eficiente.

Segurança

É seguro dizer-se que é mais difícil diagnosticar e resolver problemas desoftwarerelativos a segurança do que problemas funcionais. Enquanto que os problemas funcionais normalmente se apresentam sob a

forma de umcrashou de um resultado errado, os problemas de segurança só se tornam visíveis quando

um utilizador explora indevidamente uma falha dedesign, (isto é, em linguagem corrente, “só se tornam

visíveis tarde demais”). Os problemas de segurança não surgem da normal utilização do softwaremas

sim através de uma utilização mal intencionada. Desta forma, é mais difícil prevenir e detectar problemas de segurança, pois requerem que o programador tenha o trabalho de “pensar como o atacante” e de se “defender” de todos os ângulos de ataque possíveis. Por fim, estes problemas diferem no sentido em que um único problema de segurança pode ser catastrófico enquanto que um problema funcional geral-mente não apresenta consequências tão graves, ou seja, enquanto que a mais ínfima falha de segurança pode comprometer o sistema completo e permitir ao atacante atingir o seu objectivo, normalmente uma falha funcional, apesar de poder ser terminal, pode não impedir que o programa continue a funcionar correctamente.

Este trabalho de detectar e prevenir falhas de segurança é claramente um exercício oneroso em que o programador poderia beneficiar do auxílio de alguma forma de assistência automática, assim como o dado pelos compiladores para erros funcionais através de mensagens de erro e de aviso. Esta mecanização

do processo oferece uma alternativa com maior garantia de qualidade dosoftwareatravés da redução da

possibilidade de erro. Este tipo de abordagem tem-se tornado cada vez mais comum e vários investigadores já desenvolveram ferramentas para detectar e prevenir vários tipos de ataques.

No campo dos métodos formais, têm vindo a ser desenvolvidos métodos que permitem a detecção

automática ou semi-automática de vulnerabilidades de segurança em protocolos criptográficos (e.g.

(26)

CAPÍTULO 2. REVISÃO DA LITERATURA

descrição do protocolo (numa forma deprocess algebra) e posteriormente essa descrição é avaliada por

software demodel checking que conclui se o protocolo é, ou não, seguro. Apesar da automatização de provas de segurança estar num patamar diferente do de mecanismos que possam auxiliar o

desenvolvi-mento desoftwarecriptográfico, e que, geralmente, quando se desenvolve código criptográfico já se segue

protocolos pré-estabelecidos, estes progressos na análise formal de programas começaram a ser

frutífe-ros quanto à área dos ataques side-channel[Kocher, 1996]. Embora já sejam conhecidas e formalmente

comprovadas várias formas de defesa contra estes ataques, a sua implementação fica sempre a cargo do programador. Desta forma, apesar de haver um vasto conhecimento sobre várias técnicas de ataque e de defesa, o programa fica sempre exposto ao perigo do erro humano. Faz então sentido transferir este

conhecimento para ferramentas automatizadas de forma a assistir na detecção de áreas dosoftwareque

sejam sensíveis a ataques side-channel, e na aplicação de contra-medidas, aliviando assim o trabalho do

programador. Manutenção

Após os problemas de optimização do desempenho e validação de segurança manuais, o próximo problema com que o programador se depara é o de manutenção. Numa primeira inspecção, torna-se evidente que a mais pequena alteração no código pode desperdiçar todo o esforço anterior e requerer uma nova optimização e reavaliação da segurança. No entanto, o problema adensa-se quando é necessário o

mesmo softwarecorrer em máquinas com características muito díspares. Devido aos recentes avanços

tecnológicos, existe uma grande quantidade de dispositivos com diferentes características computacionais: desde processadores embebidos, com capacidade computacional e de memória limitadas, até servidores de alto-desempenho com baixas-latências, necessitando todos eles de garantias de segurança. Assim sendo, para garantir um bom desempenho, é possível que sejam necessárias várias implementações de um mesmo programa, de forma a tirar partido completo das funcionalidades de cada uma das diferentes máquinas; o que nos leva de volta ao problema inicial. Manter e sincronizar todas estas versões torna-se uma tarefa complexa e o desfecho mais provável é que pequenas inconsistências de negligências acabem por tornar osoftwareineficiente e/ou inseguro.

Posto isto, surge a necessidade de desenvolver implementações de software genéricas e, portanto,

transversais a múltiplas plataformas e dispositivos. Pode parecer impossível a existência de uma linguagem universal onde se pode descrever os programas e depois compilá-los para cada dispositivo-alvo, no entanto, se o âmbito do problema for reduzido ao domínio da criptografia, devido à sua natureza matemática bem estruturada, torna-se possível uma aproximação a tal linguagem. Desta forma, a questão da manutenção dosoftwarefica resolvida, visto que se torna apenas necessário descrever okernelcriptográfico uma única

(27)

2.1. LINGUAGENS CRIPTOGRÁFICAS

vez e todo o trabalho de optimização e validação fica relegado ao compilador.

Nas seguintes secções apresentam-se duas DSLs criptográficas, o Cryptol1 e o CAO2, uma para o

paradigma funcional e outra para o imperativo.

2.1.1 Cryptol

O Cryptol [Lewis and Martin, 2003] é umaDSL puramente funcional para o domínio da criptografia

assente em métodos formais, desenvolvida nos laboratórios daGalois Connections com a consultoria de

criptógrafos da National Security Agency (NSA). A linguagem disponibiliza um conjunto de ferramentas

para validação das implementações, verificação formal das especificações, geração de vectores de teste e automação de testes.

Sendo uma linguagem puramente funcional, não possui estado global neminput/output, sendo apenas

dependente dos valores passados como parâmetro, não existindo assimside-effects,i.e. não é possível a

alteração de valores fora doscopeda função sem ser através dos valores devolvidos. Sendo uma linguagem

funcional, faz uso de conceitos como funções de ordem-superior efirst-class.

Actualmente, quando os algoritmos criptográficos são criados, são descritos através de notação ma-temática sobre a qual é possível fazer testes sobre a sua correcção formal. Mais tarde, quando esses algoritmos são aceites como seguros, são descritos por “implementações de referência” que variam entre pseudo-código, C ou até linguagem natural. Da utilização destas “linguagens” para descrição de imple-mentações de referência advêm alguns problemas:

1. Utilizar pseudo-código ou linguagem natural pode tornar as implementações ambíguas e/ou incom-pletas e não há possibilidade de validação.

2. Quando se usa pseudo-código, normalmente recorre-se a um tipo de “linguagem” processual que, em primeiro lugar, acaba por obscurecer os detalhes matemáticos e, em segundo, vai forçar uma ordem sequencial no algoritmo, o que não serve de boa base para uma implementação paralela.

3. Quando as implementações são descritas numa GPL, como C, normalmente tem que se ter em

conta os detalhes técnicos da linguagem. No caso do C, exemplo disso é a alocação dinâmica de

1https://cryptol.net/

(28)

CAPÍTULO 2. REVISÃO DA LITERATURA

memória que acaba por ofuscar o fluxo do algoritmo inicial.

Posto isto, uma das motivações para a criação da linguagem foi a possibilidade das implementações

se aproximarem o máximo das especificações matemáticas, reduzindo assim o gap existente entre as

implementações e as especificações, procurando assim aumentar a sua correcção e, por consequência, a sua segurança.

O Cryptol permite também a compilação dos algoritmos tanto para implementações de referência em

GPLs, como C ou Java, como para linguagens de descrição de hardware como VHDL e Verilog. Na sua

concepção, o Cryptol foi desenvolvido voltado para a criação de implementações em hardwarede

cripto-grafia simétrica formalmente verificadas. A natureza puramente funcional da linguagem proporciona uma

boa aproximação aohardware. Pois tanto as funções como os circuitos combinatórios correspondem a um

mapeamento deinputsaoutputs, a isto acresce que a não-sequencialidade do Cryptol permite tirar partido

das propriedades paralelas dohardware.

2.1.2 CAO

O CAO é uma DSLimperativa para o domínio da criptografia suportada por um compilador [Barbosa

et al., 2005], um interpretador interactivo e uma ferramenta de verificação formal (CAOVerif). O seu com-pilador executa um conjunto de análises, transformações e optimizações tendo em conta a segurança do sistema, traduzindo os programas descritos na linguagem CAO em implementações de alto-desempenho

em C. Essas implementações podem ser posteriormente integradas em projectos de softwaremais

com-plexos. Sob o ponto de vista de manutenção, isto é extremamente benéfico pois uma única especificação

em CAO pode ser traduzida em executáveis para diferentes dispositivos utilizando back-ends específicos

sem ser necessário o conhecimento prévio, por parte do programador, de quais são esses dispositivos. Na

sua essência, atoolchain está desenhada da seguinte forma: Em primeiro lugar é descrito um programa

na linguagem CAO e depois de compilado resulta uma especificação em C equivalente. Posteriormente

essa especificação será compilada peloGNU Compiler Collection (GCC)e executada no dispositivo-alvo. O

processo interior do compilador é descrito na secção3.1.

O foco da linguagem está restrito à implementação de componentes criptográficos como funções de

hash, cifras sequenciais e por blocos, protocolos de criptografia de chave pública, etc. Por essa razão,

um dos aspectos chave do design do CAO é o suporte de conceitos criptográficos como componentes

first-class da linguagem, i.e., permite que estes conceitos sejam passados como parâmetro, devolvidos

(29)

2.1. LINGUAGENS CRIPTOGRÁFICAS

por uma função, e atribuídos a uma variável de forma a facilitar a descrição dos programas e voltando a atenção do programador para aspectos da implementação que são críticos para a segurança. Daqui advém que, utilizando uma linguagem mais natural é permitido ao programador aumentar a sua produtividade e

reduzir a probabilidade de erro. O CAO é fortemente tipado e apresenta semânticacall-by-value, não sendo

também permitida a alocação dinâmica de memória de forma a evitar erros de gestão de memória. Sendo o seu foco a implementação de componentes criptográficos, não apresenta qualquer tipo de suporte de

input/outputno sentido de comunicação com o utilizador.

Pode-se dizer que o principal objectivo do CAO é permitir que um programador pouco versado em

criptografia possa desenvolversoftwareseguro através da transferência do conhecimento de programadores

experientes para uma ferramenta automática de análise e aplicação de técnicas de defesa. É importante ressalvar que o CAO não é um sistema invencível tanto da perspectiva de segurança como da perspectiva de desempenho. Por um lado é ingénuo assumir-se que é possível construir uma ferramenta automática que é capaz de se defender de todos os tipos de ataques existentes e formular defesas para formas de ataque que ainda não foram descobertas, por outro também não é consciente afirmar que o código gerado terá sempre melhor desempenho que outro escrito à mão.

(30)

CAPÍTULO 2. REVISÃO DA LITERATURA

2.2 Técnicas de optimização

O desempenho de um sistema computacional tem sido desde sempre uma preocupação,

independen-temente do seu uso final. De forma a responder a esta necessidade, osCentral Processing Units (CPUs)

evoluíram e tornaram-se cada vez mais rápidos, no entanto, isto revelou-se um custo na complexidade dos circuitos. Eventualmente tornou-se cada vez mais difícil atingir maiores frequências de relógio com circuitos tão complexos devido a problemas físicos inevitáveis como elevados consumos de energia, a

li-mitação da velocidade da luz, dissipação de calor, etc. Estes obstáculos obrigaram a uma alteração na

abordagem ao problema. As companhias de hardware decidiram simplificar os circuitos de começar a

introduzir múltiplas unidades de processamento num únicochip, originando assim as arquitecturas

multi-processador3. Esta decisão foi uma grande mudança de perspectiva, os fabricantes deixaram de depender

apenas emInstruction-level Parallelism (ILP)para começarem a empregar tambémData-level Parallelism

(DLP)eThread-level Parallelism (TLP).

Anteriormente à génese dos multi-processadores, o paralelismo existente (ILP) era implícito e ficava ao

cuidado do compilador e do hardwaree a única forma deTLPexistente era baseada em time slicing, ou

seja, o processador alterna entre diferentes threads de forma a dar a ilusão que estas estão a executar

em paralelo. O que não é na verdade paralelismo mas sim concorrência, pois existe um recurso a ser

disputado. Com a existência de CPUs multi-processador, as threads podem executar verdadeiramente

em paralelo se executarem simultaneamente em unidades de processamento diferentes. Enquanto que

o paralelismo recorrendo a ILP era implícito e levado a cabo pelo compilador e processador, as novas

formas de paralelismo requerem a interacção directa do programador. Os programadores passaram a ser obrigados a escrever e estruturar os programas de forma explicitamente paralela, o que muitas vezes se revela um fardo. Portanto, a necessidade de explorar as capacidades de paralelismo das arquitecturas multi-processador obrigou à criação de um novo modelo de programação que até então não existia.

Sob o ponto de vista da camada aplicacional, o paralelismo pode ser obtido de duas formas:

Data-level ParallelismeTask-level Parallelism. A primeira forma refere-se à possibilidade de operar sobre vários

sub-conjuntos de dados ao mesmo tempo, e a segunda refere-se ao processamento de múltiplas tarefas

(independentes entre si) em simultâneo. Assim sendo, através de DLPé possível dividir um conjunto de

dados em n sub-conjuntos e processá-los em simultâneo utilizando n unidades de processamento, dividindo assim o tempo de execução por n. Por outro lado, utilizandoTask-level Parallelism (TLP), é possível executar

3Note-se que o termo multi-processador é utilizado como termo lato para multi-processamento. Podendo referir-se tanto a

múltiplos processadores como a múltiploscoresnumchip.

(31)

2.2. TÉCNICAS DE OPTIMIZAÇÃO

n tarefas em n unidades de processamento independentes.

Já sob o ponto de vista da camada arquitectural, existem várias formas de obter essas duas formas de paralelismo (uma delas já foi mencionada anteriormente). Essas formas são:

Instruction-level Parallelism: como o próprio nome indica, ILPaplica paralelismo ao nível da instrução.

Ou seja, um processador super-escalar pode, através da ajuda do compilador, gerar as instruções de

forma a poder tirar partido depipelining, utilizando simultaneamente vários componentes do CPU.

Também é possível utilizar outras estratégias comospeculativeeout-of-order execution.

Arquitecturas vectoriais: as arquitecturas vectoriais são arquitecturas que têm os seus registos

organiza-dos como vectores. As arquitecturas vectoriais pertencem à classe de computadoresSIMDsegundo

a taxonomia de Flynn [Flynn, 1966]. Ao contrário das arquitecturas escalares, estas possuem um ins-truction setque permite aplicar uma mesma instrução a múltiplos registos simultaneamente. Desta

forma, as arquitecturas vectoriais permitem obterDLP, uma vez que vários dados são processados

ao mesmo tempo. Inicialmente as arquitecturas vectoriais eram representadas por processadores vectoriais mas com a evolução tecnológica dos processadores escalares a utilização de processado-res vectoriais caiu em desuso. Actualmente este tipo de arquitectura é repprocessado-resentada pelas extensões

SIMDpresentes na maioria dosCPUsactuais e pelosGraphic Processing Units (GPUs).

Thread-level Parallelism: permite obter, através de múltiplasthreads, tantoDLPcomoTask-level

Paralle-lism, uma vez que cada thread pode operar sobre o mesmo conjunto de dados ou simplesmente

executar tarefas distintas.

2.2.1 SIMD Instruction Set Extensions

As extensõesSIMD, são um tipo de instruções que permitem emular a arquitecturaSIMD utilizando

uma série de registos especiais para o efeito, permitindo assim a obtenção de DLP. Inspirados pelos

computadores vectoriais, os fabricantes de processadores começaram a introduzir extensões SIMD nos

seusinstruction setsde modo a aumentar o desempenho das aplicações multimédia. Apesar deste ter sido o objectivo inicial, estas instruções podem ser aplicadas a outras áreas como por exemplo a paralelização de cálculos sobre matrizes e vectores.

(32)

optimi-CAPÍTULO 2. REVISÃO DA LITERATURA

zação de primitivas criptográficas devido ao potencial de paralelismo que pode ser aproveitado uma vez

que estão presentes na maioria dos processadores modernos [Bernstein and Schwabe, 2012][Götzfried,

2013][Bos et al., 2014][Izu and Takagi, 2004]. A utilização de instruçõesSIMD é tradicionalmente tida

como proveitosa para a protecção contra ataquesside-channel, nomeadamenteDifferential Power Analysis

(DPA), uma vez que a computação simultânea de vários valores reduz a correlação entre o comportamento

físico do processador e os valores a ser computados.

Actualmente, este tipo de instruções está presente na maioria dos processadores modernos. Assim

sendo, são uma fonte de paralelismo atractiva, principalmente quando se está a reformular um software

para correr num dispositivo onde ohardwarenão pode ser substituído facilmente.

O modelo de programação deste tipo de instruções assenta na utilização de bibliotecas específicas

ou pela utilização directa de assembly. No entanto, existem actualmente compiladores que permitem a

vectorização automática de código de forma a tirar partido destas instruções. Uma vantagem da utilização

de instruções SIMD é que o programador pode tirar partido de paralelismo sem deixar de raciocinar de

uma forma sequencial sobre a tarefa a cumprir.

2.2.2 Paralelismo Thread-level

Como o próprio nome indica, Thread-level Parallelismpermite obter paralelismo através da utilização

dethreads. No entanto, é preciso ter em atenção que podem existir aplicaçõesmulti-threadque não tirem

partido de paralelismo. Exemplo disso é quando se utilizathreadsem arquitecturas uni-processador. Neste

caso, as threadscompetem entre si por um recurso computacional obtendo assim concorrência, dando

a aparência de paralelismo (exceptuando o caso da utilização de Simultaneous Multithreading (SMT)).

Portanto, para que se possa obter verdadeiramente paralelismo através de threads, é necessário que a

arquitectura onde o programa está a executar possua mais que uma unidade de processamento e que as

diferentesthreadssejam atribuídas a cada uma delas. Este tipo de arquitecturas é normalmente apelidada

deMultiple Instruction Multiple Data (MIMD).

Asthreadspodem ser definidas como diferentes fios de execução a correr no interior dos processos,

podendo desempenhar diferentes tarefas. Ao contrário dos processos, as threads partilham o mesmo

espaço de endereçamento, pelo que este facto pode ser uma possível fonte de problemas se não for tido

em conta aquando da programação de um programamulti-threaded.

(33)

2.2. TÉCNICAS DE OPTIMIZAÇÃO

Thread-level Parallelismé normalmente utilizado para obterTask-level Parallelismmas também pode ser

utilizado para atingirData-level Parallelism. No entanto, a granularidade — representativa da quantidade

de trabalho a processar — deve ser suficientemente grande para que o desempenho obtido justifique o

overheadgerado na criação dasthreads.

2.2.2.1 Arquitecturas multi-core

Os multi-processadores podem ter dois tipos de organização de memória. Memória partilhada, onde todos os processadores têm igual acesso a uma memória central, ou memória distribuída onde cada processador tem a sua memória associada. Normalmente, o tipo de organização de memória utilizado num processador está relacionado com o número de processadores que possui. Sendo que os multi-processadores com menor número de multi-processadores geralmente utilizam memória partilhada, e os que têm um maior número de processadores utilizam memória distribuída como forma de diminuir a latência de acesso que poderia existir com um grande número de processadores a aceder à mesma memória.

As arquitecturas multi-core são um tipo de arquitectura multi-processador onde os processadores —

chamadoscores— se encontram no mesmochip. Na sua relação com o sistema operativo, os diferentes

coressão vistos como processadores individuais e atribui a cada um deles asthreadsque vão sendo

cria-das. Estescorespodem ter as mesmas ou diferentes características originando plataformas homogéneas

ou heterogéneas, respectivamente. Nos processadoresmulti-corea memória é partilhada, no entanto, a

or-ganização dascachesjá é variável consoante os modelos/fabricantes. As diferenças vão desde o número

decaches existentes ao tipo de acesso (se são privadas ou partilhadas). Normalmente o primeiro nível decache(L1) é privado e os seguintes níveis podem ser privados ou partilhados. A utilização de caches

privadas tem como vantagem a velocidade de acesso, tanto por proximidade como por exclusividade, mas causa o problema de manutenção de coerência dos dados. Por este motivo é necessária a existência de

protocolos que garantam a coerência dos dados nas diferentescaches.

2.2.2.2 OpenMP

OOpen Multi-Processing (OpenMP)4 é umaApplication Programming Interface (API)de programação

paralela orientada para o contexto de memória partilhada, que pode ser utilizada em C/C++ e Fortran. O

modelo de programação por trás do OpenMP é ofork-join. Inicialmente existe umamaster threada partir

(34)

CAPÍTULO 2. REVISÃO DA LITERATURA

da qual se criam outras —slave threads— assim que o programa encontra uma região paralela. No final

das regiões paralelas asslave threadssão terminadas e apenas amaster threadcontinua o processamento.

O OpenMP é composto por um conjunto de directivas de compilação (pragmasem C) que indicam as

secções de código que se deseja paralelizar, por variáveis de ambiente para definição de parâmetros de

funcionamento e por uma biblioteca que suporta a criação e gestão de threads, o que o torna simples

de usar. Esta simplicidade de utilização permite que o código possa ser paralelizado incrementalmente, não havendo necessidade de uma remodelação total do código de uma só vez. A isto acresce que as directivas de paralelismo são apenas lidas pelo pré-processador e ignoradas pelo compilador. Assim sendo, é possível a coexistência da versão sequencial e paralela no mesmo ficheiro e executada cada uma de acordo com a necessidade. Estas características tornam o OpenMP uma ferramenta bastante atractiva

para o desenvolvimento desoftwareparalelo.

(35)

2.3. COMPUTAÇÃO ALGÉBRICA

2.3 Computação algébrica

Grande parte da criptografia moderna assenta em conceitos matemáticos abstractos como por exemplo os corpos finitos, as curvas elípticas ou os grupos cíclicos. Desta forma, os criptógrafos necessitam de ter acesso a ferramentas que lhes permitam raciocinar e trabalhar sobre este tipo de noções matemáticas. Actualmente, existem diversas plataformas dedicadas à computação algébrica que o possibilitam. Alguns exemplos destas plataformas são o SageMath, Wolfram Mathematica e Magma. Destas referidas, o

Sage-Math é a única que éopen-source, tendo sido a motivação por trás da sua criação a disponibilização de

uma alternativaopen-sourceàs outras existentes.

Apesar destas plataformas possibilitarem a fácil manipulação de noções matemáticas de alto-nível, estas foram idealizadas para o plano científico, não sendo o seu objectivo a criação de executáveis de alto-desempenho que possam ser utilizados noutros dispositivos. Na procura por maior desempenho com-putacional é necessário recorrer a bibliotecas de nível inferior. Nas secções seguintes apresentam-se duas

bibliotecas para este efeito, aNumber Theory Library (NTL)e aGNU Multiple Precision Arithmetic Library

(GMP).

2.3.1 NTL

ONTL5, é uma biblioteca de alta-performancepara teoria dos números, como o próprio nome indica.

Está desenvolvida em C++ e pode ser utilizada em múltiplas plataformas e afirma serthreadeexception

safe.

Sendo uma biblioteca de teoria dos números, oNTLdisponibiliza estruturas de dados e algoritmos para

lidar com:

• aritmética sobre inteiros de comprimento arbitrário; • aritméticafloating-pointde precisão arbitrária;

• aritmética polinomial sobre os inteiros e corpos finitos; • redução de base de reticulados;

• álgebra linear (vectores e matrizes) sobre os inteiros, corpos finitos, e floating-point de precisão

arbitrária.

(36)

CAPÍTULO 2. REVISÃO DA LITERATURA

O NTLafirma, na sua páginaweb, ser uma das bibliotecas com a aritmética polinomial mais rápida

que existe, tendo esta obtido valoresrecordna factorização polinomial e determinação da ordem de curvas

elípticas. Também afirma ter uma das melhores implementações da redução de reticulados, tendo até já sido utilizada para quebrar a segurança de vários criptossistemas.

ONTLtem a particularidade de poder ser compilado em conjunto com aGMP(cf. § 2.3.2) para poder

utilizar internamente as suas funções de forma a obter melhor desempenho no global.

A suaAPIé simples, existindo uma classe representativa de cada tipo de dados matemático existente,

apetrechada com os métodos necessários para operar sobre os respectivos tipos de dados. Existem tam-bém outros módulos que permitem operar sobre grupos de tipos de dados mais simples, como é o caso das matrizes e vectores.

2.3.2 GMP

AGMP6é uma biblioteca de aritmética de precisão arbitrária desenvolvida em C no âmbito do projecto

GNU. No entanto também disponibiliza classeswrapperpara C++. Permite aritmética de precisão arbitrária

sobre inteiros, racionais e números de vírgula flutuante. Uma vez que pertence ao projecto GNU, a sua portabilidade está mais limitada. As plataformas para onde esta biblioteca está mais direccionada são as plataformas baseadas em unix como o GNU/Linux, Mac OS X/Darwin, BSD, Solaris, etc. No entanto, existem casos de compilações funcionais em Windows. A biblioteca foi desenvolvida com o intuito de

ser o mais rápida possível e para tal faz uso de códigoassemblyaltamente optimizado que é programado

manualmente para uma grande variedade de processadores, trocado assim simplicidade por eficácia. Outra característica interessante que é utilizada para aumentar o desempenho é a escolha dos algoritmos de

cálculo com base no tamanho dos operandos, eliminando/reduzindo desta forma ooverheadque poderia

surgir.

OGMPestá organizado por diferentes módulos para operar sobre os tipos de números referidos

anteri-ormente. Estes módulos podem ser divididos em módulos de alto-nível e baixo-nível. Os módulos alto-nível são os seguintes:

mpz: tipo de dados e aritmética sobre inteiros com sinal.

mpq: tipo de dados e aritmética para operar sobre números racionais. As funções mpz também podem

6https://gmplib.org/

(37)

2.3. COMPUTAÇÃO ALGÉBRICA

ser aqui utilizadas se aplicadas ao numerador e denominador em separado.

mpf: tipo de dados e aritmética para operar sobre número de vírgula flutuante.

O módulo de baixo-nível que existe é o mpn. As suas operações possuem um baixo grau deoverhead

e essas operações são utilizadas na maioria das implementações das funções dos outros módulos. Este módulo opera sobre inteiros de tamanho dowordsizedos registos e é de difícil utilização pois não cuida da

(38)
(39)

Capítulo 3

Compilador CAO

3.1 Arquitectura do compilador

O compilador CAO recebe comoinputum ficheiro CAO e transforma-o numa implementação

correspon-dente em C. O processo de compilação entre estes dois pontos está dividido em três partes: o front-end,

o middle-end e o back-end como se pode ver na figura3.1.

Nofront-endocorre a análise sintáctica dos ficheiros recebidos, da qual resulta uma representação

abs-tracta do código, ou seja, umaAbstract Syntax Tree (AST). É sobre esta representação que são verificadas

as regras de sintaxe da linguagem e deste processo resulta uma nova AST anotada que é utilizada nas

fases seguintes.

Código

CAO Front-end Middle-end Back-end

Código gerado

Compilador CAO

Figura 3.1: Arquitectura do Compilador CAO

Na fase domiddle-end, antes da geração do código C, é aplicado um conjunto de transformações àAST

(40)

CAPÍTULO 3. COMPILADOR CAO

mais fácil conversão para código C. Estas transformações ocorrem numa determinada ordem formando cinco etapas. A primeira etapa é a de expansão, que é uma etapa opcional, e o seu objectivo é fazer ounrolling dos ciclos existentes no código. Depois ocorre a avaliação onde são avaliadas as expressões que podem ser computadas estaticamente e posteriormente simplificadas através de propriedades dos

operadores (e.g. idempotência, cancelamento). Na etapa de simplificação são aplicadas transformações

sobre o código CAO de modo a gerar um formato de código intermédio mais simples e que se

asseme-lha aoThree-Address Code (TAC). Segue-se a optimização onde é inferido oControl Flow Graph (CFG)do

código e ao qual são aplicadas transformações de optimização na forma Static Single Assignment (SSA).

Por fim, são aplicadas medidas de protecção contra ataquesside-channel[Barbosa and Page, 2005]. Tais

contra-medidas garantem que o código gerado de duas funções potencialmente vulneráveis (por indicação do utilizador) é indiscernível fazendo-as executar as mesmas operações. Para tal, as instruções são

reor-denadas e, se necessário, são inseridas instruções postiças. É importante fazer notar, em primeiro lugar,

que esta etapa tem que ser obrigatoriamente a última pois qualquer transformação que fosse aplicada depois poderia inutilizar estas contra-medidas e em segundo, que estas apenas aumentam a resiliência das implementações e não garantem segurança contra todo o tipo de ataques.

Quando o código chega ao back-end é convertido para C. Como já foi referido, um dos objectivos

do CAO é que os executáveis resultantes da compilação possam ser aplicados a uma vasta gama de

plataformas/dispositivos sem que isso tenha custos no desempenho do software. Por isso, depois da

conversão, acontece a “linkagem” desse código a uma biblioteca estática onde estão definidos os tipos e

as operações implementadas. Desta forma, é possível que para cada plataforma/dispositivo esteja definida uma biblioteca diferente que vai ao encontro das suas características. Para cada uma destas bibliotecas

estáticas existe um ficheiro de configuração que permite explicitar as escolhas de implementação (e.g. o

return dos resultados por valor ou por referência) e quais as operações implementadas, o que permite que existam implementações incompletas e funcionais. Um excerto deste ficheiro pode ser consultado no

anexoAonde se pode ver as operações definidas para o módulo unsigned bitstrings. Mais uma vez,

a optimização do código é relegada para o compilador e não aplicada sobre o algoritmo inicial protegendo assim a sua genericidade.

3.2 Tipos de dados CAO

Um tipo de dados CAO pode pertencer a uma de duas categorias: escalares (ouprimitivos) ou

deriva-dos (ouconstruídos). Os escalares são os tipos básicos do CAO e os derivados são tipos mais complexos

(41)

3.2. TIPOS DE DADOS CAO

que são construídos à custa dos mais básicos. Os tipos de dados que permitem a construção de tipos

deri-vados são os vectores, matrizes e estruturas, representados pelaskeywordsvector, matrix e struct,

respectivamente. Como se pode adivinhar, o tipo de dados vector permite criar vectores, e o matrix matrizes, do mesmo tipo de dados. Já o tipo de dados struct, à semelhança do seu equivalente em C, permite criar um novo tipo de dados com vários tipos de dados diferentes, ou seja, enquanto que os vectores e as matrizes exigem que os tipos de dados contidos sejam do mesmo tipo, com as estruturas pode ser criado um tipo de dados novo que no seu interior contenha, por exemplo, uma matriz e um inteiro como se pode ver nalisting1.

1 def m : matrix[10,10] of int; 2 def v : vector[100] of int;

3 def s : struct[ def a: matrix[10,10] of int; def b: int];

Listing 1: Inicialização de matrizes, vectores e estruturas

Dentro dos tipos de dados escalares pode-se, de certa forma, considerar duas classes: uma de tipos simples e outra de complexos que permitem a construção de tipos matemáticos. Na primeira temos os booleanos, os inteiros de máquina (registo) e asbit strings(signedeunsigned), e na segunda os inteiros e o tipo de dados mod.

O tipo de dados booleano (bool), como em todas as linguagens de programação que o suportam, é um tipo de dados que pode tomar o valor de verdadeiro ou falso. Sobre ele estão implementadas operações de comparação e de álgebra booleana.

Os inteiros de máquina (register int), são inteiros que são convertidos para inteiros do C, o que quer

dizer que o seu tamanho máximo é limitado pela arquitectura do processador,i.e., para um processador de

32 bits, o maior inteiro (signed) que é possível representar é (231− 1) = 2147483647. Estes inteiros são

normalmente utilizados como contadores de ciclos e como índices, para evitar ooverheadda inicialização

dos outros inteiros mais complexos para este tipo de tarefas. O CAO suporta operações de comparação e aritmética simples sobre este tipo de inteiros.

Para fechar esta classe de tipos escalares mais simples, apresenta-se as bit strings. As bit strings

representam sequências de bits de tamanho n, sendo o tamanho definido aquando da inicialização da

variável e onde 0 representa o bit menos significativo e n− 1 o mais significativo. Estas podem existir

sobre o estado deunsigned(unsigned bits) ousigned(bits) onde a única diferença está na forma de

conversão para inteiro, ou seja, se éunsignedou complemento para dois, respectivamente. Sobre este tipo

(42)

CAPÍTULO 3. COMPILADOR CAO

como operações de selecção, atribuição e concatenação de bits. É importante fazer notar que, sendo as

bit stringsde tamanho limitado, as operações são fechadas sobre esse tamanho. Por exemplo, a aplicação

de umshiftde 8 bits para a esquerda a umabit stringde tamanho 8 resulta em 0.

O CAO permite a utilização de inteiros de precisão arbitrária (int). Se com os register ints o

tamanho máximo das variáveis era limitado peloword sizedo compilador, no caso dos ints, o seu tamanho

apenas é limitado pelo tamanho de memória disponível. Este tipo de inteiros é bastante útil particularmente em criptografia de chave-pública, onde muitas vezes é necessário raciocinar sobre números com centenas de dígitos.

O tipo mod permite operações sobre inteiros modulo um determinado número. Em criptografia, o

uso deste tipo de operações é bastante comum, como por exemplo no protocolo Diffie-Hellman [Diffie and

Hellman, 2006], na função RSA [Jonsson and Kaliski, 2003] e em criptografia sobre curvas elípticas [Cohen et al., 2012]. Através deste tipo dados é possível construir corpos finitos, extensões a corpos finitos e anéis polinomiais, tornando possível a utilização destas noções matemáticas como variáveisfirst-class.

1 typedef a := mod[2]; 2 typedef b := mod[3**9];

3 typedef c := mod[ a<X> / X**8 + X**4 + X**3 + X + 1];

Listing 2: Inicialização de mods

3.3 Exemplos de código CAO

Para mais facilmente se poder perceber a sintaxe CAO, é apresentada nalisting3uma implementação

em CAO da função curve25519 [Bernstein, 2006], cuja sintaxe é agora brevemente explicada. Em

primeiro lugar há a definição de typedefs que, tal como noutras linguagens, dão umaliasa um tipo de

dados para facilitar a sua utilização. Pode-se observar a existência debit stringssem sinal, um vector de

bit string, uma extensão de um corpo finito (Fp) e um tipo derivado baptizado de MontRep composto por dois Fp. A função recebe como parâmetros uma chave secreta de 255 bits e um Fp e devolve um Fp.

Dentro da função é possível ver-se a declaração de variáveis dos tipos definidos em cima através da

utilização dakeyword def e do operador (:) para indicar o tipo da variável. Também se pode ver que o

acesso às variáveis interiores da estrutura é feito através do operador (.), tal como em C. Resta apenas salientar o seq, que representa um ciclo e que permite expressar de uma forma mais natural a sua condição

(43)

3.3. EXEMPLOS DE CÓDIGO CAO 1 typedef byte := unsigned bits[8];

2 typedef unpacked := unsigned bits[256];

3 typedef packed := vector[32] of unsigned bits[8];

4 typedef skey := unsigned bits[255];

5

6 typedef Fp := mod[2**255-19];

7 typedef MontRep := struct [ def x : Fp; def z : Fp; ];

8 9 (...)

10

11 def curve25519(n : skey, base : Fp) : Fp {

12

13 def mth, mp1th, one : MontRep;

14 one.x := base; 15 one.z := [1]; 16 mth := one; 17 mp1th := doubleMont(one); 18 19 seq i := 253 to 0 by -1 { 20 if (n[i] == 1) { 21 mth := addMont(mth, mp1th, one); 22 mp1th := doubleMont(mp1th); 23 } else { 24 mp1th := addMont(mth, mp1th, one); 25 mth := doubleMont(mth); 26 } 27 } 28 if (mth.z == [0]) { 29 return [0]; 30 } else { 31 return (mth.x/mth.z); 32 } 33 } 34 35 (...)

Listing 3: Implementação CAO da função curve25519

(44)

CAPÍTULO 3. COMPILADOR CAO

3.4 Exemplo de compilação

De forma a poder-se exemplificar o processo de compilação de um ficheiro CAO e consequente geração da mesma implementação em C, sem que se torne muito extenso, pode-se utilizar a implementação CAO

do algoritmobubble sortapresentado em4.

1 typedef int_vector := vector [10] of int ;

2

3 def bubble_sort ( v : int_vector ) : int_vector {

4 def temp : int ;

5 seq i := 8 to 0 by -1 { 6 seq j := 0 to i { 7 if ( v[j] > v[j+1]) { 8 temp := v[j]; 9 v[j] := v[j+1]; 10 v[j+1] := temp; 11 } 12 } 13 } 14 return v ; 15 }

Listing 4: Implementação CAO da funçãobubble sort

O compilador CAO é invocado utilizando o seguinte comando: $ cao comp --config=default.plat bubblesort.cao

Onde default.plat representa o ficheiro de configuração da plataforma previamente mencionado (onde estão especificados os tipos de dados e respectivas operações existentes), e bubblesort.cao

representa o ficheiro CAO onde está implementado o algoritmo. No excerto de código 5 podemos ver o

ficheiro C gerado pelo compilador1.

Na primeira linha de5podemos observar o typedef que é atribuído no respectivo código CAO,

pas-sando assim CAO_int_vector a descrever um novo tipo que representa um vector de inteiros, que é utilizado no resto da implementação. Posteriormente podemos observar a transformação da função em

várias chamadas a funções C que estão definidas no back-end. É importante adicionar que são sempre

criadas duas funções adicionais, uma init() e outra dispose() que fazem, respectivamente, a de-claração/inicialização e libertação de variáveis globais. Neste exemplo não existem variáveis externas à função, pelo que as funções init() e dispose() estão vazias.

1Note-se que osincludese as funções init() e dispose() foram removidos para poupar espaço

(45)

3.4. EXEMPLO DE COMPILAÇÃO 1 typedef CAO_vector CAO_int_vector;

2 CAO_RES c_bubble_sort(CAO_vector _r0, CAO_int_vector c_v)

3 { 4 CAO_REF c_t1028; 5 CAO_bool c_t1025; 6 CAO_int c_t1027; 7 CAO_int_decl(&c_t1027); 8 CAO_int c_t1026; 9 CAO_int_decl(&c_t1026); 10 CAO_int c_t1018; 11 CAO_int_decl(&c_t1018); 12 CAO_rint c_t1019; 13 CAO_int c_t1020; 14 CAO_int_decl(&c_t1020); 15 CAO_bool c_t1021; 16 CAO_rint c_t1022; 17 CAO_int c_t1023; 18 CAO_int_decl(&c_t1023); 19 CAO_rint c_t1024; 20 CAO_int c_temp;

21 CAO_int_decl(&c_temp);

22 CAO_rint c_i; 23 CAO_rint_init(c_i, 8); 24 CAO_rint_gte(c_t1025, c_i, 0); 25 while (c_t1025) 26 { 27 CAO_rint c_j; 28 CAO_rint_init(c_j, 0); 29 CAO_rint_lte(c_t1025, c_j, c_i); 30 while (c_t1025) 31 { 32 CAO_vector_select(c_t1018, c_v, c_j); 33 CAO_rint_add(c_t1019, c_j, 1); 34 CAO_vector_select(c_t1020, c_v, c_t1019); 35 CAO_int_assign(c_t1026, c_t1018); 36 CAO_int_assign(c_t1027, c_t1020); 37 CAO_int_gt(c_t1021, c_t1026, c_t1027); 38 if (c_t1021) 39 { 40 CAO_vector_select(c_temp, c_v, c_j); 41 CAO_rint_add(c_t1022, c_j, 1); 42 CAO_vector_select(c_t1023, c_v, c_t1022); 43 c_t1028 = CAO_vector_ref(c_v, c_j); 44 CAO_int_assign(c_t1028, c_t1023); 45 CAO_rint_add(c_t1024, c_j, 1); 46 c_t1028 = CAO_vector_ref(c_v, c_t1024); 47 CAO_int_assign(c_t1028, c_temp); 48 } 49 CAO_rint_add(c_j, c_j, 1); 50 CAO_rint_lte(c_t1025, c_j, c_i); 51 } 52 CAO_rint_add(c_i, c_i, -1); 53 CAO_rint_gte(c_t1025, c_i, 0); 54 } 55 CAO_vector_assign(_r0, c_v); 56 CAO_int_dispose(c_temp); 57 CAO_int_dispose(c_t1023); 58 CAO_int_dispose(c_t1020); 59 CAO_int_dispose(c_t1018); 60 CAO_int_dispose(c_t1026); 61 CAO_int_dispose(c_t1027); 62 return CAO_OK; 63 }

(46)

CAPÍTULO 3. COMPILADOR CAO

3.5 Estrutura da biblioteca back-end genérica

Todos os tipos de dados CAO referidos na secção3.2têm o seu tipo de dados mapeado nesta biblioteca,

como se pode observar no excerto de código5. Passa-se agora a explanar, sem entrar em profundo detalhe,

quais são e como estão implementados esses tipos de dados.

CAO_bool: a implementação em C do tipo de dados bool do CAO.

Na verdade, as funções implementadas sobre este tipo de dados são apenas macros sobre operações

bitwisesobre inteiros C de forma a emular operações booleanas.

CAO_rint: a implementação em C do tipo de dados register int do CAO.

Assim como os tipos de dados bool, a implementação dos register ints são apenas macros

sobre as operações sobre o tipo de dados int do C. Existe apenas a adição da função decastde

CAO_rint para CAO_int.

CAO_int: a implementação em C do tipo de dados int do CAO.

Osints estão implementadas recorrendo à classe ZZ doNTL. A classe ZZ é a classe que implementa

as operações sobre inteiros de precisão arbitrária. Portanto, a implementação do tipo de dados e das suas operações é bastante directa.

CAO_ubits: a implementação em C do tipo de dados unsigned bitstring do CAO.

Asubitssão representadas como inteiros e fazem-se acompanhar de uma variável que indica o seu

tamanho, para que se possa fazer oresizenecessário após algumas das operações. Assim sendo,

fazem uso da classe ZZ doNTLassim como os inteiros de precisão arbitrária.

CAO_sbits: a implementação em C do tipo de dados signed bitstring do CAO.

As sbits estão implementadas de forma semelhante às anteriores, i.e., fazendo uso também da

classe ZZ e utilizando uma variável indicando o tamanho dabitstring.

CAO_mod: a implementação em C do tipo de dados mod do CAO.

As operações com módulo estão implementadas utilizando a classe ZZ_p doNTL. A classe ZZ_p

permite o raciocínio sobre inteirosmodulop, onde p representa o valor passado aquando da

decla-ração da variável.

CAO_modpol: a implementação em C do tipo de dados que alberga polinómios modulares.

Imagem

Figura 3.1: Arquitectura do Compilador CAO
Figura 4.1: Cifra por blocos em counter mode
Tabela 5.2: Rácios entre original em O2 e novas implementações em O3 para o AES
Tabela 5.3: Percentagem de utilização dos source files na função AES compilada em O0 com a  imple-mentação vec32 e com input de 4096 bytes
+7

Referências

Documentos relacionados