Repository: randombit/botan
Pull request #1987 (2019-05-29 14:31:48)

Argon2: PasswordHash, documentation, cli, hash formatting


Patch:
diff --git a/doc/manual/passhash.rst b/doc/manual/passhash.rst
index 02094a99a..4ef26f7be 100644
--- a/doc/manual/passhash.rst
+++ b/doc/manual/passhash.rst
@@ -65,8 +65,50 @@ designs, such as scrypt, explicitly attempt to provide this. The
 bcrypt approach requires over 4 KiB of RAM (for the Blowfish key
 schedule) and may also make some hardware attacks more expensive.
 
-Botan provides two techniques for password hashing, bcrypt and
-passhash9.
+Botan provides three techniques for password hashing: Argon2, bcrypt, and
+passhash9 (based on PBKDF2).
+
+Argon2
+----------------------------------------
+
+.. versionadded:: 2.11.0
+
+Argon2 is the winner of the PHC (Password Hashing Competition) and provides
+a tunable memory hard password hash. It has a standard string encoding, which looks like::
+
+  "$argon2i$v=19$m=8192,t=10,p=3$YWFhYWFhYWE$itkWB9ODqTd85wUsoib7pfpVTNGMOu0ZJan1odl25V8"
+
+Argon2 has three tunable parameters: ``M``, ``p``, and ``t``. ``M`` gives the
+total memory consumption of the algorithm in kilobytes. Increasing ``p``
+increases the available parallelism of the computation. The ``t`` parameter
+gives the number of passes which are made over the data.
+
+.. note::
+   Currently Botan does not make use of ``p`` > 1, so it is best to set it to 1
+   to minimize any advantage to highly parallel cracking attempts.
+
+There are three variants of Argon2, namely Argon2d, Argon2i and Argon2id.
+Argon2d uses data dependent table lookups with may leak information about the
+password via side channel attacks, and is **not recommended** for password
+hashing. Argon2i uses data independent table lookups and is immune to these
+attacks, but at the cost of requiring higher ``t`` for security. Argon2id uses a
+hybrid approach which is thought to be highly secure. The algorithm designers
+recommend using Argon2id with ``t`` and ``p`` both equal to 1 and ``M`` set to
+the largest amount of memory usable in your environment.
+
+.. cpp:function:: std::string argon2_generate_pwhash(const char* password, size_t password_len, \
+                          RandomNumberGenerator& rng, \
+                          size_t p, size_t M, size_t t, \
+                          size_t y = 2, size_t salt_len = 16, size_t output_len = 32)
+
+   Generate an Argon2 hash of the specified password. The ``y`` parameter specifies
+   the variant: 0 for Argon2d, 1 for Argon2i, and 2 for Argon2id.
+
+.. cpp:function:: bool argon2_check_pwhash(const char* password, size_t password_len, \
+                                           const std::string& hash)
+
+   Verify an Argon2 password hash against the provided password. Returns false if
+   the input hash seems malformed or if the computed hash does not match.
 
 Bcrypt
 ----------------------------------------
@@ -153,7 +195,7 @@ Passhash9 hashes look like::
 
 This function should be secure with the proper parameters, and will remain in
 the library for the foreseeable future, but it is specific to Botan rather than
-being a widely used password hash. Prefer bcrypt.
+being a widely used password hash. Prefer bcrypt or Argon2.
 
 .. warning::
 
diff --git a/doc/manual/pbkdf.rst b/doc/manual/pbkdf.rst
index 6539c6f0f..92f59f278 100644
--- a/doc/manual/pbkdf.rst
+++ b/doc/manual/pbkdf.rst
@@ -150,6 +150,14 @@ with this function:
 
    As a general recommendation, use N=32768, r=8, p=1
 
+Argon2
+^^^^^^^^^^
+
+.. versionadded:: 2.11.0
+
+Argon2 is the winner of the PHC (Password Hashing Competition) and
+provides a tunable memory hard PBKDF.
+
 OpenPGP S2K
 ^^^^^^^^^^^^
 
diff --git a/doc/manual/pkcs11.rst b/doc/manual/pkcs11.rst
index 781777264..6e2827b16 100644
--- a/doc/manual/pkcs11.rst
+++ b/doc/manual/pkcs11.rst
@@ -1285,8 +1285,6 @@ Test results
 +-------------------------------------+-------------------------------------------+---------------------------------------------------+---------------------------------------------------+---------------------------------------------------+---------------------------------------------------+
 | SoftHSM 2.3.0 (OpenSSL 1.0.2g)      | works                                     | Windows 10, 64-bit, version 1709                  | Cryptoki v2.40                                    |  2.4.0, Cryptoki v2.40                            |                                                   |
 +-------------------------------------+-------------------------------------------+---------------------------------------------------+---------------------------------------------------+---------------------------------------------------+---------------------------------------------------+
-| SoftHSM 2.5.0 (OpenSSL 1.1.1)       | works                                     | Windows 10, 64-bit, version 1803                  | Cryptoki v2.40                                    |  2.11.0, Cryptoki v2.40                           |                                                   |
-+-------------------------------------+-------------------------------------------+---------------------------------------------------+---------------------------------------------------+---------------------------------------------------+---------------------------------------------------+
 
 .. [50] Failing operations for CardOS 4.4:
 
diff --git a/readme.rst b/readme.rst
index dd7876368..a0bea2ce5 100644
--- a/readme.rst
+++ b/readme.rst
@@ -112,7 +112,7 @@ Other Useful Things
 * Simple compression API wrapping zlib, bzip2, and lzma libraries
 * RNG wrappers for system RNG and hardware RNGs
 * HMAC_DRBG and entropy collection system for userspace RNGs
-* Password hashing schemes PBKDF2, Scrypt, bcrypt
+* Password hashing schemes PBKDF2, Argon2, Scrypt, bcrypt
 * SRP-6a password authenticated key exchange
 * Key derivation functions including HKDF, KDF2, SP 800-108, SP 800-56A, SP 800-56C
 * HOTP and TOTP algorithms
diff --git a/src/build-data/bakefile.in b/src/build-data/bakefile.in
index 034e3d50a..a1c0ff134 100644
--- a/src/build-data/bakefile.in
+++ b/src/build-data/bakefile.in
@@ -1,6 +1,6 @@
 toolsets = vs2013;
 shared-library botan {
-   defines = "BOTAN_DLL=__declspec(dllexport)" _ENABLE_EXTENDED_ALIGNED_STORAGE;
+   defines = "BOTAN_DLL=__declspec(dllexport)";
    sources {
 %{for lib_srcs}
       %{i}
diff --git a/src/cli/argon2.cpp b/src/cli/argon2.cpp
new file mode 100644
index 000000000..2b07027c3
--- /dev/null
+++ b/src/cli/argon2.cpp
@@ -0,0 +1,78 @@
+/*
+* (C) 2019 Jack Lloyd
+*
+* Botan is released under the Simplified BSD License (see license.txt)
+*/
+
+#include "cli.h"
+
+#if defined(BOTAN_HAS_ARGON2)
+   #include <botan/argon2.h>
+#endif
+
+namespace Botan_CLI {
+
+#if defined(BOTAN_HAS_ARGON2)
+
+class Generate_Argon2 final : public Command
+   {
+   public:
+      Generate_Argon2() : Command("gen_argon2 --mem=65536 --p=1 --t=1 password") {}
+
+      std::string group() const override
+         {
+         return "passhash";
+         }
+
+      std::string description() const override
+         {
+         return "Calculate Argon2 password hash";
+         }
+
+      void go() override
+         {
+         const std::string password = get_passphrase_arg("Passphrase to hash", "password");
+         const size_t M = get_arg_sz("mem");
+         const size_t p = get_arg_sz("p");
+         const size_t t = get_arg_sz("t");
+
+         output() << Botan::argon2_generate_pwhash(password.data(), password.size(), rng(), p, M, t) << "\n";
+         }
+   };
+
+BOTAN_REGISTER_COMMAND("gen_argon2", Generate_Argon2);
+
+class Check_Argon2 final : public Command
+   {
+   public:
+      Check_Argon2() : Command("check_argon2 password hash") {}
+
+      std::string group() const override
+         {
+         return "passhash";
+         }
+
+      std::string description() const override
+         {
+         return "Verify Argon2 password hash";
+         }
+
+      void go() override
+         {
+         const std::string password = get_passphrase_arg("Password to check", "password");
+         const std::string hash = get_arg("hash");
+
+         const bool ok = Botan::argon2_check_pwhash(password.data(), password.size(), hash);
+
+         output() << "Password is " << (ok ? "valid" : "NOT valid") << std::endl;
+
+         if(ok == false)
+            set_return_code(1);
+         }
+   };
+
+BOTAN_REGISTER_COMMAND("check_argon2", Check_Argon2);
+
+#endif // argon2
+
+}
diff --git a/src/cli/bcrypt.cpp b/src/cli/bcrypt.cpp
index 6d7d7124c..68e77b8e6 100644
--- a/src/cli/bcrypt.cpp
+++ b/src/cli/bcrypt.cpp
@@ -26,7 +26,7 @@ class Generate_Bcrypt final : public Command
 
       std::string description() const override
          {
-         return "Calculate the bcrypt password digest of a given file";
+         return "Calculate bcrypt password hash";
          }
 
       void go() override
@@ -60,7 +60,7 @@ class Check_Bcrypt final : public Command
 
       std::string description() const override
          {
-         return "Checks a given bcrypt hash against hash";
+         return "Verify bcrypt password hash";
          }
 
       void go() override
diff --git a/src/cli/pbkdf.cpp b/src/cli/pbkdf.cpp
index c2e4112b6..d17c49220 100644
--- a/src/cli/pbkdf.cpp
+++ b/src/cli/pbkdf.cpp
@@ -18,7 +18,7 @@ namespace Botan_CLI {
 class PBKDF_Tune final : public Command
    {
    public:
-      PBKDF_Tune() : Command("pbkdf_tune --algo=Scrypt --max-mem=160 --output-len=32 --check *times") {}
+      PBKDF_Tune() : Command("pbkdf_tune --algo=Scrypt --max-mem=256 --output-len=32 --check *times") {}
 
       std::string group() const override
          {
diff --git a/src/cli/speed.cpp b/src/cli/speed.cpp
index c93f45ea6..9830c4f29 100644
--- a/src/cli/speed.cpp
+++ b/src/cli/speed.cpp
@@ -121,6 +121,10 @@
    #include <botan/scrypt.h>
 #endif
 
+#if defined(BOTAN_HAS_ARGON2)
+   #include <botan/argon2.h>
+#endif
+
 #if defined(BOTAN_HAS_BCRYPT)
    #include <botan/bcrypt.h>
 #endif
@@ -646,6 +650,12 @@ class Speed final : public Command
                bench_scrypt(provider, msec);
                }
 #endif
+#if defined(BOTAN_HAS_ARGON2)
+            else if(algo == "argon2")
+               {
+               bench_argon2(provider, msec);
+               }
+#endif
 #if defined(BOTAN_HAS_BCRYPT)
             else if(algo == "bcrypt")
                {
@@ -2195,6 +2205,42 @@ class Speed final : public Command
 
 #endif
 
+#if defined(BOTAN_HAS_ARGON2)
+
+      void bench_argon2(const std::string& /*provider*/,
+                        std::chrono::milliseconds msec)
+         {
+         for(size_t M : { 8*1024, 64*1024, 256*1024 })
+            {
+            for(size_t t : { 1, 2, 4 })
+               {
+               for(size_t p : { 1 })
+                  {
+                  std::unique_ptr<Timer> timer = make_timer(
+                     "Argon2id M=" + std::to_string(M) + " t=" + std::to_string(t) + " p=" + std::to_string(p));
+
+                  const size_t mode = 2;
+                  uint8_t out[64];
+                  uint8_t salt[16];
+                  rng().randomize(salt, sizeof(salt));
+
+                  while(timer->under(msec))
+                     {
+                     timer->run([&] {
+                                Botan::argon2(out, sizeof(out), "password", 8,
+                                              salt, sizeof(salt), nullptr, 0, nullptr, 0,
+                                              mode, p, M, t);
+                                });
+                     }
+
+                  record_result(timer);
+                  }
+               }
+            }
+         }
+
+#endif
+
 #if defined(BOTAN_HAS_NEWHOPE) && defined(BOTAN_HAS_CHACHA_RNG)
       void bench_newhope(const std::string& /*provider*/,
                          std::chrono::milliseconds msec)
diff --git a/src/lib/ffi/ffi.h b/src/lib/ffi/ffi.h
index 980d8f906..323f47557 100644
--- a/src/lib/ffi/ffi.h
+++ b/src/lib/ffi/ffi.h
@@ -191,11 +191,8 @@ typedef struct botan_rng_struct* botan_rng_t;
 * Initialize a random number generator object
 * @param rng rng object
 * @param rng_type type of the rng, possible values:
-*    "system": system RNG
-*    "user": userspace RNG
-*    "user-threadsafe": userspace RNG, with internal locking
-*    "rdrand": directly read RDRAND
-* Set rng_type to null to let the library choose some default.
+*    "system": System_RNG, "user": AutoSeeded_RNG
+* Set rng_type to null or empty string to let the library choose
 */
 BOTAN_PUBLIC_API(2,0) int botan_rng_init(botan_rng_t* rng, const char* rng_type);
 
@@ -245,7 +242,7 @@ BOTAN_PUBLIC_API(2,8) int botan_rng_add_entropy(botan_rng_t rng,
 /**
 * Frees all resources of the random number generator object
 * @param rng rng object
-* @return 0 if success, error if invalid object handle
+* @return always returns 0
 */
 BOTAN_PUBLIC_API(2,0) int botan_rng_destroy(botan_rng_t rng);
 
@@ -317,7 +314,7 @@ BOTAN_PUBLIC_API(2,0) int botan_hash_clear(botan_hash_t hash);
 /**
 * Frees all resources of the hash object
 * @param hash hash object
-* @return 0 if success, error if invalid object handle
+* @return always returns 0
 */
 BOTAN_PUBLIC_API(2,0) int botan_hash_destroy(botan_hash_t hash);
 
@@ -411,7 +408,7 @@ BOTAN_PUBLIC_API(2,8) int botan_mac_get_keyspec(botan_mac_t mac,
 /**
 * Frees all resources of the MAC object
 * @param mac mac object
-* @return 0 if success, error if invalid object handle
+* @return always returns 0
 */
 BOTAN_PUBLIC_API(2,0) int botan_mac_destroy(botan_mac_t mac);
 
@@ -524,7 +521,6 @@ BOTAN_PUBLIC_API(2,0) int botan_cipher_clear(botan_cipher_t hash);
 
 /**
 * Destroy the cipher object
-* @return 0 if success, error if invalid object handle
 */
 BOTAN_PUBLIC_API(2,0) int botan_cipher_destroy(botan_cipher_t cipher);
 
@@ -662,7 +658,6 @@ BOTAN_PUBLIC_API(2,1) int botan_block_cipher_init(botan_block_cipher_t* bc,
 
 /**
 * Destroy a block cipher object
-* @return 0 if success, error if invalid object handle
 */
 BOTAN_PUBLIC_API(2,1) int botan_block_cipher_destroy(botan_block_cipher_t bc);
 
@@ -734,7 +729,6 @@ BOTAN_PUBLIC_API(2,1) int botan_mp_init(botan_mp_t* mp);
 
 /**
 * Destroy (deallocate) an MPI
-* @return 0 if success, error if invalid object handle
 */
 BOTAN_PUBLIC_API(2,1) int botan_mp_destroy(botan_mp_t mp);
 
@@ -1015,9 +1009,6 @@ BOTAN_PUBLIC_API(2,0) int botan_privkey_load(botan_privkey_t* key,
                                              const uint8_t bits[], size_t len,
                                              const char* password);
 
-/**
-* @return 0 if success, error if invalid object handle
-*/
 BOTAN_PUBLIC_API(2,0) int botan_privkey_destroy(botan_privkey_t key);
 
 #define BOTAN_PRIVKEY_EXPORT_FLAG_DER 0
@@ -1093,9 +1084,6 @@ BOTAN_PUBLIC_API(2,0) int botan_pubkey_estimated_strength(botan_pubkey_t key, si
 BOTAN_PUBLIC_API(2,0) int botan_pubkey_fingerprint(botan_pubkey_t key, const char* hash,
                                        uint8_t out[], size_t* out_len);
 
-/**
-* @return 0 if success, error if invalid object handle
-*/
 BOTAN_PUBLIC_API(2,0) int botan_pubkey_destroy(botan_pubkey_t key);
 
 /*
@@ -1342,9 +1330,6 @@ BOTAN_PUBLIC_API(2,0) int botan_pk_op_encrypt_create(botan_pk_op_encrypt_t* op,
                                          const char* padding,
                                          uint32_t flags);
 
-/**
-* @return 0 if success, error if invalid object handle
-*/
 BOTAN_PUBLIC_API(2,0) int botan_pk_op_encrypt_destroy(botan_pk_op_encrypt_t op);
 
 BOTAN_PUBLIC_API(2,8) int botan_pk_op_encrypt_output_length(botan_pk_op_encrypt_t op,
@@ -1367,10 +1352,6 @@ BOTAN_PUBLIC_API(2,0) int botan_pk_op_decrypt_create(botan_pk_op_decrypt_t* op,
                                          botan_privkey_t key,
                                          const char* padding,
                                          uint32_t flags);
-
-/**
-* @return 0 if success, error if invalid object handle
-*/
 BOTAN_PUBLIC_API(2,0) int botan_pk_op_decrypt_destroy(botan_pk_op_decrypt_t op);
 
 BOTAN_PUBLIC_API(2,8) int botan_pk_op_decrypt_output_length(botan_pk_op_decrypt_t op,
@@ -1392,9 +1373,6 @@ int botan_pk_op_sign_create(botan_pk_op_sign_t* op,
                             const char* hash_and_padding,
                             uint32_t flags);
 
-/**
-* @return 0 if success, error if invalid object handle
-*/
 BOTAN_PUBLIC_API(2,0) int botan_pk_op_sign_destroy(botan_pk_op_sign_t op);
 
 BOTAN_PUBLIC_API(2,8) int botan_pk_op_sign_output_length(botan_pk_op_sign_t op, size_t* olen);
@@ -1416,9 +1394,6 @@ int botan_pk_op_verify_create(botan_pk_op_verify_t* op,
                               const char* hash_and_padding,
                               uint32_t flags);
 
-/**
-* @return 0 if success, error if invalid object handle
-*/
 BOTAN_PUBLIC_API(2,0) int botan_pk_op_verify_destroy(botan_pk_op_verify_t op);
 
 BOTAN_PUBLIC_API(2,0) int botan_pk_op_verify_update(botan_pk_op_verify_t op, const uint8_t in[], size_t in_len);
@@ -1435,9 +1410,6 @@ int botan_pk_op_key_agreement_create(botan_pk_op_ka_t* op,
                                      const char* kdf,
                                      uint32_t flags);
 
-/**
-* @return 0 if success, error if invalid object handle
-*/
 BOTAN_PUBLIC_API(2,0) int botan_pk_op_key_agreement_destroy(botan_pk_op_ka_t op);
 
 BOTAN_PUBLIC_API(2,0) int botan_pk_op_key_agreement_export_public(botan_privkey_t key,
@@ -1482,10 +1454,6 @@ typedef struct botan_x509_cert_struct* botan_x509_cert_t;
 
 BOTAN_PUBLIC_API(2,0) int botan_x509_cert_load(botan_x509_cert_t* cert_obj, const uint8_t cert[], size_t cert_len);
 BOTAN_PUBLIC_API(2,0) int botan_x509_cert_load_file(botan_x509_cert_t* cert_obj, const char* filename);
-
-/**
-* @return 0 if success, error if invalid object handle
-*/
 BOTAN_PUBLIC_API(2,0) int botan_x509_cert_destroy(botan_x509_cert_t cert);
 
 BOTAN_PUBLIC_API(2,8) int botan_x509_cert_dup(botan_x509_cert_t* new_cert, botan_x509_cert_t cert);
@@ -1598,7 +1566,6 @@ int botan_hotp_init(botan_hotp_t* hotp,
 
 /**
 * Destroy a HOTP instance
-* @return 0 if success, error if invalid object handle
 */
 BOTAN_PUBLIC_API(2,8)
 int botan_hotp_destroy(botan_hotp_t hotp);
@@ -1640,7 +1607,6 @@ int botan_totp_init(botan_totp_t* totp,
 
 /**
 * Destroy a TOTP instance
-* @return 0 if success, error if invalid object handle
 */
 BOTAN_PUBLIC_API(2,8)
 int botan_totp_destroy(botan_totp_t totp);
@@ -1684,9 +1650,6 @@ int botan_fpe_fe1_init(botan_fpe_t* fpe, botan_mp_t n,
                        const uint8_t key[], size_t key_len,
                        size_t rounds, uint32_t flags);
 
-/**
-* @return 0 if success, error if invalid object handle
-*/
 BOTAN_PUBLIC_API(2,8)
 int botan_fpe_destroy(botan_fpe_t fpe);
 
diff --git a/src/lib/ffi/ffi_util.h b/src/lib/ffi/ffi_util.h
index e77a2debb..2962b05e1 100644
--- a/src/lib/ffi/ffi_util.h
+++ b/src/lib/ffi/ffi_util.h
@@ -82,11 +82,7 @@ int apply_fn(botan_struct<T, M>* o, const char* func_name, F func)
    if(o->magic_ok() == false)
       return BOTAN_FFI_ERROR_INVALID_OBJECT;
 
-   T* p = o->unsafe_get();
-   if(p == nullptr)
-      return BOTAN_FFI_ERROR_INVALID_OBJECT;
-
-   return ffi_guard_thunk(func_name, [&]() { return func(*p); });
+   return ffi_guard_thunk(func_name, [&]() { return func(*o->unsafe_get()); });
    }
 
 #define BOTAN_FFI_DO(T, obj, param, block)                              \
diff --git a/src/lib/pbkdf/argon2/argon2.cpp b/src/lib/pbkdf/argon2/argon2.cpp
index e446e1513..6bdaf8166 100644
--- a/src/lib/pbkdf/argon2/argon2.cpp
+++ b/src/lib/pbkdf/argon2/argon2.cpp
@@ -48,7 +48,8 @@ secure_vector<uint8_t> argon2_H0(HashFunction& blake2b,
    return blake2b.final();
    }
 
-void Htick(uint8_t output[],
+void Htick(secure_vector<uint8_t>& T,
+           uint8_t output[],
            size_t output_len,
            HashFunction& blake2b,
            const secure_vector<uint8_t>& H0,
@@ -56,27 +57,25 @@ void Htick(uint8_t output[],
    {
    BOTAN_ASSERT_NOMSG(output_len % 64 == 0);
 
-   secure_vector<uint8_t> B(blake2b.output_length());
-
    blake2b.update_le<uint32_t>(output_len);
    blake2b.update(H0);
    blake2b.update_le<uint32_t>(p0);
    blake2b.update_le<uint32_t>(p1);
 
-   blake2b.final(&B[0]);
+   blake2b.final(&T[0]);
 
    while(output_len > 64)
       {
-      copy_mem(output, &B[0], 32);
+      copy_mem(output, &T[0], 32);
       output_len -= 32;
       output += 32;
 
-      blake2b.update(B);
-      blake2b.final(&B[0]);
+      blake2b.update(T);
+      blake2b.final(&T[0]);
       }
 
    if(output_len > 0)
-      copy_mem(output, &B[0], output_len);
+      copy_mem(output, &T[0], output_len);
    }
 
 void extract_key(uint8_t output[], size_t output_len,
@@ -125,6 +124,7 @@ void init_blocks(secure_vector<uint64_t>& B,
    BOTAN_ASSERT_NOMSG(B.size() >= threads*256);
 
    secure_vector<uint8_t> H(1024);
+   secure_vector<uint8_t> T(blake2b.output_length());
 
    for(size_t i = 0; i != threads; ++i)
       {
@@ -132,14 +132,14 @@ void init_blocks(secure_vector<uint64_t>& B,
 
       BOTAN_ASSERT_NOMSG(B.size() >= 128*(B_off+2));
 
-      Htick(&H[0], H.size(), blake2b, H0, 0, i);
+      Htick(T, &H[0], H.size(), blake2b, H0, 0, i);
 
       for(size_t j = 0; j != 128; ++j)
          {
          B[128*B_off+j] = load_le<uint64_t>(H.data(), j);
          }
 
-      Htick(&H[0], H.size(), blake2b, H0, 1, i);
+      Htick(T, &H[0], H.size(), blake2b, H0, 1, i);
 
       for(size_t j = 0; j != 128; ++j)
          {
@@ -150,16 +150,16 @@ void init_blocks(secure_vector<uint64_t>& B,
 
 inline void blamka_G(uint64_t& A, uint64_t& B, uint64_t& C, uint64_t& D)
    {
-   A += B + 2*(A & 0xFFFFFFFF)*(B & 0xFFFFFFFF);
+   A += B + (static_cast<uint64_t>(2) * static_cast<uint32_t>(A)) * static_cast<uint32_t>(B);
    D = rotr<32>(A ^ D);
 
-   C += D + 2*(C & 0xFFFFFFFF) * (D & 0xFFFFFFFF);
+   C += D + (static_cast<uint64_t>(2) * static_cast<uint32_t>(C)) * static_cast<uint32_t>(D);
    B = rotr<24>(B ^ C);
 
-   A += B + 2*(A & 0xFFFFFFFF) * (B & 0xFFFFFFFF);
+   A += B + (static_cast<uint64_t>(2) * static_cast<uint32_t>(A)) * static_cast<uint32_t>(B);
    D = rotr<16>(A ^ D);
 
-   C += D + 2*(C & 0xFFFFFFFF) * (D & 0xFFFFFFFF);
+   C += D + (static_cast<uint64_t>(2) * static_cast<uint32_t>(C)) * static_cast<uint32_t>(D);
    B = rotr<63>(B ^ C);
    }
 
@@ -179,13 +179,12 @@ inline void blamka(uint64_t& V0, uint64_t& V1, uint64_t& V2, uint64_t& V3,
    blamka_G(V3, V4, V9, VE);
    }
 
-void process_block_xor(secure_vector<uint64_t>& B,
+void process_block_xor(secure_vector<uint64_t>& T,
+                       secure_vector<uint64_t>& B,
                        size_t offset,
                        size_t prev,
                        size_t new_offset)
    {
-   secure_vector<uint64_t> T(128);
-
    for(size_t i = 0; i != 128; ++i)
       T[i] = B[128*prev+i] ^ B[128*new_offset+i];
 
@@ -209,11 +208,12 @@ void process_block_xor(secure_vector<uint64_t>& B,
       B[128*offset + i] ^= T[i] ^ B[128*prev+i] ^ B[128*new_offset+i];
    }
 
-void gen_2i_addresses(secure_vector<uint64_t>& B,
+void gen_2i_addresses(secure_vector<uint64_t>& T, secure_vector<uint64_t>& B,
                       size_t n, size_t lane, size_t slice, size_t memory,
                       size_t time, size_t mode, size_t cnt)
    {
    BOTAN_ASSERT_NOMSG(B.size() == 128);
+   BOTAN_ASSERT_NOMSG(T.size() == 128);
 
    clear_mem(B.data(), B.size());
    B[0] = n;
@@ -226,7 +226,7 @@ void gen_2i_addresses(secure_vector<uint64_t>& B,
 
    for(size_t r = 0; r != 2; ++r)
       {
-      secure_vector<uint64_t> T = B;
+      copy_mem(T.data(), B.data(), B.size());
 
       for(size_t i = 0; i != 128; i += 16)
          {
@@ -285,7 +285,8 @@ uint32_t index_alpha(uint64_t random,
    return ref_lane*lanes + (s + m - (p+1)) % lanes;
    }
 
-void process_block_argon2d(secure_vector<uint64_t>& B,
+void process_block_argon2d(secure_vector<uint64_t>& T,
+                           secure_vector<uint64_t>& B,
                            size_t n, size_t slice, size_t lane,
                            size_t lanes, size_t segments, size_t threads)
    {
@@ -304,13 +305,14 @@ void process_block_argon2d(secure_vector<uint64_t>& B,
       const uint64_t random = B.at(128*prev);
       const size_t new_offset = index_alpha(random, lanes, segments, threads, n, slice, lane, index);
 
-      process_block_xor(B, offset, prev, new_offset);
+      process_block_xor(T, B, offset, prev, new_offset);
 
       index += 1;
       }
    }
 
-void process_block_argon2i(secure_vector<uint64_t>& B,
+void process_block_argon2i(secure_vector<uint64_t>& T,
+                           secure_vector<uint64_t>& B,
                            size_t n, size_t slice, size_t lane,
                            size_t lanes, size_t segments, size_t threads, uint8_t mode,
                            size_t memory, size_t time)
@@ -322,7 +324,7 @@ void process_block_argon2i(secure_vector<uint64_t>& B,
    secure_vector<uint64_t> addresses(128);
    size_t address_counter = 1;
 
-   gen_2i_addresses(addresses, n, lane, slice, memory, time, mode, address_counter);
+   gen_2i_addresses(T, addresses, n, lane, slice, memory, time, mode, address_counter);
 
    while(index < segments)
       {
@@ -335,13 +337,13 @@ void process_block_argon2i(secure_vector<uint64_t>& B,
       if(index > 0 && index % 128 == 0)
          {
          address_counter += 1;
-         gen_2i_addresses(addresses, n, lane, slice, memory, time, mode, address_counter);
+         gen_2i_addresses(T, addresses, n, lane, slice, memory, time, mode, address_counter);
          }
 
       const uint64_t random = addresses[index % 128];
       const size_t new_offset = index_alpha(random, lanes, segments, threads, n, slice, lane, index);
 
-      process_block_xor(B, offset, prev, new_offset);
+      process_block_xor(T, B, offset, prev, new_offset);
 
       index += 1;
       }
@@ -356,6 +358,7 @@ void process_blocks(secure_vector<uint64_t>& B,
    const size_t lanes = memory / threads;
    const size_t segments = lanes / SYNC_POINTS;
 
+   secure_vector<uint64_t> T(128);
    for(size_t n = 0; n != t; ++n)
       {
       for(size_t slice = 0; slice != SYNC_POINTS; ++slice)
@@ -364,9 +367,9 @@ void process_blocks(secure_vector<uint64_t>& B,
          for(size_t lane = 0; lane != threads; ++lane)
             {
             if(mode == 1 || (mode == 2 && n == 0 && slice < SYNC_POINTS/2))
-               process_block_argon2i(B, n, slice, lane, lanes, segments, threads, mode, memory, t);
+               process_block_argon2i(T, B, n, slice, lane, lanes, segments, threads, mode, memory, t);
             else
-               process_block_argon2d(B, n, slice, lane, lanes, segments, threads);
+               process_block_argon2d(T, B, n, slice, lane, lanes, segments, threads);
             }
          }
       }
diff --git a/src/lib/pbkdf/argon2/argon2.h b/src/lib/pbkdf/argon2/argon2.h
index 27a6a3220..e0fe1d83d 100644
--- a/src/lib/pbkdf/argon2/argon2.h
+++ b/src/lib/pbkdf/argon2/argon2.h
@@ -1,5 +1,5 @@
 /**
-* (C) 2018 Jack Lloyd
+* (C) 2018,2019 Jack Lloyd
 *
 * Botan is released under the Simplified BSD License (see license.txt)
 */
@@ -7,10 +7,70 @@
 #ifndef BOTAN_ARGON2_H_
 #define BOTAN_ARGON2_H_
 
-#include <botan/types.h>
+#include <botan/pwdhash.h>
 
 namespace Botan {
 
+class RandomNumberGenerator;
+
+/**
+* Argon2 key derivation function
+*/
+class BOTAN_PUBLIC_API(2,11) Argon2 final : public PasswordHash
+   {
+   public:
+      Argon2(uint8_t family, size_t M, size_t t, size_t p);
+
+      Argon2(const Argon2& other) = default;
+      Argon2& operator=(const Argon2&) = default;
+
+      /**
+      * Derive a new key under the current Argon2 parameter set
+      */
+      void derive_key(uint8_t out[], size_t out_len,
+                      const char* password, const size_t password_len,
+                      const uint8_t salt[], size_t salt_len) const override;
+
+      std::string to_string() const override;
+
+      size_t M() const { return m_M; }
+      size_t t() const { return m_t; }
+      size_t p() const { return m_p; }
+
+      size_t iterations() const override { return t(); }
+
+      size_t parallelism() const override { return p(); }
+
+      size_t memory_param() const override { return M(); }
+
+      size_t total_memory_usage() const override { return M() * 1024; }
+
+   private:
+      uint8_t m_family;
+      size_t m_M, m_t, m_p;
+   };
+
+class BOTAN_PUBLIC_API(2,11) Argon2_Family final : public PasswordHashFamily
+   {
+   public:
+      Argon2_Family(uint8_t family);
+
+      std::string name() const override;
+
+      std::unique_ptr<PasswordHash> tune(size_t output_length,
+                                         std::chrono::milliseconds msec,
+                                         size_t max_memory) const override;
+
+      std::unique_ptr<PasswordHash> default_params() const override;
+
+      std::unique_ptr<PasswordHash> from_iterations(size_t iter) const override;
+
+      std::unique_ptr<PasswordHash> from_params(
+         size_t M, size_t t, size_t p) const override;
+   private:
+      const uint8_t m_family;
+   };
+
 /**
 * Argon2 key derivation function
 *
@@ -31,6 +91,20 @@ void BOTAN_PUBLIC_API(2,11) argon2(uint8_t output[], size_t output_len,
                                    const uint8_t ad[], size_t ad_len,
                                    size_t y, size_t p, size_t M, size_t t);
 
+std::string BOTAN_PUBLIC_API(2,11)
+   argon2_generate_pwhash(const char* password, size_t password_len,
+                          RandomNumberGenerator& rng,
+                          size_t p, size_t M, size_t t,
+                          size_t y = 2, size_t salt_len = 16, size_t output_len = 32);
+
+/**
+* Check a previously created password hash
+* @param password the password to check against
+* @param hash the stored hash to check against
+*/
+bool BOTAN_PUBLIC_API(2,11) argon2_check_pwhash(const char* password, size_t password_len,
+                                                const std::string& hash);
+
 }
 
 #endif
diff --git a/src/lib/pbkdf/argon2/argon2fmt.cpp b/src/lib/pbkdf/argon2/argon2fmt.cpp
new file mode 100644
index 000000000..f64cd2bbd
--- /dev/null
+++ b/src/lib/pbkdf/argon2/argon2fmt.cpp
@@ -0,0 +1,125 @@
+/**
+* (C) 2019 Jack Lloyd
+*
+* Botan is released under the Simplified BSD License (see license.txt)
+*/
+
+#include <botan/argon2.h>
+#include <botan/rng.h>
+#include <botan/base64.h>
+#include <botan/parsing.h>
+#include <sstream>
+
+namespace Botan {
+
+namespace {
+
+std::string strip_padding(std::string s)
+   {
+   while(s.size() > 0 && s[s.size()-1] == '=')
+      s.resize(s.size() - 1);
+   return s;
+   }
+
+}
+
+std::string argon2_generate_pwhash(const char* password, size_t password_len,
+                                   RandomNumberGenerator& rng,
+                                   size_t p, size_t M, size_t t,
+                                   size_t y, size_t salt_len, size_t output_len)
+   {
+   std::vector<uint8_t> salt(salt_len);
+   rng.randomize(salt.data(), salt.size());
+
+   std::vector<uint8_t> output(output_len);
+   argon2(output.data(), output.size(),
+          password, password_len,
+          salt.data(), salt.size(),
+          nullptr, 0,
+          nullptr, 0,
+          y, p, M, t);
+
+   std::ostringstream oss;
+
+   if(y == 0)
+      oss << "$argon2d$";
+   else if(y == 1)
+      oss << "$argon2i$";
+   else
+      oss << "$argon2id$";
+
+   oss << "v=19$m=" << M << ",t=" << t << ",p=" << p << "$";
+   oss << strip_padding(base64_encode(salt)) << "$" << strip_padding(base64_encode(output));
+
+   return oss.str();
+   }
+
+bool argon2_check_pwhash(const char* password, size_t password_len,
+                         const std::string& input_hash)
+   {
+   const std::vector<std::string> parts = split_on(input_hash, '$');
+
+   if(parts.size() != 5)
+      return false;
+
+   size_t family = 0;
+
+   if(parts[0] == "argon2d")
+      family = 0;
+   else if(parts[0] == "argon2i")
+      family = 1;
+   else if(parts[0] == "argon2id")
+      family = 2;
+   else
+      return false;
+
+   if(parts[1] != "v=19")
+      return false;
+
+   const std::vector<std::string> params = split_on(parts[2], ',');
+
+   if(params.size() != 3)
+      return false;
+
+   size_t M = 0, t = 0, p = 0;
+
+   for(auto param_str : params)
+      {
+      const std::vector<std::string> param = split_on(param_str, '=');
+
+      if(param.size() != 2)
+         return false;
+
+      const std::string key = param[0];
+      const size_t val = to_u32bit(param[1]);
+      if(key == "m")
+         M = val;
+      else if(key == "t")
+         t = val;
+      else if(key == "p")
+         p = val;
+      else
+         return false;
+      }
+
+   std::vector<uint8_t> salt(base64_decode_max_output(parts[3].size()));
+   salt.resize(base64_decode(salt.data(), parts[3], false));
+
+   std::vector<uint8_t> hash(base64_decode_max_output(parts[4].size()));
+   hash.resize(base64_decode(hash.data(), parts[4], false));
+
+   if(hash.size() < 4)
+      return false;
+
+   std::vector<uint8_t> generated(hash.size());
+   argon2(generated.data(), generated.size(),
+          password, password_len,
+          salt.data(), salt.size(),
+          nullptr, 0,
+          nullptr, 0,
+          family, p, M, t);
+
+   return constant_time_compare(generated.data(), hash.data(), generated.size());
+   }
+
+}
diff --git a/src/lib/pbkdf/argon2/argon2pwhash.cpp b/src/lib/pbkdf/argon2/argon2pwhash.cpp
new file mode 100644
index 000000000..5af3c709b
--- /dev/null
+++ b/src/lib/pbkdf/argon2/argon2pwhash.cpp
@@ -0,0 +1,154 @@
+/**
+* (C) 2019 Jack Lloyd
+*
+* Botan is released under the Simplified BSD License (see license.txt)
+*/
+
+#include <botan/argon2.h>
+#include <botan/exceptn.h>
+#include <botan/internal/timer.h>
+#include <algorithm>
+
+namespace Botan {
+
+Argon2::Argon2(uint8_t family, size_t M, size_t t, size_t p) :
+   m_family(family),
+   m_M(M),
+   m_t(t),
+   m_p(p)
+   {}
+
+void Argon2::derive_key(uint8_t output[], size_t output_len,
+                        const char* password, const size_t password_len,
+                        const uint8_t salt[], size_t salt_len) const
+   {
+   argon2(output, output_len,
+          password, password_len,
+          salt, salt_len,
+          nullptr, 0,
+          nullptr, 0,
+          m_family, m_p, m_M, m_t);
+   }
+
+namespace {
+
+std::string argon2_family_name(uint8_t f)
+   {
+   switch(f)
+      {
+      case 0:
+         return "Argon2d";
+      case 1:
+         return "Argon2i";
+      case 2:
+         return "Argon2id";
+      default:
+         throw Invalid_Argument("Unknown Argon2 parameter");
+      }
+   }
+
+}
+
+std::string Argon2::to_string() const
+   {
+   return argon2_family_name(m_family) + "(" +
+      std::to_string(m_M) + "," +
+      std::to_string(m_t) + "," +
+      std::to_string(m_p) + ")";
+  }
+
+Argon2_Family::Argon2_Family(uint8_t family) : m_family(family)
+   {
+   if(m_family != 0 && m_family != 1 && m_family != 2)
+      throw Invalid_Argument("Unknown Argon2 family identifier");
+   }
+
+std::string Argon2_Family::name() const
+   {
+   return argon2_family_name(m_family);
+   }
+
+std::unique_ptr<PasswordHash> Argon2_Family::tune(size_t /*output_length*/,
+                                                  std::chrono::milliseconds msec,
+                                                  size_t max_memory) const
+   {
+   const size_t max_kib = (max_memory == 0) ? 256*1024 : max_memory*1024;
+
+   // Tune with a large memory otherwise we measure cache vs RAM speeds and underestimate
+   // costs for larger params
+   const size_t tune_M = 256*1024; // in KiB
+   const size_t p = 1;
+   size_t t = 1;
+
+   Timer timer("Argon2");
+   const auto tune_time = BOTAN_PBKDF_TUNING_TIME;
+
+   timer.run_until_elapsed(tune_time, [&]() {
+      uint8_t output[32] = { 0 };
+      argon2(output, sizeof(output), "test", 4, nullptr, 0, nullptr, 0, nullptr, 0, m_family, p, tune_M, t);
+      });
+
+   if(timer.events() == 0 || timer.value() == 0)
+      return default_params();
+
+   size_t M = 4*1024;
+
+   const uint64_t measured_time = timer.value() / (timer.events() * (tune_M / M));
+
+   const uint64_t target_nsec = msec.count() * static_cast<uint64_t>(1000000);
+
+   /*
+   * Argon2 scaling rules:
+   * k*M, k*t, k*p all increase cost by about k
+   *
+   * Since we don't even take advantage of p > 1, we prefer increasing
+   * t or M instead.
+   *
+   * If possible to increase M, prefer that.
+   */
+
+   uint64_t est_nsec = measured_time;
+
+   if(est_nsec < target_nsec && M < max_kib)
+      {
+      const size_t desired_cost_increase = (target_nsec + est_nsec - 1) / est_nsec;
+      const size_t mem_headroom = max_kib / M;
+
+      const size_t M_mult = std::min(desired_cost_increase, mem_headroom);
+      M *= M_mult;
+      est_nsec *= M_mult;
+      }
+
+   if(est_nsec < target_nsec)
+      {
+      const size_t desired_cost_increase = (target_nsec + est_nsec - 1) / est_nsec;
+      t *= desired_cost_increase;
+      }
+
+   return this->from_params(M, t, p);
+   }
+
+std::unique_ptr<PasswordHash> Argon2_Family::default_params() const
+   {
+   return this->from_params(128*1024, 1, 1);
+   }
+
+std::unique_ptr<PasswordHash> Argon2_Family::from_iterations(size_t iter) const
+   {
+   /*
+   These choices are arbitrary, but should not change in future
+   releases since they will break applications expecting deterministic
+   mapping from iteration count to params
+   */
+   const size_t M = iter;
+   const size_t t = 1;
+   const size_t p = 1;
+   return this->from_params(M, t, p);
+   }
+
+std::unique_ptr<PasswordHash> Argon2_Family::from_params(size_t M, size_t t, size_t p) const
+   {
+   return std::unique_ptr<PasswordHash>(new Argon2(m_family, M, t, p));
+   }
+
+}
diff --git a/src/lib/pbkdf/pwdhash.cpp b/src/lib/pbkdf/pwdhash.cpp
index 610ae7ac7..f5ee26c6a 100644
--- a/src/lib/pbkdf/pwdhash.cpp
+++ b/src/lib/pbkdf/pwdhash.cpp
@@ -20,6 +20,10 @@
    #include <botan/scrypt.h>
 #endif
 
+#if defined(BOTAN_HAS_ARGON2)
+   #include <botan/argon2.h>
+#endif
+
 namespace Botan {
 
 std::unique_ptr<PasswordHashFamily> PasswordHashFamily::create(const std::string& algo_spec,
@@ -52,6 +56,21 @@ std::unique_ptr<PasswordHashFamily> PasswordHashFamily::create(const std::string
       }
 #endif
 
+#if defined(BOTAN_HAS_ARGON2)
+   if(req.algo_name() == "Argon2d")
+      {
+      return std::unique_ptr<PasswordHashFamily>(new Argon2_Family(0));
+      }
+   else if(req.algo_name() == "Argon2i")
+      {
+      return std::unique_ptr<PasswordHashFamily>(new Argon2_Family(1));
+      }
+   else if(req.algo_name() == "Argon2id")
+      {
+      return std::unique_ptr<PasswordHashFamily>(new Argon2_Family(2));
+      }
+#endif
+
 #if defined(BOTAN_HAS_PGP_S2K)
    if(req.algo_name() == "OpenPGP-S2K" && req.arg_count() == 1)
       {
diff --git a/src/lib/prov/pkcs11/p11_ecdh.cpp b/src/lib/prov/pkcs11/p11_ecdh.cpp
index f6e27e513..0cd671bcb 100644
--- a/src/lib/prov/pkcs11/p11_ecdh.cpp
+++ b/src/lib/prov/pkcs11/p11_ecdh.cpp
@@ -74,7 +74,7 @@ class PKCS11_ECDH_KA_Operation final : public PK_Ops::Key_Agreement
          attributes.add_bool(AttributeType::Extractable, true);
          attributes.add_numeric(AttributeType::Class, static_cast< CK_OBJECT_CLASS >(ObjectClass::SecretKey));
          attributes.add_numeric(AttributeType::KeyType, static_cast< CK_KEY_TYPE >(KeyType::GenericSecret));
-         attributes.add_numeric(AttributeType::ValueLen, static_cast< CK_ULONG >(key_len));
+         attributes.add_numeric(AttributeType::ValueLen, key_len);
          m_key.module()->C_DeriveKey(m_key.session().handle(), m_mechanism.data(), m_key.handle(), attributes.data(),
                                      attributes.count(), &secret_handle);
 
diff --git a/src/scripts/tls_scanner/boa.txt b/src/scripts/tls_scanner/boa.txt
new file mode 100644
index 000000000..436b78572
--- /dev/null
+++ b/src/scripts/tls_scanner/boa.txt
@@ -0,0 +1,1 @@
+bankofamerica.com


Test patch:
diff --git a/src/scripts/test_cli.py b/src/scripts/test_cli.py
index c891d5ffb..166f909d5 100755
--- a/src/scripts/test_cli.py
+++ b/src/scripts/test_cli.py
@@ -197,6 +197,16 @@ def cli_bcrypt_tests():
     test_cli("check_bcrypt", "santa $2a$04$gHX4Qg7pDSJuXiPXnmt8leyb.FFzX1Bv4rXwIj2cPSakJ8zNnhIka",
              "Password is NOT valid")
 
+def cli_argon2_tests():
+    password = "s3kr1t"
+    expected = "$argon2id$v=19$m=8,t=1,p=1$2A+I9q2+ZayxDDYC5n2YWw$/Lhx+Jbtlpw+Kxpskfv7+AKhBL/5ebalTJkVC1O5+1E"
+    test_cli("gen_argon2", ['--mem=8', password], expected)
+    test_cli("gen_argon2", ['--mem=8', '--t=1', password], expected)
+    test_cli("gen_argon2", ['--mem=8', '--t=1', '--p=1', password], expected)
+
+    test_cli("check_argon2", [password, expected], "Password is valid")
+    test_cli("check_argon2", ["guessing", expected], "Password is NOT valid")
+
 def cli_gen_dl_group_tests():
 
     pem = """-----BEGIN X9.42 DH PARAMETERS-----
@@ -322,6 +332,14 @@ def cli_pbkdf_tune_tests():
         if expected_pbkdf2.match(line) is None:
             logging.error("Unexpected line '%s'" % (line))
 
+    expected_argon2 = re.compile(r'For (default|[1-9][0-9]*) ms selected Argon2id\([0-9]+,[0-9]+,[0-9]+\)')
+
+    output = test_cli("pbkdf_tune", ["--algo=Argon2id", "--check", "1", "10", "50", "default"], None).split('\n')
+
+    for line in output:
+        if expected_argon2.match(line) is None:
+            logging.error("Unexpected line '%s'" % (line))
+
 def cli_psk_db_tests():
     if not check_for_command("psk_get"):
         return
@@ -733,7 +751,7 @@ def cli_speed_tests():
     math_ops = ['mp_mul', 'mp_div', 'mp_div10', 'modexp', 'random_prime', 'inverse_mod',
                 'rfc3394', 'fpe_fe1', 'ecdsa_recovery', 'ecc_init',
                 'bn_redc', 'nistp_redc', 'ecc_mult', 'ecc_ops', 'os2ecp', 'primality_test',
-                'bcrypt', 'passhash9']
+                'bcrypt', 'passhash9', 'argon2']
 
     format_re = re.compile(r'^.* [0-9]+ /sec; [0-9]+\.[0-9]+ ms/op .*\([0-9]+ (op|ops) in [0-9]+(\.[0-9]+)? ms\)')
     for op in math_ops:
@@ -813,6 +831,7 @@ def main(args=None):
     start_time = time.time()
 
     test_fns = [
+        cli_argon2_tests,
         cli_asn1_tests,
         cli_base58_tests,
         cli_base64_tests,
diff --git a/src/tests/data/passhash/argon2.vec b/src/tests/data/passhash/argon2.vec
new file mode 100644
index 000000000..6de6724d5
--- /dev/null
+++ b/src/tests/data/passhash/argon2.vec
@@ -0,0 +1,38 @@
+
+[Verify]
+Password = pass
+Passhash = $argon2i$v=19$m=8,t=1,p=1$YWFhYWFhYWE$3ney028aI7naIJ/5U///1ICfSVF0Ta4jh2SpJ1jhsCE
+
+Password = pass
+Passhash = $argon2d$v=19$m=8,t=1,p=1$YWFhYWFhYWE$0WM+IC/fpCF2boiNXmu0lnBXDAKes/BHiYuq9abKsWQ
+
+Password = pass
+Passhash = $argon2id$v=19$m=8,t=1,p=1$YWFhYWFhYWE$tPAla38/iYe0rtvQKVaPv04WYar67QEGlc4fhxU185s
+
+[Generate]
+Password = pass
+Mode = 2
+M = 8
+T = 1
+P = 1
+Salt = 313233343536373839616263646566
+OutLen = 32
+Passhash = $argon2id$v=19$m=8,t=1,p=1$MTIzNDU2Nzg5YWJjZGVm$+iAchMa6urtGUvqS2c2ly5SxSb3Jj9S/nq4SZaIgLaI
+
+Password = pass
+Mode = 0
+M = 8192
+T = 3
+P = 1
+Salt = 313233343536373839616263646566
+OutLen = 32
+Passhash = $argon2d$v=19$m=8192,t=3,p=1$MTIzNDU2Nzg5YWJjZGVm$6C4pewOLgibFqWOo9mKTN2xV8KBRq7wjD8PM7DsoV0k
+
+Password = pass
+Mode = 1
+M = 8192
+T = 3
+P = 1
+Salt = 313233343536373839616263646566
+OutLen = 32
+Passhash = $argon2i$v=19$m=8192,t=3,p=1$MTIzNDU2Nzg5YWJjZGVm$7iO3QHobBZHBgjSM+u92dRHJeKpsMdbZ+sLxPjcm9MI
diff --git a/src/tests/test_passhash.cpp b/src/tests/test_passhash.cpp
index b6bd268b1..0b39e7ffc 100644
--- a/src/tests/test_passhash.cpp
+++ b/src/tests/test_passhash.cpp
@@ -14,6 +14,11 @@
    #include <botan/passhash9.h>
 #endif
 
+#if defined(BOTAN_HAS_ARGON2)
+   #include <botan/argon2.h>
+   #include "test_rng.h"
+#endif
+
 namespace Botan_Tests {
 
 namespace {
@@ -76,6 +81,54 @@ BOTAN_REGISTER_TEST("bcrypt", Bcrypt_Tests);
 
 #endif
 
+#if defined(BOTAN_HAS_ARGON2)
+class Argon2_Tests final : public Text_Based_Test
+   {
+   public:
+      Argon2_Tests() : Text_Based_Test("passhash/argon2.vec", "Password,Passhash", "Mode,M,T,P,Salt,OutLen") {}
+
+      Test::Result run_one_test(const std::string& header, const VarMap& vars) override
+         {
+         const std::string password = vars.get_req_str("Password");
+         const std::string passhash = vars.get_req_str("Passhash");
+
+         Test::Result result("Argon2 password hash");
+
+         if(header == "Verify")
+            {
+            const bool accepted = Botan::argon2_check_pwhash(password.data(), password.size(), passhash);
+            result.test_eq("correct hash accepted", accepted, true);
+            }
+         else if(header == "Generate")
+            {
+            const std::vector<uint8_t> salt = vars.get_req_bin("Salt");
+            const size_t y = vars.get_req_sz("Mode");
+            const size_t M = vars.get_req_sz("M");
+            const size_t t = vars.get_req_sz("T");
+            const size_t p = vars.get_req_sz("P");
+            const size_t out_len = vars.get_req_sz("OutLen");
+
+            Fixed_Output_RNG rng(salt);
+
+            const std::string generated = Botan::argon2_generate_pwhash(password.data(), password.size(),
+                                                                        rng,
+                                                                        p, M, t, y, salt.size(), out_len);
+
+            result.test_eq("expected hash generated", generated, passhash);
+            const bool accepted = Botan::argon2_check_pwhash(password.data(), password.size(), generated);
+            result.test_eq("generated hash accepted", accepted, true);
+            }
+         else
+            throw Test_Error("Unexpected header in Argon2 password hash test file");
+
+         return result;
+         }
+   };
+
+BOTAN_REGISTER_TEST("argon2_pass", Argon2_Tests);
+
+#endif
+
 #if defined(BOTAN_HAS_PASSHASH9)
 class Passhash9_Tests final : public Text_Based_Test
    {
diff --git a/src/tests/test_pbkdf.cpp b/src/tests/test_pbkdf.cpp
index 9ddb1e171..88d45e8f6 100644
--- a/src/tests/test_pbkdf.cpp
+++ b/src/tests/test_pbkdf.cpp
@@ -88,48 +88,56 @@ class Pwdhash_Tests : public Test
          {
          std::vector<Test::Result> results;
 
-         for(std::string pwdhash : { "Scrypt", "PBKDF2(SHA-256)", "OpenPGP-S2K(SHA-384)"})
+         for(std::string pwdhash : { "Scrypt", "PBKDF2(SHA-256)", "OpenPGP-S2K(SHA-384)", "Argon2d", "Argon2i", "Argon2id" })
             {
             Test::Result result("Pwdhash " + pwdhash);
             auto pwdhash_fam = Botan::PasswordHashFamily::create(pwdhash);
 
             if(pwdhash_fam)
                {
+               result.start_timer();
+
                const std::vector<uint8_t> salt(8);
                const std::string password = "test";
 
-               auto pwdhash_tune = pwdhash_fam->tune(32, std::chrono::milliseconds(50));
+               auto tuned_pwhash = pwdhash_fam->tune(32, std::chrono::milliseconds(10));
 
                std::vector<uint8_t> output1(32);
-               pwdhash_tune->derive_key(output1.data(), output1.size(),
+               tuned_pwhash->derive_key(output1.data(), output1.size(),
                                         password.c_str(), password.size(),
                                         salt.data(), salt.size());
 
                std::unique_ptr<Botan::PasswordHash> pwhash;
 
-               if(pwdhash_fam->name() == "Scrypt")
+               if(pwdhash_fam->name() == "Scrypt" || pwdhash_fam->name().find("Argon2") == 0)
                   {
-                  pwhash = pwdhash_fam->from_params(pwdhash_tune->memory_param(),
-                                                    pwdhash_tune->iterations(),
-                                                    pwdhash_tune->parallelism());
+                  pwhash = pwdhash_fam->from_params(tuned_pwhash->memory_param(),
+                                                    tuned_pwhash->iterations(),
+                                                    tuned_pwhash->parallelism());
                   }
                else
                   {
-                  pwhash = pwdhash_fam->from_params(pwdhash_tune->iterations());
+                  pwhash = pwdhash_fam->from_params(tuned_pwhash->iterations());
                   }
 
                std::vector<uint8_t> output2(32);
-               pwdhash_tune->derive_key(output2.data(), output2.size(),
-                                        password.c_str(), password.size(),
-                                        salt.data(), salt.size());
+               pwhash->derive_key(output2.data(), output2.size(),
+                                  password.c_str(), password.size(),
+                                  salt.data(), salt.size());
 
                result.test_eq("PasswordHash produced same output when run with same params",
                               output1, output2);
 
+               auto default_pwhash = pwdhash_fam->default_params();
+               std::vector<uint8_t> output3(32);
+               default_pwhash->derive_key(output3.data(), output3.size(),
+                                          password.c_str(), password.size(),
+                                          salt.data(), salt.size());
 
-               //auto pwdhash_tuned = pwdhash_fam->tune(32, std::chrono::milliseconds(150));
-
+               result.end_timer();
                }
+            else
+               result.test_note("No such algo " + pwdhash);
 
             results.push_back(result);
             }