Getting identical Argon2i hashes with C reference, Rust, Kotlin/Java, python/libsodium & javascript
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:
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())