Getting identical Argon2i hashes with C reference, Rust, Kotlin/Java, python/libsodium & javascript

published Nov 30, 2017 11:15   by admin ( last modified Dec 06, 2017 08:57 )

How do you know the Argon2i library you are using is giving you the correct hash? One way to boost confidence is to see if independent implementations yield the same result.

Below first going through configuration differences, then showing an example of an identical configuration for the five Argon2i implementations, yielding the same hash:

cc894b3e1345fcc3f36c0f9b808021160ec34a97441987ffb7a775bb0c34d5e8

Tested:

  • Web interface to a pure javascript/wasm implementation at http://antelle.net/argon2-browser/, based on a slightly modified version of the reference implementation
  • PyNaCl-1.2.0 for Python3, using libsodium
  • The argon2 command line tool in Ubuntu 17.10, version 0~20161029-1, based on the reference implementation
  • Pure Java implementation by Andreas Gadermaier, version 0.1
  • Rust implementation rust-argon2 by SRU-Systems, version 0.3.0

Different numerical versions of Argon2 yield different results

All above implementations above conform to the hash yielded according to version 0.13 of Argon. However only one of the libraries explicitly state so, rust-argon2. Kudos to SRU systems! You can change the version of Argon2 used in rust-argon2, and with version 0.10 you get this hash (for the same parameters):

ffd0caa5b4df1587f06033e6f1d0060e75742cbad0f7193f4bc97297d3977d76

The Argon2 paper states in its change log for what's new in version 1.3:

• The blocks are XORed with, not overwritten in the second pass and later

It seems to me that being explicit with what version you are using, or even be aware of that there are different version yielding different results, is a pretty big deal. Your password checking or keystretching may otherwise become quite infuriating and stressful.

There is another Rust implementation of Argon2 Argon2rs, that yields the 0.10 hash in the version available at crate.io: 0.2.5 at the time of this writing . There is work underway to conform argon2rs also to the 0.13 standard.

Configuration

The following parameters give the same hash for all five listed implementations. The argon2i version was used, since it is available in all.

Salt

Impl. Name Format
argon2-browser salt string
PyNacl salt byte string b"abc"
argon2 CLI <first argument> string
Kotlin/Java <second arg to hash> "abc".toByteArray
rust-argon2 <second arg to hash_raw> byte string b"abc"

 

Iterations

Impl. Name Format
argon2-browser iterations number
PyNacl OPS_LIMIT integer
argon2 CLI -t string
Kotlin/Java setIterations integer
rust-argon2 time_cost integer

Password

Impl. Name Format
argon2-browser password string
PyNacl password byte string b"abc"
argon2 CLI <STDIN> string
Kotlin/Java <first arg to hash> "abc".toByteArray
rust-argon2 <first arg to hash_raw> byte string b"abc"

For the argon2 CLI:

echo -n 'password' | argon2 […]

Memory

Impl. Name Format
argon2-browser memory number in kibibytes
PyNacl memlimit integer in bytes
argon2 CLI -m string, power of 2 in kibibytes
Kotlin/Java setMemory integer, power of 2 in kibibytes
rust-argon2 mem_cost integer in kibibytes

Memory, as it is called in argon2-browser is called memlimit in pynacl. If you set it to 1024 in argon2-browser, because it is in kibibytes, it should be 1048576 in pynacl, which uses bytes as unit. Google can do the conversion for you.

The argon2 command line tool wants kibibyte powers of 2, so "10" will set it to 2¹⁰ kib which is 1024 kibibytes. Same for Java/Kotlin version.

Octets output length

Impl. Name Format
argon2-browser Hash length number
PyNacl <first argument> integer
argon2 CLI -l string
Kotlin/Java   set to 32 always, maybe?
rust-argon2 hash_length integer

 

Output in hex format

Impl. How
argon2-browser always in hex
PyNacl key.hex()
argon2 CLI -e
Kotlin/Java always in hex
rust-argon2 hexify yourself

In argon2-browser, it is always in hex, in pynacl it is the .hex() method on the result object. In the argon2 it is hex by default but can be changed to raw bytes with the -r flag. In Java/Kotlin version, it is hex.

Examples of an identical configuration of all

Argon2-web screenshot:

Argon2-web
Click to view full-size image…
Size: 34.1 kB

Pure java implemetation called from Kotlin example (thanks to Mikael Ståldal for help on this):

package se.webworks

import at.gadermaier.argon2.Argon2Factory

fun main(args: Array<String>) {
    val password = "masonit".toByteArray()
    val salt = "0123456789ABCDEF".toByteArray()
    val hash = Argon2Factory.create()
            .setIterations(8)
            .setMemory(10)
            .setParallelism(1)
            .hash(password, salt)
    println(hash)
}

 

You can also call the java jar directly with command line arguments:

echo -n "masonit" | java -jar argon2-0.1.jar 0123456789ABCDEF -i -m 10 -p 1 -t 8

Rust example with rust-argon2:

extern crate argon2;
extern crate hex;

use argon2::{Config, ThreadMode, Variant, Version};
fn main() {

let password = b"masonit";
let salt = b"0123456789ABCDEF";
let config = Config {
    variant: Variant::Argon2i,
    version: Version::Version13,
    mem_cost: 1024,
    time_cost: 8,
    lanes: 1,
    thread_mode: ThreadMode::Parallel,
    secret: &[],
    ad: &[],
    hash_length: 32
};

let hash = argon2::hash_raw(password, salt, &config).unwrap();
    let hex_string = hex::encode(hash);
    println!("{}", hex_string);

}

argon2 command line tool example:

echo -n 'masonit' | argon2 0123456789ABCDEF -t 8 -m 10

Python code example:

from nacl import pwhash

password = b'masonit'

kdf = pwhash.argon2i.kdf
salt = b'0123456789ABCDEF'

Alices_key = kdf(32, password, salt,
                 opslimit=8, memlimit=1048576 )
print(Alices_key.hex())