Repository: randombit/botan
Pull request #2705 (2021-04-07 16:19:44)

Split password hashing functionality headers
I want to make the specific families (eg `PKCS5_PBKDF2`) internal only and used only via the base classes. However at the moment some functionality is only available through the headers, namely doing the RFC 4880 S2K count encoding and Argon2 password hash formatting. Move these to new headers which will remain public after the algorithm headers are made internal.


Patch:
diff --git a/doc/api_ref/pbkdf.rst b/doc/api_ref/pbkdf.rst
index 2138d02e9..137ad2d3c 100644
--- a/doc/api_ref/pbkdf.rst
+++ b/doc/api_ref/pbkdf.rst
@@ -9,60 +9,16 @@ key. It is useful to slow down the computation of these computations in order to
 reduce the speed of brute force search, thus they are parameterized in some
 way which allows their required computation to be tuned.
 
-PBKDF
----------
-
-:cpp:class:`PBKDF` is the older API for this functionality, presented in header
-``pbkdf.h``. It does not support Scrypt, nor will it be able to support other
-future hashes (such as Argon2) that may be added in the future. In addition,
-this API requires the passphrase be entered as a ``std::string``, which means
-the secret will be stored in memory that will not be zeroed.
-
-.. cpp:class:: PBKDF
-
-   .. cpp:function:: void pbkdf_iterations(uint8_t out[], size_t out_len, \
-                            const std::string& passphrase, \
-                            const uint8_t salt[], size_t salt_len, \
-                            size_t iterations) const
-
-      Run the PBKDF algorithm for the specified number of iterations,
-      with the given salt, and write output to the buffer.
-
-   .. cpp:function:: void pbkdf_timed(uint8_t out[], size_t out_len, \
-                         const std::string& passphrase, \
-                         const uint8_t salt[], size_t salt_len, \
-                         std::chrono::milliseconds msec, \
-                         size_t& iterations) const
-
-      Choose (via short run-time benchmark) how many iterations to perform
-      in order to run for roughly msec milliseconds. Writes the number
-      of iterations used to reference argument.
-
-   .. cpp:function:: OctetString derive_key( \
-               size_t output_len, const std::string& passphrase, \
-               const uint8_t* salt, size_t salt_len, \
-               size_t iterations) const
-
-   Computes a key from *passphrase* and the *salt* (of length
-   *salt_len* bytes) using an algorithm-specific interpretation of
-   *iterations*, producing a key of length *output_len*.
-
-   Use an iteration count of at least 10000. The salt should be
-   randomly chosen by a good random number generator (see
-   :ref:`random_number_generators` for how), or at the very least
-   unique to this usage of the passphrase.
-
-   If you call this function again with the same parameters, you will
-   get the same key.
-
 PasswordHash
 --------------
 
 .. versionadded:: 2.8.0
 
-This API has two classes, one representing the algorithm (such as
-"PBKDF2(SHA-256)", or "Scrypt") and the other representing a specific instance
-of the problem which is fully specified (say "Scrypt" with N=8192,r=64,p=8).
+This API, declared in ``pwdhash.h``, has two classes, ``PasswordHashFamily``
+representing the general algorithm, such as "PBKDF2(SHA-256)", or "Scrypt", and
+``PasswordHash`` representing a specific instance of the problem which is fully
+specified with all parameters (say "Scrypt" with ``N`` = 8192, ``r`` = 64, and
+``p`` = 8) and which can be used to derive keys.
 
 .. cpp:class:: PasswordHash
 
@@ -70,20 +26,41 @@ of the problem which is fully specified (say "Scrypt" with N=8192,r=64,p=8).
                      const char* password, const size_t password_len, \
                      const uint8_t salt[], size_t salt_len) const
 
-      Derive a key, placing it into output
+      Derive a key from the specified password and salt, placing it into output.
 
    .. cpp:function:: std::string to_string() const
 
       Return a descriptive string including the parameters (iteration count, etc)
 
+   .. cpp:function:: size_t iterations() const
+
+      Return the iteration parameter
+
+   .. cpp:function:: size_t memory_param() const
+
+      Return the memory usage parameter, or 0 if the algorithm does not offer
+      a memory usage option.
+
+   .. cpp:function:: size_t parallelism() const
+
+      Returns the parallelism parameter, or 0 if the algorithm does not offer a
+      parallelism option.
+
+   .. cpp:function:: size_t total_memory_usage() const
+
+      Return a guesstimate of the total number of bytes of memory consumed when
+      running this algorithm. If the function is not intended to be memory-hard
+      and uses an effictively fixed amount of memory when running, this function
+      returns 0.
+
 The ``PasswordHashFamily`` creates specific instances of ``PasswordHash``:
 
 .. cpp:class:: PasswordHashFamily
 
    .. cpp:function:: static std::unique_ptr<PasswordHashFamily> create(const std::string& what)
 
-      For example "PBKDF2(SHA-256)", "Scrypt", "OpenPGP-S2K(SHA-384)". Returns
-      null if not available.
+      For example "PBKDF2(SHA-256)", "Scrypt", "Argon2id". Returns null if the
+      algorithm is not available.
 
    .. cpp:function:: std::unique_ptr<PasswordHash> default_params() const
 
@@ -100,9 +77,11 @@ The ``PasswordHashFamily`` creates specific instances of ``PasswordHash``:
          size_t i1, size_t i2 = 0, size_t i3 = 0) const
 
       Create a password hash using some scheme specific format. Parameters are as follows:
-      - For PBKDF2, PGP-S2K, and Bcrypt-PBKDF, i1 is iterations
-      - Scrypt uses N, r, p for i{1-3}
-      - Argon2 family uses memory (in KB), iterations, and parallelism for i{1-3}
+
+      * For PBKDF2, PGP-S2K, and Bcrypt-PBKDF, ``i1`` is iterations
+      * Scrypt uses ``i1`` == ``N``, ``i2`` == ``r``, and ``i3`` == ``p``
+      * Argon2 family uses ``i1`` == ``M``, ``i2`` == ``t``, and ``i3`` == ``p``
+
       All unneeded parameters should be set to 0 or left blank.
 
 Available Schemes
@@ -111,8 +90,9 @@ Available Schemes
 General Recommendations
 ^^^^^^^^^^^^^^^^^^^^^^^^^
 
-If you need wide interoperability use PBKDF2 with HMAC-SHA256 and at least 10K
-iterations. If you don't, use Scrypt with N=32768, r=8, p=1.
+If you need wide interoperability use PBKDF2 with HMAC-SHA256 and at least 50K
+iterations. If you don't, use Argon2id with p=1, t=3 and M as large as you
+can reasonable set (say 1 gigabyte).
 
 You can test how long a particular PBKDF takes to execute using the cli tool
 ``pbkdf_tune``::
@@ -120,11 +100,10 @@ You can test how long a particular PBKDF takes to execute using the cli tool
   $ ./botan pbkdf_tune --algo=Argon2id 500 --max-mem=192 --check
   For 500 ms selected Argon2id(196608,3,1) using 192 MiB took 413.159 msec to compute
 
-This indicates the parameters chosen by the fast auto-tuning algorithm and
+This returns the parameters chosen by the fast auto-tuning algorithm, and
 because ``--check`` was supplied the hash is also executed with the full set of
 parameters and timed.
 
-
 PBKDF2
 ^^^^^^^^^^^^
 
@@ -132,41 +111,30 @@ PBKDF2 is the "standard" password derivation scheme, widely implemented in many
 different libraries. It uses HMAC internally and requires choosing a hash
 function to use. (If in doubt use SHA-256 or SHA-512). It also requires choosing
 an iteration count, which makes brute force attacks more expensive. Use *at
-least* 10000 and preferably much more.
+least* 50000 and preferably much more. Using 250,000 would not be unreasonable.
 
 Scrypt
 ^^^^^^^^^^
 
+.. versionadded:: 2.7.0
+
 Scrypt is a relatively newer design which is "memory hard" - in
 addition to requiring large amounts of CPU power it uses a large block
 of memory to compute the hash. This makes brute force attacks using
 ASICs substantially more expensive.
 
-Scrypt is not supported through :cpp:class:`PBKDF`, only :cpp:class:`PasswordHash`,
-starting in 2.8.0. In addition, starting in version 2.7.0, scrypt is available
-with this function:
-
-.. cpp:function:: void scrypt(uint8_t output[], size_t output_len, \
-                              const std::string& password, \
-                              const uint8_t salt[], size_t salt_len, \
-                              size_t N, size_t r, size_t p)
+Scrypt has three parameters, usually termed ``N``, ``r``, and ``p``.  ``N`` is
+the primary control of the workfactor, and must be a power of 2. For interactive
+logins use 32768, for protection of secret keys or backups use 1048576.
 
-   Computes the Scrypt using the password and salt, and produces an output
-   of arbitrary length.
+The ``r`` parameter controls how 'wide' the internal hashing operation is. It
+also increases the amount of memory that is used. Values from 1 to 8 are
+reasonable.
 
-   The N, r, p parameters control how much work and memory Scrypt
-   uses.  N is the primary control of the workfactor, and must be a
-   power of 2. For interactive logins use 32768, for protection of
-   secret keys or backups use 1048576.
+Setting ``p`` parameter to greater than 1 splits up the work in a way that up
+to p processors can work in parallel.
 
-   The r parameter controls how 'wide' the internal hashing operation
-   is. It also increases the amount of memory that is used. Values
-   from 1 to 8 are reasonable.
-
-   Setting p parameter to greater than one splits up the work in a way
-   that up to p processors can work in parallel.
-
-   As a general recommendation, use N=32768, r=8, p=1
+As a general recommendation, use ``N`` = 32768, ``r`` = 8, ``p`` = 1
 
 Argon2
 ^^^^^^^^^^
@@ -214,3 +182,59 @@ iteration count (this should be significantly larger than the size of the
 longest passphrase that might reasonably be used; somewhere from 1024 to 65536
 would probably be about right). Using both a reasonably sized salt and a large
 iteration count is highly recommended to prevent password guessing attempts.
+
+PBKDF
+---------
+
+:cpp:class:`PBKDF` is the older API for this functionality, presented in header
+``pbkdf.h``. It only supports PBKDF2 and the PGP S2K algorithm, not
+Scrypt, Argon2, or bcrypt. This interface is deprecated and will be removed
+in a future major release.
+
+In addition, this API requires the passphrase be entered as a
+``std::string``, which means the secret will be stored in memory that
+will not be zeroed.
+
+.. cpp:class:: PBKDF
+
+   .. cpp:function:: static std::unique_ptr<PBKDF> create(const std::string& algo_spec, \
+                                                          const std::string& provider = "")
+
+      Return a newly created PBKDF object. The name should be in the
+      format "PBKDF2(HASHNAME)", "PBKDF2(HMAC(HASHNAME))", or
+      "OpenPGP-S2K".  Returns null if the algorithm is not available.
+
+   .. cpp:function:: void pbkdf_iterations(uint8_t out[], size_t out_len, \
+                            const std::string& passphrase, \
+                            const uint8_t salt[], size_t salt_len, \
+                            size_t iterations) const
+
+      Run the PBKDF algorithm for the specified number of iterations,
+      with the given salt, and write output to the buffer.
+
+   .. cpp:function:: void pbkdf_timed(uint8_t out[], size_t out_len, \
+                         const std::string& passphrase, \
+                         const uint8_t salt[], size_t salt_len, \
+                         std::chrono::milliseconds msec, \
+                         size_t& iterations) const
+
+      Choose (via short run-time benchmark) how many iterations to perform
+      in order to run for roughly msec milliseconds. Writes the number
+      of iterations used to reference argument.
+
+   .. cpp:function:: OctetString derive_key( \
+               size_t output_len, const std::string& passphrase, \
+               const uint8_t* salt, size_t salt_len, \
+               size_t iterations) const
+
+   Computes a key from *passphrase* and the *salt* (of length
+   *salt_len* bytes) using an algorithm-specific interpretation of
+   *iterations*, producing a key of length *output_len*.
+
+   Use an iteration count of at least 10000. The salt should be
+   randomly chosen by a good random number generator (see
+   :ref:`random_number_generators` for how), or at the very least
+   unique to this usage of the passphrase.
+
+   If you call this function again with the same parameters, you will
+   get the same key.
diff --git a/src/lib/pbkdf/argon2/argon2fmt.cpp b/src/lib/passhash/argon2fmt/argon2fmt.cpp
similarity index 99%
rename from src/lib/pbkdf/argon2/argon2fmt.cpp
rename to src/lib/passhash/argon2fmt/argon2fmt.cpp
index 974710b29..1bec44593 100644
--- a/src/lib/pbkdf/argon2/argon2fmt.cpp
+++ b/src/lib/passhash/argon2fmt/argon2fmt.cpp
@@ -4,6 +4,7 @@
 * Botan is released under the Simplified BSD License (see license.txt)
 */
 
+#include <botan/argon2fmt.h>
 #include <botan/argon2.h>
 #include <botan/rng.h>
 #include <botan/base64.h>
diff --git a/src/lib/passhash/argon2fmt/argon2fmt.h b/src/lib/passhash/argon2fmt/argon2fmt.h
new file mode 100644
index 000000000..1ad43f986
--- /dev/null
+++ b/src/lib/passhash/argon2fmt/argon2fmt.h
@@ -0,0 +1,36 @@
+/**
+* (C) 2018,2019,2021 Jack Lloyd
+*
+* Botan is released under the Simplified BSD License (see license.txt)
+*/
+
+#ifndef BOTAN_ARGON2_FMT_H_
+#define BOTAN_ARGON2_FMT_H_
+
+#include <botan/types.h>
+#include <string>
+
+namespace Botan {
+
+class RandomNumberGenerator;
+
+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,
+                          uint8_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 password_len the length of password
+* @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/passhash/argon2fmt/info.txt b/src/lib/passhash/argon2fmt/info.txt
new file mode 100644
index 000000000..2d1bae416
--- /dev/null
+++ b/src/lib/passhash/argon2fmt/info.txt
@@ -0,0 +1,13 @@
+<defines>
+ARGON2_FMT -> 20210407
+</defines>
+
+<requires>
+argon2
+base64
+rng
+</requires>
+
+<header:public>
+argon2fmt.h
+</header:public>
diff --git a/src/lib/pbkdf/argon2/argon2.h b/src/lib/pbkdf/argon2/argon2.h
index 3a1b859f0..82880031a 100644
--- a/src/lib/pbkdf/argon2/argon2.h
+++ b/src/lib/pbkdf/argon2/argon2.h
@@ -9,6 +9,10 @@
 
 #include <botan/pwdhash.h>
 
+#if defined(BOTAN_HAS_ARGON2_FMT)
+  #include <botan/argon2fmt.h>
+#endif
+
 //BOTAN_FUTURE_INTERNAL_HEADER(argon2.h)
 
 namespace Botan {
@@ -98,21 +102,6 @@ void BOTAN_PUBLIC_API(2,11) argon2(uint8_t output[], size_t output_len,
                                    const uint8_t ad[], size_t ad_len,
                                    uint8_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,
-                          uint8_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 password_len the length of password
-* @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/info.txt b/src/lib/pbkdf/argon2/info.txt
index c0a33f580..c2eff1936 100644
--- a/src/lib/pbkdf/argon2/info.txt
+++ b/src/lib/pbkdf/argon2/info.txt
@@ -1,13 +1,11 @@
 <defines>
-ARGON2 -> 20190824
+ARGON2 -> 20210407
 </defines>
 
 <requires>
 blake2
-base64
 </requires>
 
-
 <header:public>
 argon2.h
 </header:public>
diff --git a/src/lib/pbkdf/pgp_s2k/info.txt b/src/lib/pbkdf/pgp_s2k/info.txt
index 4868dd070..a1c048eaf 100644
--- a/src/lib/pbkdf/pgp_s2k/info.txt
+++ b/src/lib/pbkdf/pgp_s2k/info.txt
@@ -1,5 +1,6 @@
 <defines>
 PGP_S2K -> 20170527
+RFC4880 -> 20210407
 </defines>
 
 <requires>
@@ -8,4 +9,5 @@ hash
 
 <header:public>
 pgp_s2k.h
+rfc4880.h
 </header:public>
diff --git a/src/lib/pbkdf/pgp_s2k/pgp_s2k.cpp b/src/lib/pbkdf/pgp_s2k/pgp_s2k.cpp
index 11409417a..0d78f4358 100644
--- a/src/lib/pbkdf/pgp_s2k/pgp_s2k.cpp
+++ b/src/lib/pbkdf/pgp_s2k/pgp_s2k.cpp
@@ -13,64 +13,6 @@
 
 namespace Botan {
 
-/*
-PGP stores the iteration count as a single byte
-Thus it can only actually take on one of 256 values, based on the
-formula in RFC 4880 section 3.6.1.3
-*/
-static const uint32_t OPENPGP_S2K_ITERS[256] = {
-   1024, 1088, 1152, 1216, 1280, 1344, 1408, 1472, 1536, 1600,
-   1664, 1728, 1792, 1856, 1920, 1984, 2048, 2176, 2304, 2432,
-   2560, 2688, 2816, 2944, 3072, 3200, 3328, 3456, 3584, 3712,
-   3840, 3968, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888,
-   6144, 6400, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8704,
-   9216, 9728, 10240, 10752, 11264, 11776, 12288, 12800, 13312,
-   13824, 14336, 14848, 15360, 15872, 16384, 17408, 18432, 19456,
-   20480, 21504, 22528, 23552, 24576, 25600, 26624, 27648, 28672,
-   29696, 30720, 31744, 32768, 34816, 36864, 38912, 40960, 43008,
-   45056, 47104, 49152, 51200, 53248, 55296, 57344, 59392, 61440,
-   63488, 65536, 69632, 73728, 77824, 81920, 86016, 90112, 94208,
-   98304, 102400, 106496, 110592, 114688, 118784, 122880, 126976,
-   131072, 139264, 147456, 155648, 163840, 172032, 180224, 188416,
-   196608, 204800, 212992, 221184, 229376, 237568, 245760, 253952,
-   262144, 278528, 294912, 311296, 327680, 344064, 360448, 376832,
-   393216, 409600, 425984, 442368, 458752, 475136, 491520, 507904,
-   524288, 557056, 589824, 622592, 655360, 688128, 720896, 753664,
-   786432, 819200, 851968, 884736, 917504, 950272, 983040, 1015808,
-   1048576, 1114112, 1179648, 1245184, 1310720, 1376256, 1441792,
-   1507328, 1572864, 1638400, 1703936, 1769472, 1835008, 1900544,
-   1966080, 2031616, 2097152, 2228224, 2359296, 2490368, 2621440,
-   2752512, 2883584, 3014656, 3145728, 3276800, 3407872, 3538944,
-   3670016, 3801088, 3932160, 4063232, 4194304, 4456448, 4718592,
-   4980736, 5242880, 5505024, 5767168, 6029312, 6291456, 6553600,
-   6815744, 7077888, 7340032, 7602176, 7864320, 8126464, 8388608,
-   8912896, 9437184, 9961472, 10485760, 11010048, 11534336,
-   12058624, 12582912, 13107200, 13631488, 14155776, 14680064,
-   15204352, 15728640, 16252928, 16777216, 17825792, 18874368,
-   19922944, 20971520, 22020096, 23068672, 24117248, 25165824,
-   26214400, 27262976, 28311552, 29360128, 30408704, 31457280,
-   32505856, 33554432, 35651584, 37748736, 39845888, 41943040,
-   44040192, 46137344, 48234496, 50331648, 52428800, 54525952,
-   56623104, 58720256, 60817408, 62914560, 65011712 };
-
-uint8_t RFC4880_encode_count(size_t desired_iterations)
-   {
-   if(desired_iterations <= OPENPGP_S2K_ITERS[0])
-      return 0;
-
-   if(desired_iterations >= OPENPGP_S2K_ITERS[255])
-      return 255;
-
-   auto i = std::lower_bound(OPENPGP_S2K_ITERS, OPENPGP_S2K_ITERS + 256, desired_iterations);
-
-   return static_cast<uint8_t>(i - OPENPGP_S2K_ITERS);
-   }
-
-size_t RFC4880_decode_count(uint8_t iter)
-   {
-   return OPENPGP_S2K_ITERS[iter];
-   }
-
 namespace {
 
 void pgp_s2k(HashFunction& hash,
diff --git a/src/lib/pbkdf/pgp_s2k/pgp_s2k.h b/src/lib/pbkdf/pgp_s2k/pgp_s2k.h
index 8d148e725..c56142a1d 100644
--- a/src/lib/pbkdf/pgp_s2k/pgp_s2k.h
+++ b/src/lib/pbkdf/pgp_s2k/pgp_s2k.h
@@ -12,36 +12,12 @@
 #include <botan/pbkdf.h>
 #include <botan/pwdhash.h>
 #include <botan/hash.h>
-
-/*
-This header will not be fully internal - the RFC4880 count
-encoding functions will remain here. But the definition of
-OpenPGP_S2K will be made internal
-*/
+#include <botan/rfc4880.h>
 
 //BOTAN_FUTURE_INTERNAL_HEADER(pgp_s2k.h)
 
 namespace Botan {
 
-/**
-* RFC 4880 encodes the iteration count to a single-byte value
-*/
-uint8_t BOTAN_PUBLIC_API(2,8) RFC4880_encode_count(size_t iterations);
-
-/**
-* Decode the iteration count from RFC 4880 encoding
-*/
-size_t BOTAN_PUBLIC_API(2,8) RFC4880_decode_count(uint8_t encoded_iter);
-
-/**
-* Round an arbitrary iteration count to next largest iteration count
-* supported by RFC4880 encoding.
-*/
-inline size_t RFC4880_round_iterations(size_t iterations)
-   {
-   return RFC4880_decode_count(RFC4880_encode_count(iterations));
-   }
-
 /**
 * OpenPGP's S2K
 *
diff --git a/src/lib/pbkdf/pgp_s2k/rfc4880.cpp b/src/lib/pbkdf/pgp_s2k/rfc4880.cpp
new file mode 100644
index 000000000..b47a3e5cb
--- /dev/null
+++ b/src/lib/pbkdf/pgp_s2k/rfc4880.cpp
@@ -0,0 +1,74 @@
+/*
+* (C) 2021 Jack Lloyd
+*
+* Botan is released under the Simplified BSD License (see license.txt)
+*/
+
+#include <botan/rfc4880.h>
+#include <algorithm>
+
+namespace Botan {
+
+namespace {
+
+/*
+PGP stores the iteration count as a single byte
+Thus it can only actually take on one of 256 values, based on the
+formula in RFC 4880 section 3.6.1.3
+*/
+static const uint32_t OPENPGP_S2K_ITERS[256] = {
+   1024, 1088, 1152, 1216, 1280, 1344, 1408, 1472, 1536, 1600,
+   1664, 1728, 1792, 1856, 1920, 1984, 2048, 2176, 2304, 2432,
+   2560, 2688, 2816, 2944, 3072, 3200, 3328, 3456, 3584, 3712,
+   3840, 3968, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888,
+   6144, 6400, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8704,
+   9216, 9728, 10240, 10752, 11264, 11776, 12288, 12800, 13312,
+   13824, 14336, 14848, 15360, 15872, 16384, 17408, 18432, 19456,
+   20480, 21504, 22528, 23552, 24576, 25600, 26624, 27648, 28672,
+   29696, 30720, 31744, 32768, 34816, 36864, 38912, 40960, 43008,
+   45056, 47104, 49152, 51200, 53248, 55296, 57344, 59392, 61440,
+   63488, 65536, 69632, 73728, 77824, 81920, 86016, 90112, 94208,
+   98304, 102400, 106496, 110592, 114688, 118784, 122880, 126976,
+   131072, 139264, 147456, 155648, 163840, 172032, 180224, 188416,
+   196608, 204800, 212992, 221184, 229376, 237568, 245760, 253952,
+   262144, 278528, 294912, 311296, 327680, 344064, 360448, 376832,
+   393216, 409600, 425984, 442368, 458752, 475136, 491520, 507904,
+   524288, 557056, 589824, 622592, 655360, 688128, 720896, 753664,
+   786432, 819200, 851968, 884736, 917504, 950272, 983040, 1015808,
+   1048576, 1114112, 1179648, 1245184, 1310720, 1376256, 1441792,
+   1507328, 1572864, 1638400, 1703936, 1769472, 1835008, 1900544,
+   1966080, 2031616, 2097152, 2228224, 2359296, 2490368, 2621440,
+   2752512, 2883584, 3014656, 3145728, 3276800, 3407872, 3538944,
+   3670016, 3801088, 3932160, 4063232, 4194304, 4456448, 4718592,
+   4980736, 5242880, 5505024, 5767168, 6029312, 6291456, 6553600,
+   6815744, 7077888, 7340032, 7602176, 7864320, 8126464, 8388608,
+   8912896, 9437184, 9961472, 10485760, 11010048, 11534336,
+   12058624, 12582912, 13107200, 13631488, 14155776, 14680064,
+   15204352, 15728640, 16252928, 16777216, 17825792, 18874368,
+   19922944, 20971520, 22020096, 23068672, 24117248, 25165824,
+   26214400, 27262976, 28311552, 29360128, 30408704, 31457280,
+   32505856, 33554432, 35651584, 37748736, 39845888, 41943040,
+   44040192, 46137344, 48234496, 50331648, 52428800, 54525952,
+   56623104, 58720256, 60817408, 62914560, 65011712 };
+
+}
+
+uint8_t RFC4880_encode_count(size_t desired_iterations)
+   {
+   if(desired_iterations <= OPENPGP_S2K_ITERS[0])
+      return 0;
+
+   if(desired_iterations >= OPENPGP_S2K_ITERS[255])
+      return 255;
+
+   auto i = std::lower_bound(OPENPGP_S2K_ITERS, OPENPGP_S2K_ITERS + 256, desired_iterations);
+
+   return static_cast<uint8_t>(i - OPENPGP_S2K_ITERS);
+   }
+
+size_t RFC4880_decode_count(uint8_t iter)
+   {
+   return OPENPGP_S2K_ITERS[iter];
+   }
+
+}
diff --git a/src/lib/pbkdf/pgp_s2k/rfc4880.h b/src/lib/pbkdf/pgp_s2k/rfc4880.h
new file mode 100644
index 000000000..8d751985c
--- /dev/null
+++ b/src/lib/pbkdf/pgp_s2k/rfc4880.h
@@ -0,0 +1,39 @@
+/*
+* (C) 2021 Jack Lloyd
+*
+* Botan is released under the Simplified BSD License (see license.txt)
+*/
+
+#ifndef BOTAN_RFC4880_H_
+#define BOTAN_RFC4880_H_
+
+#include <botan/types.h>
+
+namespace Botan {
+
+/*
+Helpers for encoding PGP S2K values (see RFC 4880)
+*/
+
+/**
+* RFC 4880 encodes the iteration count to a single-byte value
+*/
+uint8_t BOTAN_PUBLIC_API(2,8) RFC4880_encode_count(size_t iterations);
+
+/**
+* Decode the iteration count from RFC 4880 encoding
+*/
+size_t BOTAN_PUBLIC_API(2,8) RFC4880_decode_count(uint8_t encoded_iter);
+
+/**
+* Round an arbitrary iteration count to next largest iteration count
+* supported by RFC4880 encoding.
+*/
+inline size_t RFC4880_round_iterations(size_t iterations)
+   {
+   return RFC4880_decode_count(RFC4880_encode_count(iterations));
+   }
+
+}
+
+#endif
diff --git a/src/lib/pbkdf/pwdhash.h b/src/lib/pbkdf/pwdhash.h
index 4255e1d43..d57775e73 100644
--- a/src/lib/pbkdf/pwdhash.h
+++ b/src/lib/pbkdf/pwdhash.h
@@ -50,7 +50,7 @@ class BOTAN_PUBLIC_API(2,8) PasswordHash
       virtual size_t parallelism() const { return 0; }
 
       /**
-      * Returns an estimate of the total memory usage required to perform this
+      * Returns an estimate of the total number of bytes required to perform this
       * key derivation.
       *
       * If this algorithm uses a small and constant amount of memory, with no


Test patch:
diff --git a/src/tests/test_passhash.cpp b/src/tests/test_passhash.cpp
index 849163322..dc1d275a4 100644
--- a/src/tests/test_passhash.cpp
+++ b/src/tests/test_passhash.cpp
@@ -14,8 +14,8 @@
    #include <botan/passhash9.h>
 #endif
 
-#if defined(BOTAN_HAS_ARGON2)
-   #include <botan/argon2.h>
+#if defined(BOTAN_HAS_ARGON2_FMT)
+   #include <botan/argon2fmt.h>
    #include "test_rng.h"
 #endif
 
@@ -81,7 +81,7 @@ BOTAN_REGISTER_TEST("pbkdf", "bcrypt", Bcrypt_Tests);
 
 #endif
 
-#if defined(BOTAN_HAS_ARGON2)
+#if defined(BOTAN_HAS_ARGON2_FMT)
 class Argon2_Tests final : public Text_Based_Test
    {
    public: