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
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
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
Agora vamos comparar os resultados de cada string.
a82db2ff0b312da9d856a75ba260e9955f0fe467307cd7793521624d11921365
06b3dfaec148fb1bb2b066f10ec285e7c9bf402ab32aa78a5d38e34566810cd2
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> )
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
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));
}
Vamos atribuir um valor para string de “Hash Este String”. O resultado que teremos é:
0x5f82559096154cc5c8b38479da101b29c56995ade10aa89e4940b68ef1002567
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
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
É 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));
}
Vamos usar um exemplo simples (Exemplo 1):
_string1 = AAA
_string2 = BBB
O resultado quando concatenamos os strings e usamos a função hash é:
0xf6568e65381c5fc6a447ddf0dcb848248282b798b2121d9944a87599b7921358
Agora vamos mudar os valores para o seguinte (Exemplo 2):
_string1 = AA
_string2 = ABBB
Eis o resultado:
0xf6568e65381c5fc6a447ddf0dcb848248282b798b2121d9944a87599b7921358
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
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));
}
Com o primeiro exemplo (Exemplo 1) o resultado é:
0xd6da8b03238087e6936e7b951b58424ff2783accb08af423b2b9a71cb02bd18b
É bem diferente de quando se usa o abi.encodePacked. Agora, a hora da verdade (Exemplo 2), vai resultar o mesmo hash?
0x54bc5894818c61a0ab427d51905bc936ae11a8111a8b373e53c8a34720957abb
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.
Latest comments (0)