Mais um blog inútil.

Serious-Business

Setembro 16, 2009

Multiplicação de Polinómios

Arquivado em: coding, serious-business, useless — dcoder @ 20:17

Depois de um breve hiato, volto a este Blol colocar pensamentos inúteis. E hoje pensei na multiplicação de polinómios - em particular, polinómios com coeficientes mod 2, i.e.

$$\mathbb{F}_2[x]$$

. Como é que representamos estes polinómios?

Dado que cada coeficiente apenas pode ser 0 ou 1, podemos utilizar apenas um bit para estes. Assim, se tivermos um polinómio

$$x^3 + x + 1$$

, representamos o mesmo por 0b1011 ou 0x0B.

A adição de polinómios é trivial - consiste unicamente em aplicar a operação XOR, dado que XOR é adição mod 2. A multiplicação pode ser efectuada como efectuamos multiplicações de inteiros, excepto que ignoramos todos os transportes ( carries). Podemos então implementar uma multiplicação de polinómios como:

u32 polymul(u32 a, u32 b)
{
  u32 z = 0;
  while(b != 0)
  {
    if(b&1 != 0)
      z ^= a;
    b >>= 1; a <<= 1;
  }
  return z;
}

Isto, no entanto, contém branching e operações condicionais. Se usarmos C++, podemos criar uma função sem qualquer loop que nos permita efectuar a mesma operação:

template<u32 N>
inline u32 mul(u32 a, u32 b)
{
  return (a & -(b&1)) ^ mul<N-1>(a<<1, b>>1);
}

template<>
inline u32 mul<0>(u32 a, u32 b)
{
  return 0;
}

O parâmetro N, utilizado na instanciação da função, corresponde ao grau máximo dos polinómios de entrada. No caso de um inteiro de 32 bits, a soma dos graus de a e b não poderá exceder 32, de forma a evitar overflows.

Agosto 29, 2009

Faço saites!!!

Arquivado em: serious-business — falco @ 15:51

Um anúncio (http://www.net-empregos.com/detalhe_anuncio_livre.asp?REF=805336) da GFI Portugal e alguém no #loonixhalp da WoW.net deram-me uma ideia para bater toda a concorrência. A partir de agora faço saites de interweb, por um pacote de bolachas maria (daqueles pequeninos de 4 bolachas) e um copo de água (para ajudar a engolir).

Agosto 25, 2009

Os Simples - Ao vivo nos BVT

Arquivado em: serious-business — falso @ 08:54

Ora viva amigos.

Há dias veio aqui um senhor ao blol comentar no post d’Os Simples com alta conhecimento obscuro sobre eles que amei, o que me fez querer partilhar com vocês outra gravação que tinha em casa. Desta vez um concerto nos Bombeiros Voluntários da Trafaria (pelo menos é o que a cassete diz) nos anos 70. A qualidade desta gravação não é a melhor, mas é o que se arranja de algo assim tão raríssimo.

01 - Intro
02 - Retalhos da Vida - Alcione
03 - (The System of) Doctor Tarr and Professor Fether - The Alan Parsons Project
04 - Love is in the Air - John Paul Young
05 - Europa - Carlos Santana (incomplete)
06 - One For You, One For Me - La Bionda
07 - Trem das Onze - Adoniran Barbosa
08 - Give a Little Bit - Supertramp
09 - Outro

Sem mais demoras, aqui está.

Espero que alguem aprecie. E se quiserem fazer download está alojado no archive.org: Os Simples - Ao vivo nos BVT

Agosto 7, 2009

Mário Gamito

Arquivado em: drama, lulz, serious-business — devnull @ 17:33

Para mais tarde recordar.
gamito

Side-channel

Arquivado em: drama, fail, serious-business — dcoder @ 03:40

Além dos ataques comuns existentes para atacar algoritmos criptográficos, alguns destes já mencionados anteriormente (e.g. AES-256), existe uma classe de ataques que não explora deficiências algorítmicas, mas sim deficiências de implementação — Side-channel attacks.

A vasta maioria destes ataques tem como base o tempo que uma operação demora a executar; este tipo de ataques foi originalmente proposto para algoritmos baseados na exponenciação de inteiros, como RSA ou DSA. É fácil de perceber que quando se utiliza o método binário de exponenciação e o expoente é uma chave privada, o tempo de execução da exponenciação está directamente relacionado com o número de bits no expoente. Este tipo de ataques pode ser evitado através de blinding ou outros algoritmos de exponenciação, como as cadeias de Montgomery.

Estes ataques, naturalmente, estendem-se também aos grupos criados por curvas elípticas, onde a multiplicação de um ponto também está dependente de um valor privado. As soluções são semelhantes — de facto, existe uma representação especial de pontos desenhada para este efeito, a representação de Montgomery.

Estes ataques não se limitam a algoritmos assimétricos — o AES já foi várias vezes atacado devido a más implementações susceptíveis a ataques derivados às tabelas necessárias não caberem inteiramente em cache. Não apenas o AES é vítima deste tipo de ataques — alguns dos finalistas da eSTREAM também são vulneráveis, assim como outra cifra bastante utilizada, Camellia.

Como evitar estes ataques?

Para quem desenha cifras, é importante não permitir que o tempo de execução esteja de alguma forma dependente de chaves secretas; de preferência, o tempo de execução deveria ser sempre constante para qualquer entrada. Cifras mais recentes, como a Salsa20 ou Threefish utilizam apenas operações que são efectuadas em tempo constante, i.e. XOR, ADD, ROL.

No caso de cifras já existentes, pode ser possível criar implementações em tempo constante das mesmas. No caso do AES, é possível implementar, utilizando as extensões SSSE3, uma versão em tempo constante que cifra 16 blocos simultaneamente (bitslice). Os acessos à memória são trocados por operações booleanas que, devido a serem amortizadas por 16 blocos, acabam por se tornar mais rápidas.

Existem imensas outras instâncias de ataques causados por implementações com tempos de execução variáveis; existem ataques a implementações com gastos de energia variáveis!

A conclusão será que num sistema seguro, não apenas a qualidade dos algoritmos mas também da implementação dos mesmo são importantes para a segurança dos nossos dados.

Agosto 4, 2009

Blogosphere

Arquivado em: drama, fail, serious-business — devnull @ 09:54

Blogosphere is just multiplayer copy & paste.

Julho 31, 2009

Software livre? What's that?????

Arquivado em: drama, serious-business — gatuno @ 01:20

Ao que parece o nosso primeiro ministro nem sabe do que se trata quando se faz uma pergunta coerente. “Bem” fez ele.. deu a volta à pergunta para puxar a brasa à sua sardinha… 1 milhão de computadores… bla bla bla bla bla….. e não fomos nós, nós só demos o primeiro passo… mas graças a nós, todos têm aquilo que é preciso… bla bla bla bla bla… eu, eu, eu, eu… arrrrrrrrrrrgh!
Take a look:

Acho que este post foge um pouco ao “blog inútil”…(ou não.. com uma conversa daquelas….) lulz…

Junho 9, 2009

Curvas Elípticas

Arquivado em: drama, serious-business, useless — dcoder @ 23:41

Nos últimos anos, uma grande parte da investigação feita em criptografia de chave pública tem-se desviado dos grupos definidos sobre os números inteiros mod m, i.e.

$$\mathbb{Z}/m\mathbb{Z}$$

, para os grupos definidos por curvas elípticas sobre um corpo finito K, este geralmente

$$\mathbb{F}_p$$

ou

$$\mathbb{F}_{2^m}$$

— E/K. Não só a investigação, mas também a própria indústria tem-se virado para as curvas elípticas em detrimento dos algoritmos mais clássicos - o NIST definiu em 1999 uma série de curvas e corpos standard e em 2006 anunciou a Suite B, um conjunto de algoritmos aprovados para proteger dados até ao nível ‘Top Secret’.

A que é que se deve esta mudança?

Tem a ver com a estrutura, ou falta dela, existente nas curvas elípticas. No caso do Diffie-Hellman ou RSA clássico, estamos a trabalhar nos números inteiros mod N, e o problema fundamental a resolver é a factorização de um produto de dois primos ou o logaritmo discreto. Ambos estes problemas podem ser resolvidos com essencialmente o mesmo algoritmo — NFS. A complexidade deste é:

$$O(e^{\frac{64}{9}^{1/3} (\log n)^{1/3} (\log \log n)^{2/3}})$$

A razão pela qual este algoritmo funciona em tempo sub-exponencial relativamente a n está relacionada com a estrutura de

$$\mathbb{Z}$$

— todos os inteiros podem ser representados como um produto de factores primos. Associando esta propriedade às propriedades dos logaritmos e exponenciações e à distribuição dos números primos, conseguimos resolver logaritmos discretos em menos tempo do que os métodos genéricos, como o Pollard’s Rho. Nos grupos das curvas elípticas sobre um corpo, para parâmetros razoavelmente bem escolhidos, não são conhecidos algoritmos sub-exponenciais ou polinomiais para resolver o problema onde reside a segurança do algoritmo.

Antes de mais, vamos definir uma curva elíptica. Na prática, uma curva elíptica é definida pela equação de Weierstrass num corpo finito arbitrário:

$$y^2 + a_1 xy + a_3 y = x^3 + a_2 x^2 + a_4 x + a_6$$

Os coeficientes

$$a_1 \ldots a_6$$

definem, portanto, a curva. Tipicamente, quando a curva é definida sobre o corpo finito dos inteiros mod p, p primo, apenas

$$a_4$$

e

$$a_6$$

são diferentes de 0 (uma excepção sendo a Curve25519 do djb).

Naturalmente, os elementos deste grupo serão as soluções da equação acima — dado que existem duas soluções para cada x, os elementos são definidos como pontos P = (x, y) na curva. Ainda nos falta, no entanto, a operação de grupo - a adição. Esta é ligeiramente diferente para o caso em que P != Q e P = Q, quando queremos obter P+Q = (x3, y3), P=(x1, y1), Q=(x2, y2):

Adição

$$x3 = \frac{y2-y1}{x2-x1}^2 - x1 - x2 \\ y3 = \frac{y2-y1}{x2-x1}(x1-x3)-y1$$

Duplicação

$$x3 = \frac{3x1 + a_4}{2y1}^2 - 2x1 \\ y3 = \frac{3x1 + a_4}{2y1}(x1-x3)-y1$$

Existe ainda um ponto especial,

$$\infty$$

, que representa a identidade relativamente à adição, i.e.

$$P+\infty = \infty+P = P$$

.

Tendo o grupo definido, surge a operação relevante para fins criptográficos: a multiplicação. Esta é definida como:

$$nP = P+P+\ldots +P$$

i.e. n vezes o ponto P.

Outro conceito importante é a ordem de uma curva — o número total de pontos que nela existem — #E. Este valor deve ser sempre ou primo ou bastante próximo de um primo, de forma a evitar ataques conhecidos.

Uma propriedade importante da multiplicação em curvas elípticas é que é bastante fácil calcular

$$n P$$

, mas dado

$$P$$

e

$$n P$$

é extremamente difícil obter

$$n$$

. Este é o designado problema do logaritmo discreto em curvas elípticas. O melhor algoritmo conhecido para calcular este logaritmo é o Pollard’s Rho, que tem uma complexidade de

$$O({\#E}^{1/2})$$

A diferença entre o logaritmo discreto nas curvas elípticas e nos inteiros mod n é esta - não existe conceito de divisibilidade entre pontos, i.e. não existem pontos ‘primos’ nos quais seja possível decompor um ponto arbitrário. Esta (falta de) estrutura provê uma grande vantagem a nivel de segurança relativamente aos grupos mais clássicos.

Para fazer uma comparação directa entre os tamanhos dos corpos finitos necessários para uma segurança de 80 bits, podemos aplicar directamente as complexidades acima. Com curvas elípticas, precisamos de um corpo finito de tamanho

$${2^{80}}^2 = 2^{160}$$

, i.e. 160 bits. Com e.g. Diffie-Hellman, precisamos de pelo menos 896 bits de chave —

$$e^{\frac{64}{9}^{1/3} (\log 2^{896})^{1/3} (\log \log 2^{896})^{2/3}} \approx 2^{81.85}$$

. Note-se que isto são apenas complexidades assimptóticas; o custo real é muito diferente e geralmente muito, muito maior que o apresentado nestas fórmulas simplificadas.

Portanto, podemos ver como as curvas elípticas podem ser apelativas para quem implementa sistemas criptográficos — maior segurança por tamanho de chave, maior rapidez (derivado das chaves menores), menor estrutura dentro do grupo, minimizando as vias de ataque. Até agora parece ser em quase todos os sentidos uma opção superior ao RSA, ElGamal, DH tradicionais.

PS. Este post foi essencialmente elaborado para testar o

$$\LaTeX$$

no blol.

Junho 5, 2009

Threefish - Part II

Arquivado em: assembly, coding, serious-business, useless — dcoder @ 22:28

Como prometido aos meus fiéis leitores ontem, hoje vamos ver como optimizar a cifra Threefish de 250 ciclos por byte (em 32 bit) para algo mais útil na prática.

Em primeiro lugar, e de longe mais relevante, vamos eliminar todos os loops e branches da função. Estes não apenas ocupam recursos que podiam estar a ser usados na cifração em si, mas impedem o uso de todas as unidades de execução. Em segundo lugar, queremos eliminar os acessos à memória desnecessários e manter o máximo de dados possível em registos: substituímos os arrays por variáveis para facilitar esta tarefa ao compilador. Infelizmente, não existem registos para toda a gente (em x86) e vamos sempre ter de aceder à stack para guardar alguns dados. Mas não faz mal. Assim sendo, ficamos com uma função do género:

void ThreefishEncrypt(const ThreefishKey ks, u8 *in, u8 *out)
{
	u64 *p = (u64*)in;
	u64 v0, v1, v2, v3;
	u64 f0, f1, f2, f3;
	int i, d;
		
	v0 = p[0];
	v1 = p[1];
	v2 = p[2];
	v3 = p[3];
	
	/* start of round 0 */
	v0 += ks->subkey[0][0];
	v1 += ks->subkey[0][1];
	v2 += ks->subkey[0][2];
	v3 += ks->subkey[0][3];
	
	MIX(f1, f0, v1, v0, 5);
	MIX(f3, f2, v3, v2, 56);
	
	v0 = f0; v1 = f3; v2 = f2; v3 = f1;
	
	/* start of round 1 */
	MIX(f1, f0, v1, v0, 36);
	MIX(f3, f2, v3, v2, 28);
	
	v0 = f0; v1 = f3; v2 = f2; v3 = f1;
	
	/* start of round 2 */
	MIX(f1, f0, v1, v0, 13);
	MIX(f3, f2, v3, v2, 46);

	v0 = f0; v1 = f3; v2 = f2; v3 = f1;
	......

	((u64*)out)[0] = v0 + ks->subkey[ROUNDS/4][0];		
	((u64*)out)[1] = v1 + ks->subkey[ROUNDS/4][1];	
	((u64*)out)[2] = v2 + ks->subkey[ROUNDS/4][2];	
	((u64*)out)[3] = v3 + ks->subkey[ROUNDS/4][3];	
}

Esta versão já é substancialmente mais rápida - passamos de 250 para 80 ciclos por byte. Em 64 bit passamos de 50 para 11!

Será que ainda podemos acelerar o processo?

A resposta é sim.

Recorrendo às instruções SSE2 presentes em essencialmente todos os CPUs x86 actuais, podemos cifrar 2 blocos simultaneamente por essencialmente o custo de um. Como é que isto é conseguido?

void ThreefishEncryptSSE2(const ThreefishKey ks, u8 *in, u8 *out)
{
	__m128i *p = (__m128i*)in;
	__m128i v0, v1, v2, v3;
	__m128i f0, f1, f2, f3;
	__m128i t0, t1, t2, t3;
	__m128i k0, k1, k2, k3;
		
	t0 = _mm_load_si128(&p[0]);
	t1 = _mm_load_si128(&p[1]);
	t2 = _mm_load_si128(&p[2]);
	t3 = _mm_load_si128(&p[3]);
	
	v0 = _mm_unpacklo_epi64(t0, t2);
	v1 = _mm_unpackhi_epi64(t0, t2);
	v2 = _mm_unpacklo_epi64(t1, t3);
	v3 = _mm_unpackhi_epi64(t1, t3);
	
	t0 = _mm_load_si128((__m128i*)&(ks->subkey[0][0]));
	t1 = _mm_load_si128((__m128i*)&(ks->subkey[0][2]));
	
	k0 = _mm_shuffle_epi32(t0, 0x44);
	k1 = _mm_shuffle_epi32(t0, 0xEE);
	k2 = _mm_shuffle_epi32(t1, 0x44);
	k3 = _mm_shuffle_epi32(t1, 0xEE);
	
	/* Round 0 */
	v0 = _mm_add_epi64(v0, k0);
	v1 = _mm_add_epi64(v1, k1);
	v2 = _mm_add_epi64(v2, k2);
	v3 = _mm_add_epi64(v3, k3);
	
	/* load next subkey */
	t0 = _mm_load_si128((__m128i*)&(ks->subkey[1][0]));
	t1 = _mm_load_si128((__m128i*)&(ks->subkey[1][2]));
	k0 = _mm_shuffle_epi32(t0, 0x44);
	k1 = _mm_shuffle_epi32(t0, 0xEE);
	k2 = _mm_shuffle_epi32(t1, 0x44);
	k3 = _mm_shuffle_epi32(t1, 0xEE);
	
	/* mix */
	f0 = _mm_add_epi64(v0, v1);
	f1 = _mm_xor_si128(_mm_xor_si128(_mm_slli_epi64(v1, 5),_mm_srli_epi64(v1, 64-5)), f0);
	f2 = _mm_add_epi64(v2, v3);
	f3 = _mm_xor_si128(_mm_xor_si128(_mm_slli_epi64(v3, 56),_mm_srli_epi64(v3, 64-56)), f2);
	
	/* permute */
	v0 = f0;
	v1 = f3;
	v2 = f2;
	v3 = f1;
	
	....

	/* Round 71 */
	/* mix */
	f0 = _mm_add_epi64(v0, v1);
	f1 = _mm_xor_si128(_mm_xor_si128(_mm_slli_epi64(v1, 59),_mm_srli_epi64(v1, 64-59)), f0);
	f2 = _mm_add_epi64(v2, v3);
	f3 = _mm_xor_si128(_mm_xor_si128(_mm_slli_epi64(v3, 50),_mm_srli_epi64(v3, 64-50)), f2);
	
	/* permute */
	v0 = _mm_add_epi64(f0, k0);
	v1 = _mm_add_epi64(f3, k1);
	v2 = _mm_add_epi64(f2, k2);
	v3 = _mm_add_epi64(f1, k3);
	
	// unpack
	t0 = _mm_unpacklo_epi64(v0, v1);
	t1 = _mm_unpacklo_epi64(v2, v3);
	t2 = _mm_unpackhi_epi64(v0, v1);
	t3 = _mm_unpackhi_epi64(v2, v3);
	
	// place holder
	p = (__m128i*)out;
	_mm_store_si128(&p[0], t0);
	_mm_store_si128(&p[1], t1);
	_mm_store_si128(&p[2], t2);
	_mm_store_si128(&p[3], t3);
}

Em primeiro lugar, carregamos os dois blocos para registos XMM. No entanto, quando fazemos isto, os inteiros de 64 bits dentro dos blocos ficam na ordem errada. Através da instrução PUNPCKLQDQ e PUNPCKHQDQ alteramos a ordem dos blocos de a0a1 a2a3 b0b1 b2b3 para a0b0 a1b1 a2b2 a3b3. Assim já podemos facilmente processar os dois blocos como se o estivessemos a fazer no caso acima, sem SSE2.

Ainda de notar é também o truque necessário para suportar o mesmo formato das subkeys: através de 2 loads e 4 shuffles, essencialmente extendemos a subchave a0 a1 a2 a3 para a0a0 a1a1 a2a2 a3a3.

Após as 72 iterações, temos de retornar os inteiros à ordem original, novamente com as instruções PUNPCKLQDQ e PUNPCKHQDQ. Após isto só nos resta armazenar as variáveis no buffer de saída.

Será que todo este trabalho vale a pena? Mais uma vez, sim. No modo CTR, passamos de 80 ciclos por byte (acima) para 26. Podemos ainda melhorar este número? Quebrando a compatibilidade com o formato das subchaves acima, podemos. Trocamos então 2 loads + 4 shuffles por 4 loads. Isto leva-nos ao resultado final de 19 ciclos por byte em 32 bit e 10 ciclos por byte em 64 bit. Isto não é óptimo, mas está bastante perto do óptimo num Core 2.

Para obter melhores desempenhos ainda, particularmente no modo CTR, poderíamos utilizar as variantes com blocos maiores do Threefish, e.g. 512 ou 1024 bits. Estas facilmente iriam parar aos 10 ou menos ciclos por byte em 32 bits. No entanto, blocos deste tamanho são incómodos para os tamanhos típicos de payloads cifradas - só para mensagens relativamente grandes é que faz sentido utilizar estas variantes.

O código utilizado para estas experiências está aqui. Não está bem comentado nem bem organizado - é apenas o resultado de experimentação. Divirtam-se.

Threefish

Arquivado em: coding, serious-business, useless — dcoder @ 03:54

Uma das formas de criar hash functions seguras passa por reutilizar uma cifra num ou noutro modo de operação especial. Entre estes encontram-se o modo Davies-Meyer, UBI, Miyaguchi-Preneel, etc.

Um dos candidatos ao SHA-3, o Skein, utiliza uma cifra nova em modo UBI — Threefish. Dada esta conversa toda sobre o AES-256 ser inseguro, e como não temos já cifras seguras suficientes, implementei esta cifra em C a partir das especificações acessíveis aqui. Existem vários tamanhos para o bloco e chave do Threefish — implementei apenas o mais realista para cifração de dados: bloco de 256 bits, chave de 256 bits.

Além do bloco e chave de 256 bits, esta cifra aceita um parâmetro adicional, chamado “tweak”. Tipicamente para cifração de dados não é muito útil, mas isto muda de figura quando queremos criar modos de operação para cifração sector a sector, como os utilizados pelo TrueCrypt e companhia. A cifra é bastante simples: 72 iterações de adições, xor e rotações em inteiros de 64 bits. A cada 4 iterações é misturada uma subchave derivada da chave com o bloco. No fim de cada iteração a ordem dos inteiros é alterada através de uma permutação. Se acederem às especificações da cifra, irão notar que esta não segue a estrutura típica de uma Feistel Network — trata-se de uma Substitution-Permutation Network. As vantagens deste design serão abordadas noutra altura mais conveniente.

Uma implementação canónica que aparenta respeitar as especificações encontra-se aqui. O desempenho desta implementação é miserável — ~250 cpb em 32 bits, ~50 cpb em 64 bit quando a cifra é usada em CTR mode.

O próximo post abordará como optimizar esta cifra para níveis usáveis de desempenho.