WEB3DEV

Cover image for Funções Hashing em Solidity Usando Keccak256
Fatima Lima
Fatima Lima

Posted on • Atualizado em

Funções Hashing em Solidity Usando Keccak256

O algoritmo keccak256 (família SHA-3) processa o hash de um input para um output de comprimento fixo. O input pode ser um string de comprimento variável ou um número, mas o resultado será sempre um tipo de dado fixo bytes32. É uma função hash criptográfica de uma via, que não pode ser decodificada de trás para frente. Consiste de 64 caracteres (letras e números) que podem ser expressos como números hexadecimais.

A compilação, ou resultado de um hash não foi feito para ser descriptografado com uma chave como algoritmos de criptografia (ex. criptografia de chave pública). A melhor maneira de recuperar um código hash é verificá-lo como resultado de uma função hash. A metodologia de força bruta poderia ser uma alternativa, mas não é o método mais rápido para resolver um código hash para recuperar a mensagem original. Esse é um meio de proteção contra ataques.
 
Princípio de Hash Básico

Dado um input de um string, por exemplo “Hello World”, passe para uma função hash usando o keccak256. O resultado seria:

Hello World -> keccak256 ->
592fa743889fc7f92ac2a37bb1f5ba1daf2a5c84741ca0e0061d243a2e6707ba
Enter fullscreen mode Exit fullscreen mode

O string “Hello World” não é o mesmo que “hello world”. Se fazemos um hash “hello world”, teremos um resultado totalmente diferente.

hello world -> keccak256 ->
47173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad
Enter fullscreen mode Exit fullscreen mode

A menor modificação ou mudança no string resulta em mudança considerável na compilação do hash. Entretanto, pode-se ter inputs dimensionados aleatoriamente, mas eles sempre resultarão no mesmo output.
Pegue, por exemplo, dois strings:
“O lobo marrom ligeiro pulou em cima do cachorro preguiçoso”
“Olá”
Agora vamos fazer um hash em cada string usando o keccak256 e vejamos o resultado (Note: As aspas não fazem parte do input).

The quick brown fox jumped over the lazy dog -> keccak256 ->
a82db2ff0b312da9d856a75ba260e9955f0fe467307cd7793521624d11921365
Hello -> keccak256 ->
06b3dfaec148fb1bb2b066f10ec285e7c9bf402ab32aa78a5d38e34566810cd2
Enter fullscreen mode Exit fullscreen mode

Agora vamos comparar os resultados de cada string.

a82db2ff0b312da9d856a75ba260e9955f0fe467307cd7793521624d11921365
06b3dfaec148fb1bb2b066f10ec285e7c9bf402ab32aa78a5d38e34566810cd2
Enter fullscreen mode Exit fullscreen mode

Como podemos ver, ambos têm um comprimento fixo (64 caracteres) embora um string seja mais comprido que o outro.
Agora vamos ver as funções de hash utilizada usando o keccak256 nos smart contracts da Solidity.
 
Codificando Dados de Input
Na Solidity (linguagem de programação utilizada na Ethereum), uma função hash deve primeiro ter o input de dados codificado. Alguém pode dizer que não seja necessário codificar os dados e somente faça um hash dos dados brutos (dados originais) diretamente. Pode-se fazer isso, mas não é a melhor prática principalmente se você quer proteger os dados (o objetivo do hashing em primeiro lugar).
A palavra-chave abi.encodePacked é usada com demonstrações para codificação de input de dados. Ela é utilizada com as seguintes condições:
Tipos mais curtos que 32 bytes são concatenados diretamente, sem preenchimento ou extensão de assinatura.
Tipos dinâmicos são codificados no local e sem o comprimento
Elementos do tipo array são preenchidos, mas ainda assim, codificados no local.

abi.encodePacked( <data input> )
Enter fullscreen mode Exit fullscreen mode

Isso significa que tipos dinâmicos são codificados no local sem comprimento enquanto tipos estáticos não serão preenchidos se tiverem menos do que 32 bytes.
Por exemplo:

abi.encodePacked("AAAA")
0x41414141
Enter fullscreen mode Exit fullscreen mode

Agora podemos usar nossa função hash utilizando a palavra-chave keccak256 no seguinte método:
keccak256(abi.encodePacked( <data input> ))
Aqui está um exemplo:

function hash(string memory _string) public pure returns(bytes32) {
    return keccak256(abi.encodePacked(_string));
}
Enter fullscreen mode Exit fullscreen mode

Vamos atribuir um valor para string de “Hash Este String”. O resultado que teremos é:

0x5f82559096154cc5c8b38479da101b29c56995ade10aa89e4940b68ef1002567
Enter fullscreen mode Exit fullscreen mode

Observe o prefixo 0x antes da compilação do hash. Isto faz com que o comprimento do string inteiro tenha 66 caracteres de comprimento. O prefixo 0x é adicionado para indicar que é um string hexadecimal.

A codificação foi feita para dar ao programador mais controle de como os dados poderiam ser codificados. Antes, era o compilador que realizava essa função, mas pode ser problemático porque isso pode causar o que se chama de colisões.
 
Evitando Colisões
Existe também uma técnica mais complexa para fazer a codificação, chamada abi.encode. A função abi.encodePacked foi feita para ser mais simples e mais compacta para codificar dados. A função abi.encode pode ser útil quando vem evitar colisões nas funções hash.
Uma colisão pode acontecer quando dois inputs diferentes produzem o mesmo output. Isto pode parecer impossível, mas pode acontecer de forma menos esperada. Pegue por exemplo os seguintes inputs:

(AAA, BBB) -> AAABBB        
(AA, ABBB) -> AAABBB
Enter fullscreen mode Exit fullscreen mode

Eles deveriam ser diferentes um do outro, mas como concatenados como uma única string, eles realmente produzirão o mesmo output.
Com o abi.encode, codificar um string resulta em:

abi.encode("AAAA")
0x0000000000000000000000000000000000000000000000000000000000000020
0x0000000000000000000000000000000000000000000000000000000000000004
0x4141414100000000000000000000000000000000000000000000000000000000
Enter fullscreen mode Exit fullscreen mode

É um comprimento de 96 bytes ou 3 palavras. Observe que foram preenchidos zeros enquanto no abi.encodePacked isso não aconteceu.
Agora vamos usar a função keccak256 e ver porque as colisões podem acontecer. Primeiro, use abi.encodePacked. Vamos usar dois strings como variáveis (não-estado) _string1 e _string2.

function collisionExample(string memory _string1, string memory _string2)
public pure returns (bytes32) {
    return keccak256(abi.encodePacked(_string1, _string2));
}
Enter fullscreen mode Exit fullscreen mode

Vamos usar um exemplo simples (Exemplo 1):
_string1 = AAA
_string2 = BBB

O resultado quando concatenamos os strings e usamos a função hash é:

0xf6568e65381c5fc6a447ddf0dcb848248282b798b2121d9944a87599b7921358
Enter fullscreen mode Exit fullscreen mode

Agora vamos mudar os valores para o seguinte (Exemplo 2):
_string1 = AA
_string2 = ABBB

Eis o resultado:

0xf6568e65381c5fc6a447ddf0dcb848248282b798b2121d9944a87599b7921358
Enter fullscreen mode Exit fullscreen mode

Obtemos o mesmo resultado, exatamente igual ao do exemplo anterior. A razão disso é que quando se concatena os strings, eles resultam exatamente no mesmo string:

AAABBB
Enter fullscreen mode Exit fullscreen mode

Independente da ordem dos caracteres. Pode-se ter _string1 definido para A e _string2 definido para AABBB e o resultado será o mesmo. Isso é uma colisão e em sistemas de produção isso será um problema.
Agora vamos usar a mesma função,mas dessa vez com o abi.encode.

function collisionExample2(string memory _string1, string memory _string2)
public pure returns (bytes32) {
    return keccak256(abi.encode(_string1, _string2));
}
Enter fullscreen mode Exit fullscreen mode

Com o primeiro exemplo (Exemplo 1) o resultado é:

0xd6da8b03238087e6936e7b951b58424ff2783accb08af423b2b9a71cb02bd18b
Enter fullscreen mode Exit fullscreen mode

É bem diferente de quando se usa o abi.encodePacked. Agora, a hora da verdade (Exemplo 2), vai resultar o mesmo hash?

0x54bc5894818c61a0ab427d51905bc936ae11a8111a8b373e53c8a34720957abb
Enter fullscreen mode Exit fullscreen mode

Obtemos um output totalmente diferente que evita a ocorrência da colisão. Se existir a probabilidade de inputs resultarem em outputs que pode causar colisão, é recomendado o uso do abi.encode em vez do abi.encodePacked. Há outras técnicas que os desenvolvedores podem utilizar (ex. adicionar um valor aleatório à string concatenada), mas essa técnica é a mais comum.
 
Sinopse
Hashing é um aspecto importante da segurança criptográfica para carteiras digitais e transações na blockchain. Pode ajudar a criar um valor específico determinístico de um input hash, aplicado a esquemas Commit-Reveal e para assinaturas criptográficas compactas.
Usar o keccak256 é apenas um exemplo das várias funções hash. O protocolo Ethereum usa o keccak256 em sua rede com um mecanismo de consenso chamado Ethash. Ele tem um papel importante na produção de blocos e na proteção deles na blockchain.

15 de fevereiro

Esse artigo foi escrito por Vincent Tabora e traduzido por Fátima Lima. Você pode ler o original aqui.

Top comments (0)