# Argon2 constructs reference vectors

Since libsodium implements a different API for argon2 constructs 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 vectors 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`.

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=(",", ": "))