Introdução à criptografia e às funções Hash

As criptomoedas, como o Bitcoin, utilizam-se de tecnologias criptográficas como criptografia de chave publica,e funções de Hash. Neste notebook vamos nos familiarizar com estes conceitos que nos serão úteis em nosso estudo da bitcoin e outras criptomoedas.

Funções de Hash Criptográfico

As funções de Hash criptográfico são o componentes mais fundamental da maioria das blockchains pois é a "cola" que garante a coesão, correção, imutabilidade e outras características fundamentais das blockchains.

Uma função de Hash é uma função que apresenta algumas características básicas:

  1. é fácil de calcular para qualquer tipo de dado (baixo custo computacional)
  2. É impossível ou extremamente difícil de inverter, isto é, de encontrar o input correspondente a um hash.
  3. É extremamente improvável que dois inputs diferentes gerem o mesmo valor de hash.

A biblioteca padrão do Python nos oferece uma biblioteca com implementações das principais funções de hash, a Hashlib.


In [2]:
import hashlib
hashlib.algorithms_available


Out[2]:
{'BLAKE2b512',
 'BLAKE2s256',
 'MD4',
 'MD5',
 'MD5-SHA1',
 'RIPEMD160',
 'SHA1',
 'SHA224',
 'SHA256',
 'SHA384',
 'SHA512',
 'blake2b',
 'blake2b512',
 'blake2s',
 'blake2s256',
 'md4',
 'md5',
 'md5-sha1',
 'ripemd160',
 'sha1',
 'sha224',
 'sha256',
 'sha384',
 'sha3_224',
 'sha3_256',
 'sha3_384',
 'sha3_512',
 'sha512',
 'shake_128',
 'shake_256',
 'whirlpool'}

Criptografia com curvas elípticas

A Bitcoin se utiliza de curvas elípticas para suas necessidades criptográficas. Mais precisamente, utiliza o algoritmo de assinatura digital por curvas elipticas (ECDSA). A ECDSA envolve três componentes principais: uma chave pública, uma chave privada e assinatura.

A Bitcoin usa uma curva elíptica específica chamada secp256k1. A função em si parece inofensiva: $$y^2=x^3+7$$ onde $4a^3 +27b^2 \neq 0$ (para excluir curvas singulares). $$\begin{array}{rcl} \left\{(x, y) \in \mathbb{R}^2 \right. & \left. | \right. & \left. y^2 = x^3 + ax + b, \right. \\ & & \left. 4a^3 + 27b^2 \ne 0\right\}\ \cup\ \left\{0\right\} \end{array}$$

Porém, em aplicações criptográficas, esta função não é definida sobre os números reais, mas sobre um campo de números primos: mais precisamente ${\cal Z}$ modulo $2^{256} - 2^{32} - 977$.

\begin{array}{rcl} \left\{(x, y) \in (\mathbb{F}_p)^2 \right. & \left. | \right. & \left. y^2 \equiv x^3 + ax + b \pmod{p}, \right. \\ & & \left. 4a^3 + 27b^2 \not\equiv 0 \pmod{p}\right\}\ \cup\ \left\{0\right\} \end{array}

Para um maior aprofundamento sobre a utilização de curvas elítpicas em criptografia leia este material.

Encriptando textos

A forma mais simples de criptografia é a criptografia simétrica, na qual se utilizando de uma chave gerada aleatóriamente, converte um texto puro em um texto encriptado. então de posse da mesma chave é possível inverter a operação, recuperando o texto original. Quando falamos em texto aqui estamos falando apenas de uma aplicação possível de criptografia. Na verdade o que será aplicado aqui para textos, pode ser aplicado para qualquer sequencia de bytes, ou seja para qualquer objeto digital.


In [6]:
from Crypto.Cipher import DES3
from Crypto import Random

Neste exemplo vamos usar o algoritmo conhecido como "triplo DES" para encriptar e desencriptar um texto. Para este exemplo a chave deve ter um comprimento múltiplo de 8 bytes.


In [7]:
chave = b"chave secreta um"
sal = Random.get_random_bytes(8)
des3 = DES3.new(chave, DES3.MODE_CFB, sal)

Note que adicionamos sal à ao nosso encriptador. o "sal" é uma sequência aleatória de bytes feitar para dificultar ataques.


In [9]:
texto = b"Este e um texto super secreto que precisa ser protegido a qualquer custo de olhares nao autorizados."
enc = des3.encrypt(texto)
enc


Out[9]:
b'\xf3\xa70I\xe1\x8bw\xb9=*\xe2\t\xda\xab\x16\x9e\xf1qK\xa9\xe3~\r\xda\xca\xf8\xd1\x18\xaf\xf9O!\x83]D\xb5t\xec\t=qk\xcd\xe1\xbc\t\x14\xeb~\x95\xf6\x03\xf0<\xda\xe3\xef\xd5\xcb\x81\x00$\xe0\xc9\xb9\x93\x94\xdcmb$>\x92\x82\x19 \xe8\xc7[\r\x7f\xa9\x14N\x7f|\x00\xaeu1\r\xa1cvP\xf8\xdaa\xfc\x1c'

In [12]:
des3 = DES3.new(chave, DES3.MODE_CFB, sal)
des3.decrypt(enc)


Out[12]:
b'Este e um texto super secreto que precisa ser protegido a qualquer custo de olhares nao autorizados.'

Um dos problemas com esta metodologia de encriptação, é que se você deseja enviar este arquivo encriptado a um amigo, terá que encontrar uma forma segura de lhe transmitir a chave, caso contrário um inimigo mal intencionado poderá desencriptar sua mensagem de posse da chave. Para resolver este problema introduzimos um novo métodos de encriptação:

Criptografia de chave pública

Nesta metodologia temos duas chaves: uma pública e outra privada.


In [7]:
from Crypto.PublicKey import RSA
from Crypto.Random import get_random_bytes
from Crypto.Cipher import AES, PKCS1_OAEP

Vamos criar uma chave privada, e também encriptá-la, no caso de termos que mantê-la em algum lugar onde possa ser observada por um terceiro.


In [10]:
senha = "minha senha super secreta."
key = RSA.generate(2048) # Chave privada
print(key.exportKey())
chave_privada_encryptada = key.exportKey(passphrase=senha, pkcs=8, protection="scryptAndAES128-CBC")


b'-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEAwrLXrG9o+WTuJhy8ByXtu8mo60H/Z9+vUs9aoDDjLYKJgjJe\negiKGe6fUNo3Nvn+MEAu1UMbhwkk9UDvwXp/um9+dr7bSv6y2KgUY28dJgm1RIRt\nsQ1u1qlOBoajfjbbk2fxi5ZUFmcZQlHAZkkerL7SqMxQELtwDg/StrpM03kpnSBk\nMLx48f51uZ/W6phzcPiZDKUXT7Vrh+VJIafhh2dBf7vak0MvfQuwyzLTaU3uiYOl\nDoAdc4SKfeZ+8e1zRy+0+1N6P2YnHMLqDCcNAKkt+UKTefEFa5hp1kPKvANAuCqE\nZZ1ghH0PBJbKaZcSoVI0vddI1C3bNfyHY4/qQQIDAQABAoIBABdTrhtuzbjzqsTO\nNIj2tmEFbEOjiKI9XpghV85RjSZquWbr+f3NAB7TRCs/udXQZfAKAJJ0k9u98Luh\nqBgaQStpKFG51DRMB8mFecvgCDICf8FRzIKmBV2as0p83yHEGNK0ZXA/9psohxI3\nEUIwCIjR5BNtRzp5+pb0tl5GO+YqLGj5kzvAefhZlOYglmFZfUTbAvNKpNLKuPDx\n4Sd70e6UxqAPkSlDwVm0XvAuiXfK0kj2yJZkXTeK8BA9eZ4QstuoFl1JOjupzcoS\nFs2LYWcr/mIj6d4JmikY44cRycafRCXGu+YKaKeocSRh8wO06JkE+I/JWoXaKzka\ng1xx1HECgYEAzE3Jcpc5lnv0izT0YN+SqVpMv16lNz0j+BDYuV+VI3vwbkGbUCOy\nOWAlROOlaGU5T3AQSejwT/LmkSl3xa1pDVJfL4mMuiy+ASg8tUwpP4Kcik9tC7M+\nLH+M52ugHKLVe3hTRCKTzwwU4PIhl/ZbT+vkc7vUNsnKRfD+YQQer9sCgYEA8/ba\n6h90WkiKEixU+yGWl6w+1Ojk4ElbNUTg72/ARoIuiYhCq0XN6MNhgpQKTK8sKI+z\nNSiHuts/ss2qhcUHTW1ZpkN+QayK43Ll5oOrao5vkmEej6rwIoRl3XIpEIvoblxO\np4X+hpQmKkNkZLxEW56vj46IzG0WvCf8DFMDpxMCgYANDjt9UySe9TTfYHbtadwZ\nbmfRIt8z9HhD9e+jiPQi3Fy07wRZzq4o9HftOw7q6O0MooCn+HbC5SbOx/ObckVw\nUH88rnGLxCgwMr4OZ9uK73HJdOHnCYFDnrMGZt5Idi9mQH4xsHP5mzAKX+69o0Sa\nityCeNJK3gJLDedzk6vSWQKBgCIDS5gSlJU1wdIwzhRJJYa14/ipE6fVclrl5Hpd\nWTA7XCOelLTuQXvvKXwcCaeRUJXDGxgRM4MwqBvUoq6Nvqvjz/jnNgYP+Tkmvri5\nqL9tZk7BLZKYIWyeitkp3mHJL7mbMvgWLVsz8nI3/RoySVGAuTdJhvcBSocnB/Pr\n6vlzAoGBAIY5wwAubN1QkJeWZESJDa60TVIPXyzHrZEuDGFQ3nsf7azNuTeFkpvS\nxE9gw6PfpixPYPtbNfJ6MBHFyuyH1WhSQYsk0AxnAqk+fJOdyQ0IK69mz9S1Z8yy\nOP08WgH4856SlfIRRSx3yG6hIo6cwWRySuVgAULB1Jd2kMQ1SBs6\n-----END RSA PRIVATE KEY-----'

In [17]:
publica = key.publickey()
publica.exportKey()


Out[17]:
b'-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwrLXrG9o+WTuJhy8ByXt\nu8mo60H/Z9+vUs9aoDDjLYKJgjJeegiKGe6fUNo3Nvn+MEAu1UMbhwkk9UDvwXp/\num9+dr7bSv6y2KgUY28dJgm1RIRtsQ1u1qlOBoajfjbbk2fxi5ZUFmcZQlHAZkke\nrL7SqMxQELtwDg/StrpM03kpnSBkMLx48f51uZ/W6phzcPiZDKUXT7Vrh+VJIafh\nh2dBf7vak0MvfQuwyzLTaU3uiYOlDoAdc4SKfeZ+8e1zRy+0+1N6P2YnHMLqDCcN\nAKkt+UKTefEFa5hp1kPKvANAuCqEZZ1ghH0PBJbKaZcSoVI0vddI1C3bNfyHY4/q\nQQIDAQAB\n-----END PUBLIC KEY-----'

De posse da senha podemos recuperar as duas chaves.


In [18]:
key2 = RSA.import_key(chave_privada_encryptada, passphrase=senha)
print(key2==key)
key.publickey().exportKey() == key2.publickey().exportKey()


True
Out[18]:
True

Agora podemos encriptar algum documento qualquer. Para máxima segurança, vamos usar o protocolo PKCS#1 OAEP com a algoritmo RSA para encriptar assimetricamente uma chave de sessão AES. Esta chave de sessão pode ser usada para encriptar os dados. Vamos usar o modo EAX para permitir a detecção de modificações não autorizadas.


In [31]:
data = "Minha senha do banco é 123456".encode('utf8')
chave_de_sessão = get_random_bytes(16)

# Encripta a chave de sessão com a a chave RSA pública.
cifra_rsa = PKCS1_OAEP.new(publica)
chave_de_sessão_enc = cifra_rsa.encrypt(chave_de_sessão)

# Encrypta os dados.
cifra_aes = AES.new(chave_de_sessão, AES.MODE_EAX)
texto_cifrado, tag = cifra_aes.encrypt_and_digest(data)

O destinatário da mensagem pode então desencriptar a mensagem usando a chave privada para desencriptar a chave da sessão, e com esta a mensagem.


In [32]:
# Desencripta a chave de sessão com a chave privada RSA.
cifra_rsa = PKCS1_OAEP.new(key)
chave_de_sessão = cifra_rsa.decrypt(chave_de_sessão_enc)

# Desencripta os dados com a chave de sessão AES
cifra_aes = AES.new(chave_de_sessão, AES.MODE_EAX, cifra_aes.nonce)
data2 = cifra_aes.decrypt_and_verify(texto_cifrado, tag)
print(data.decode("utf-8"))


Minha senha do banco é 123456

In [ ]:


In [ ]: