Argon2 constructs reference vectors¶
Since libsodium implements a different API for argon2 contructs
than the one exposed by the reference implementation available at
The password hash Argon2… <https://github.com/P-H-C/phc-winner-argon2/>,
the kats
data provided along to the reference implementation sources
cannot be directly used as test vectors in PyNaCl tests.
Therefore, we are using a python driver for the command line
argon2
, which can be built following the instruction in the
reference implementation sources.
Vector generation¶
The argondriver.py
requires setting, via the command line option
-x
, the path to the argon2 executable; and as a default generates
hex-encoded raw hash data on standard output.
Setting the -e
option on the command line allows generating
modular crypt formatted hashes.
The other command line options influence the minimum and maximum sizes
of generated parameters as shown in the driver’s command line help,
which is printed by inserting the -h
option in the command line.
To generate vector data files in tests/data
, the argondriver.py
have been called to generate password hashes with parameters compatible
with libsodium
’s implementation; in particular, the minimum operations
count must be 3 for argon2i
and 1 for argon2id
, and the salt
length must be 16 for raw hashes, and can vary for modular crypt formatted
hashes.
The full command lines used in generating the vactors are:
- for raw argon2i
python3 docs/vectors/python/argondriver.py \ -x ~/phc-winner-argon2/argon2 \ -c argon2i \ -s 16 -S 16 -p 8 -P 16 -m 14 -M 18 \ -l 18 -L 32 -t 3 -T 5 -n 10 \ -w tests/data/raw_argon2id_hashes.json
- for raw argon2id
python3 docs/vectors/python/argondriver.py \ -x ~/phc-winner-argon2/argon2 \ -c argon2id \ -s 16 -S 16 -p 8 -P 16 -m 14 -M 18 \ -l 18 -L 32 -t 1 -T 5 -n 10 \ -w tests/data/raw_argon2id_hashes.json
- for modular crypt argon2i
python3 docs/vectors/python/argondriver.py \ -x ~/phc-winner-argon2/argon2 \ -c argon2i -e \ -s 8 -S 16 -p 8 -P 16 -m 14 -M 18 \ -l 18 -L 32 -t 3 -T 5 -n 10 \ -w tests/data/modular_crypt_argon2id_hashes.json
- for modular crypt argon2id
python3 docs/vectors/python/argondriver.py \ -x ~/phc-winner-argon2/argon2 \ -c argon2id -e \ -s 8 -S 16 -p 8 -P 16 -m 14 -M 18 \ -l 18 -L 32 -t 1 -T 5 -n 10 \ -w tests/data/modular_crypt_argon2id_hashes.json
Code for the vector generator driver¶
The code for argondriver.py
is available inside
the docs/vectors/python
directory of PyNaCl distribution
and can also be directly downloaded from
argondriver.py
.
#!/usr/bin/python
#
import argparse
import json
import random
import string
import subprocess
import sys
class argonRunner:
GOODCHARS = string.ascii_letters + string.digits
def __init__(self, args):
self.exe = args.exe
self.mnsaltlen = args.mnsaltlen
self.mnpwlen = args.mnpwlen
self.mndgstlen = args.mndgstlen
self.mnmem = args.mnmem
self.mniters = args.mniters
self.mxsaltlen = args.mxsaltlen
self.mxpwlen = args.mxpwlen
self.mxdgstlen = args.mxdgstlen
self.mxmem = args.mxmem
self.mxiters = args.mxiters
self.encoded = args.encoded
self.rng = random.SystemRandom()
self.version = args.version
self.construct = args.construct
self.maxcount = args.n
self.count = 0
def _runOnce(self, passwd, salt, dgst_len, maxmem, iters):
argv = [
self.exe,
salt.encode("ascii"),
"-t",
"{:2d}".format(iters),
"-m",
"{:2d}".format(maxmem),
"-l",
"{:3d}".format(dgst_len),
"-v",
self.version,
]
if self.encoded:
argv.append("-e")
mode = "crypt"
else:
argv.append("-r")
mode = "raw"
if self.construct == "argon2i":
argv.append("-i")
elif self.construct == "argon2d":
argv.append("-d")
elif self.construct == "argon2id":
argv.append("-id")
p = subprocess.Popen(
argv, stdin=subprocess.PIPE, stdout=subprocess.PIPE
)
out, err = p.communicate(passwd.encode("ascii"))
return dict(
passwd=passwd,
salt=salt,
dgst_len=dgst_len,
maxmem=2 ** maxmem,
iters=iters,
mode=mode,
pwhash=out.decode("ascii").rstrip(),
construct=self.construct,
)
def _genSalt(self):
sltln = self.rng.randint(self.mnsaltlen, self.mxsaltlen)
chrs = [self.rng.choice(self.GOODCHARS) for x in range(sltln)]
return "".join(chrs)
def _genPw(self):
pwln = self.rng.randint(self.mnpwlen, self.mxpwlen)
chrs = [self.rng.choice(self.GOODCHARS) for x in range(pwln)]
return "".join(chrs)
def __next__(self):
if self.count >= self.maxcount:
raise StopIteration
psw = self._genPw()
slt = self._genSalt()
mem = self.rng.randint(self.mnmem, self.mxmem)
iters = self.rng.randint(self.mniters, self.mxiters)
dgstln = self.rng.randint(self.mndgstlen, self.mxdgstlen)
rs = self._runOnce(psw, slt, dgstln, mem, iters)
self.count += 1
return rs
def __iter__(self):
return self
next = __next__
if __name__ == "__main__":
p = argparse.ArgumentParser()
p.add_argument("-x", "--executable", dest="exe", required=True)
p.add_argument(
"-c", "--construction", dest="construct", type=str, default="argon2i"
)
p.add_argument("-v", "--version", dest="version", type=str, default="13")
p.add_argument(
"-e",
"--encoded",
dest="encoded",
default=False,
action="store_true",
)
p.add_argument(
"-s", "--min-salt-len", dest="mnsaltlen", type=int, default=8
)
p.add_argument(
"-S", "--max-salt-len", dest="mxsaltlen", type=int, default=8
)
p.add_argument(
"-p", "--min-password-len", dest="mnpwlen", type=int, default=16
)
p.add_argument(
"-P", "--max-password-len", dest="mxpwlen", type=int, default=16
)
p.add_argument(
"-l", "--min-digest-len", dest="mndgstlen", type=int, default=64
)
p.add_argument(
"-L", "--max-digest-len", dest="mxdgstlen", type=int, default=64
)
p.add_argument(
"-m", "--min-memory-exponent", dest="mnmem", type=int, default=16
)
p.add_argument(
"-M", "--max-memory-exponent", dest="mxmem", type=int, default=16
)
p.add_argument(
"-t", "--min-time-opscount", dest="mniters", type=int, default=3
)
p.add_argument(
"-T", "--max-time-opscount", dest="mxiters", type=int, default=3
)
p.add_argument("-n", "--count", dest="n", type=int, default=10)
p.add_argument(
"-w",
"--output",
dest="outfile",
default=sys.stdout,
type=argparse.FileType("w"),
)
args = p.parse_args()
res = [x for x in argonRunner(args)]
json.dump(res, args.outfile, indent=2, separators=(",", ": "))