]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
Use OpenSSL's scrypt for secure credential storage
authorRemi Gacogne <remi.gacogne@powerdns.com>
Sat, 12 Jun 2021 15:40:09 +0000 (17:40 +0200)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Thu, 16 Sep 2021 12:12:27 +0000 (14:12 +0200)
m4/pdns_check_libcrypto.m4
m4/pdns_with_libsodium.m4
pdns/credentials.cc
pdns/credentials.hh
pdns/pdnsutil.cc
pdns/rec_control.cc
pdns/recursordist/Makefile.am
pdns/test-credentials_cc.cc

index 789f29416432d5395c09b4b29bbf5e8cec9b8476..4ceb18fa42e3f554360753222e20093c41689077 100644 (file)
@@ -112,6 +112,7 @@ AC_DEFUN([PDNS_CHECK_LIBCRYPTO], [
         [
             AC_MSG_RESULT([yes])
             AC_CHECK_FUNCS([RAND_bytes RAND_pseudo_bytes CRYPTO_memcmp OPENSSL_init_crypto EVP_MD_CTX_new EVP_MD_CTX_free RSA_get0_key])
+            AC_CHECK_DECL(EVP_PKEY_CTX_set1_scrypt_salt, [AC_DEFINE([HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT], [1], [Define to 1 if you have EVP_PKEY_CTX_set1_scrypt_saltEVP_PKEY_CTX_set1_scrypt_salt]) ], [], [#include <openssl/kdf.h>])
             $1
         ], [
             AC_MSG_RESULT([no])
index 2c316de7cae163ebeb0b396c594af5d0c65e2023..5b1198205507597f235ff9f331d383c9b373bde8 100644 (file)
@@ -15,7 +15,7 @@ AC_DEFUN([PDNS_WITH_LIBSODIUM], [
         save_LIBS=$LIBS
         CFLAGS="$LIBSODIUM_CFLAGS $CFLAGS"
         LIBS="$LIBSODIUM_LIBS $LIBS"
-        AC_CHECK_FUNCS([crypto_box_easy_afternm crypto_box_curve25519xchacha20poly1305_easy randombytes_stir sodium_memcmp crypto_pwhash_str])
+        AC_CHECK_FUNCS([crypto_box_easy_afternm crypto_box_curve25519xchacha20poly1305_easy randombytes_stir sodium_memcmp])
         CFLAGS=$save_CFLAGS
         LIBS=$save_LIBS
       ], [ : ])
index 980e5d3dd04fe36ea940f9cde8f65831737428fc..ddb23439f79043742fee989c548cda11333682e2 100644 (file)
  */
 #include "config.h"
 
+#include <cmath>
 #include <stdexcept>
 
 #ifdef HAVE_LIBSODIUM
 #include <sodium.h>
 #endif
 
+#ifdef HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT
+#include <openssl/evp.h>
+#include <openssl/kdf.h>
+#include <openssl/rand.h>
+#endif
+
 #include <fcntl.h>
 #include <sys/stat.h>
 #include <unistd.h>
 
+#include "base64.hh"
 #include "credentials.hh"
 #include "misc.hh"
 
+#ifdef HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT
+static size_t const pwhash_max_size = 128U; /* maximum size of the output */
+static size_t const pwhash_output_size = 32U; /* size of the hashed output (before base64 encoding) */
+static unsigned int const pwhash_salt_size = 16U; /* size of the salt (before base64 encoding */
+static uint64_t const pwhash_max_work_factor =  32768U; /* max N for interactive login purposes */
+
+/* PHC string format, storing N as log2(N) as done by passlib.
+   for now we only support one algo but we might have to change that later */
+static std::string const pwhash_prefix = "$scrypt$";
+static size_t const pwhash_prefix_size = pwhash_prefix.size();
+#endif
+
+uint64_t const CredentialsHolder::s_defaultWorkFactor{1024U}; /* N */
+uint64_t const CredentialsHolder::s_defaultParallelFactor{1U}; /* p */
+uint64_t const CredentialsHolder::s_defaultBlockSize{8U}; /* r */
+
+SensitiveData::SensitiveData(std::string&& data): d_data(std::move(data))
+{
+#ifdef HAVE_LIBSODIUM
+  sodium_mlock(d_data.data(), d_data.size());
+#endif
+}
+
+SensitiveData::SensitiveData(size_t bytes)
+{
+  d_data.resize(bytes);
+#ifdef HAVE_LIBSODIUM
+  sodium_mlock(d_data.data(), d_data.size());
+#endif
+}
+
+SensitiveData::~SensitiveData()
+{
+  clear();
+}
+
+void SensitiveData::clear()
+{
+#ifdef HAVE_LIBSODIUM
+  sodium_munlock(d_data.data(), d_data.size());
+#endif
+  d_data.clear();
+}
+
+#ifdef HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT
+static std::string hashPasswordInternal(const std::string& password, const std::string& salt, uint64_t workFactor, uint64_t parallelFactor, uint64_t blockSize)
+{
+  auto pctx = std::unique_ptr<EVP_PKEY_CTX, void(*)(EVP_PKEY_CTX*)>(EVP_PKEY_CTX_new_id(EVP_PKEY_SCRYPT, nullptr), EVP_PKEY_CTX_free);
+  if (!pctx) {
+    throw std::runtime_error("Error getting a scrypt context to hash the supplied password");
+  }
+
+  if (EVP_PKEY_derive_init(pctx.get()) <= 0) {
+    throw std::runtime_error("Error intializing the scrypt context to hash the supplied password");
+  }
+
+  if (EVP_PKEY_CTX_set1_pbe_pass(pctx.get(), reinterpret_cast<const unsigned char*>(password.data()), password.size()) <= 0) {
+    throw std::runtime_error("Error adding the password to the scrypt context to hash the supplied password");
+ }
+
+  if (EVP_PKEY_CTX_set1_scrypt_salt(pctx.get(), salt.data(), salt.size()) <= 0) {
+    throw std::runtime_error("Error adding the salt to the scrypt context to hash the supplied password");
+  }
+
+  if (EVP_PKEY_CTX_set_scrypt_N(pctx.get(), workFactor) <= 0) {
+    throw std::runtime_error("Error setting the work factor to the scrypt context to hash the supplied password");
+  }
+
+  if (EVP_PKEY_CTX_set_scrypt_r(pctx.get(), blockSize) <= 0) {
+    throw std::runtime_error("Error setting the block size to the scrypt context to hash the supplied password");
+  }
+
+  if (EVP_PKEY_CTX_set_scrypt_p(pctx.get(), parallelFactor) <= 0) {
+    throw std::runtime_error("Error setting the parallel factor to the scrypt context to hash the supplied password");
+  }
+
+  std::string out;
+  out.resize(pwhash_output_size);
+  size_t outlen = out.size();
+
+  if (EVP_PKEY_derive(pctx.get(), reinterpret_cast<unsigned char*>(out.data()), &outlen) <= 0 || outlen != pwhash_output_size) {
+    throw std::runtime_error("Error deriving the output from the scrypt context to hash the supplied password");
+  }
+
+  return out;
+}
+#endif
+
+static std::string generateRandomSalt()
+{
+#ifdef HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT
+  /* generate a random salt */
+  std::string salt;
+  salt.resize(pwhash_salt_size);
+
+  if (RAND_bytes(reinterpret_cast<unsigned char*>(salt.data()), salt.size()) != 1) {
+    throw std::runtime_error("Error while generating a salt to hash the supplied password");
+  }
+
+  return salt;
+#else
+  throw std::runtime_error("Generating a salted password requires scrypt support in OpenSSL, and it is not available");
+#endif
+}
+
 std::string hashPassword(const std::string& password)
 {
-#ifdef HAVE_CRYPTO_PWHASH_STR
+#ifdef HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT
   std::string result;
-  result.resize(crypto_pwhash_STRBYTES);
-  sodium_mlock(result.data(), result.size());
+  result.reserve(pwhash_max_size);
 
-  int res = crypto_pwhash_str(const_cast<char*>(result.c_str()),
-                              password.c_str(),
-                              password.size(),
-                              crypto_pwhash_OPSLIMIT_INTERACTIVE,
-                              crypto_pwhash_MEMLIMIT_INTERACTIVE);
-  if (res != 0) {
-    throw std::runtime_error("Error while hashing the supplied password");
-  }
+  result.append(pwhash_prefix);
+  result.append("ln=");
+  result.append(std::to_string(static_cast<uint64_t>(std::log2(CredentialsHolder::s_defaultWorkFactor))));
+  result.append(",p=");
+  result.append(std::to_string(CredentialsHolder::s_defaultParallelFactor));
+  result.append(",r=");
+  result.append(std::to_string(CredentialsHolder::s_defaultBlockSize));
+  result.append("$");
+  auto salt = generateRandomSalt();
+  result.append(Base64Encode(salt));
+  result.append("$");
+
+  auto out = hashPasswordInternal(password, salt, CredentialsHolder::s_defaultWorkFactor, CredentialsHolder::s_defaultParallelFactor, CredentialsHolder::s_defaultBlockSize);
+
+  result.append(Base64Encode(out));
 
   return result;
 #else
-  throw std::runtime_error("Hashing a password requires libsodium support, and it is not available");
+  throw std::runtime_error("Hashing a password requires scrypt support in OpenSSL, and it is not available");
+#endif
+}
+
+bool verifyPassword(const std::string& binaryHash, const std::string& salt, uint64_t workFactor, uint64_t parallelFactor, uint64_t blockSize, const std::string& binaryPassword)
+{
+#ifdef HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT
+  auto expected = hashPasswordInternal(binaryPassword, salt, workFactor, parallelFactor, blockSize);
+  return constantTimeStringEquals(expected, binaryHash);
+#else
+  throw std::runtime_error("Hashing a password requires scrypt support in OpenSSL, and it is not available");
+#endif
+}
+
+/* parse a hashed password in PHC string format */
+static void parseHashed(const std::string& hash, std::string& salt, std::string& hashedPassword, uint64_t& workFactor, uint64_t& parallelFactor, uint64_t& blockSize)
+{
+#ifdef HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT
+  auto parametersEnd = hash.find('$', pwhash_prefix.size());
+  if (parametersEnd == std::string::npos || parametersEnd == hash.size()) {
+    cerr<<hash<<endl;
+    throw std::runtime_error("Invalid hashed password format, no parameters");
+  }
+
+  auto parametersStr = hash.substr(pwhash_prefix.size(), parametersEnd);
+  std::vector<std::string> parameters;
+  parameters.reserve(3);
+  stringtok(parameters, parametersStr, ",");
+  if (parameters.size() != 3) {
+    throw std::runtime_error("Invalid hashed password format, expecting 3 parameters, got " + std::to_string(parameters.size()));
+  }
+
+  if (!boost::starts_with(parameters.at(0), "ln=")) {
+    throw std::runtime_error("Invalid hashed password format, ln= parameter not found");
+  }
+
+  if (!boost::starts_with(parameters.at(1), "p=")) {
+    throw std::runtime_error("Invalid hashed password format, p= parameter not found");
+  }
+
+  if (!boost::starts_with(parameters.at(2), "r=")) {
+    throw std::runtime_error("Invalid hashed password format, r= parameter not found");
+  }
+
+  auto saltPos = parametersEnd + 1;
+  auto saltEnd = hash.find('$', saltPos);
+  if (saltEnd == std::string::npos || saltEnd == hash.size()) {
+    throw std::runtime_error("Invalid hashed password format");
+  }
+
+  try {
+    workFactor = pdns_stou(parameters.at(0).substr(3));
+    workFactor = 1 << workFactor;
+    if (workFactor > pwhash_max_work_factor) {
+      throw std::runtime_error("Invalid work factor of " + std::to_string(workFactor) + " in hashed password string, maximum is " + std::to_string(pwhash_max_work_factor));
+    }
+
+    parallelFactor = pdns_stou(parameters.at(1).substr(2));
+    blockSize = pdns_stou(parameters.at(2).substr(2));
+
+    auto b64Salt = hash.substr(saltPos, saltEnd - saltPos);
+    salt.reserve(pwhash_salt_size);
+    B64Decode(b64Salt, salt);
+
+    if (salt.size() != pwhash_salt_size) {
+      throw std::runtime_error("Invalid salt in hashed password string");
+    }
+
+    hashedPassword.reserve(pwhash_output_size);
+    B64Decode(hash.substr(saltEnd + 1), hashedPassword);
+
+    if (hashedPassword.size() != pwhash_output_size) {
+      throw std::runtime_error("Invalid hash in hashed password string");
+    }
+  }
+  catch (const std::exception& e) {
+    throw std::runtime_error("Invalid hashed password format, unable to parse parameters");
+  }
 #endif
 }
 
 bool verifyPassword(const std::string& hash, const std::string& password)
 {
-#ifdef HAVE_CRYPTO_PWHASH_STR
-  if (hash.size() > crypto_pwhash_STRBYTES) {
-    throw std::runtime_error("Invalid password hash supplied for verification, size is " + std::to_string(hash.size()) + ", expected at most " + std::to_string(crypto_pwhash_STRBYTES));
+  if (!isPasswordHashed(hash)) {
+    return false;
   }
 
-  return crypto_pwhash_str_verify(hash.c_str(),
-                                  password.c_str(),
-                                  password.size())
-    == 0;
+#ifdef HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT
+  std::string salt;
+  std::string hashedPassword;
+  uint64_t workFactor = 0;
+  uint64_t parallelFactor = 0;
+  uint64_t blockSize = 0;
+  parseHashed(hash, salt, hashedPassword, workFactor, parallelFactor, blockSize);
+
+  auto expected = hashPasswordInternal(password, salt, workFactor, parallelFactor, blockSize);
+
+  return constantTimeStringEquals(expected, hashedPassword);
 #else
-  throw std::runtime_error("Verifying a hashed password requires libsodium support, and it is not available");
+  throw std::runtime_error("Verifying a hashed password requires scrypt support in OpenSSL, and it is not available");
 #endif
 }
 
 bool isPasswordHashed(const std::string& password)
 {
-#ifdef HAVE_CRYPTO_PWHASH_STR
-  if (password.size() > crypto_pwhash_STRBYTES) {
+#ifdef HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT
+  if (password.size() < pwhash_prefix_size || password.size() > pwhash_max_size) {
+    return false;
+  }
+
+  if (!boost::starts_with(password, pwhash_prefix)) {
     return false;
   }
 
-  int res = crypto_pwhash_str_needs_rehash(password.c_str(),
-                                           crypto_pwhash_OPSLIMIT_INTERACTIVE,
-                                           crypto_pwhash_MEMLIMIT_INTERACTIVE);
+  auto parametersEnd = password.find('$', pwhash_prefix.size());
+  if (parametersEnd == std::string::npos || parametersEnd == password.size()) {
+    return false;
+  }
 
-  if (res == -1) {
+  auto saltEnd = password.find('$', parametersEnd + 1);
+  if (saltEnd == std::string::npos || saltEnd == password.size()) {
     return false;
   }
-  /* 1 means a rehashing is needed (different parameters), 0 is fine.
-     Either way this is a valid hash */
+
+  /* the salt is base64 encoded so it has to be larger than that */
+  if ((saltEnd - parametersEnd - 1) < pwhash_salt_size) {
+    return false;
+  }
+
+  /* the hash base64 encoded so it has to be larger than that */
+  if ((password.size() - saltEnd - 1) < pwhash_output_size)  {
+    return false;
+  }
+
   return true;
 #else
   return false;
@@ -96,43 +313,36 @@ bool isPasswordHashed(const std::string& password)
 
 /* if the password is in cleartext and hashing is available,
    the hashed form will be kept in memory */
-CredentialsHolder::CredentialsHolder(std::string&& password, bool hashPlaintext)
+CredentialsHolder::CredentialsHolder(std::string&& password, bool hashPlaintext): d_credentials(std::move(password))
 {
-  bool locked = false;
-
   if (isHashingAvailable()) {
-    if (!isPasswordHashed(password)) {
+    if (!isPasswordHashed(d_credentials.getString())) {
       if (hashPlaintext) {
-        d_credentials = hashPassword(password);
-        locked = true;
+        d_salt = generateRandomSalt();
+        d_workFactor = s_defaultWorkFactor;
+        d_parallelFactor = s_defaultParallelFactor;
+        d_blockSize = s_defaultBlockSize;
+        d_credentials = SensitiveData(hashPasswordInternal(d_credentials.getString(), d_salt, d_workFactor, d_parallelFactor, d_blockSize));
         d_isHashed = true;
       }
     }
     else {
       d_wasHashed = true;
       d_isHashed = true;
-      d_credentials = std::move(password);
+      std::string hashedPassword;
+      parseHashed(d_credentials.getString(), d_salt, hashedPassword, d_workFactor, d_parallelFactor, d_blockSize);
+      d_credentials = SensitiveData(std::move(hashedPassword));
     }
   }
 
   if (!d_isHashed) {
     d_fallbackHashPerturb = random();
-    d_fallbackHash = burtle(reinterpret_cast<const unsigned char*>(password.data()), password.size(), d_fallbackHashPerturb);
-    d_credentials = std::move(password);
-  }
-
-  if (!locked) {
-#ifdef HAVE_LIBSODIUM
-    sodium_mlock(d_credentials.data(), d_credentials.size());
-#endif
+    d_fallbackHash = burtle(reinterpret_cast<const unsigned char*>(d_credentials.getString().data()), d_credentials.getString().size(), d_fallbackHashPerturb);
   }
 }
 
 CredentialsHolder::~CredentialsHolder()
 {
-#ifdef HAVE_LIBSODIUM
-  sodium_munlock(d_credentials.data(), d_credentials.size());
-#endif
   d_fallbackHashPerturb = 0;
   d_fallbackHash = 0;
 }
@@ -140,7 +350,7 @@ CredentialsHolder::~CredentialsHolder()
 bool CredentialsHolder::matches(const std::string& password) const
 {
   if (d_isHashed) {
-    return verifyPassword(d_credentials, password);
+    return verifyPassword(d_credentials.getString(), d_salt, d_workFactor, d_parallelFactor, d_blockSize, password);
   }
   else {
     uint32_t fallback = burtle(reinterpret_cast<const unsigned char*>(password.data()), password.size(), d_fallbackHashPerturb);
@@ -148,13 +358,13 @@ bool CredentialsHolder::matches(const std::string& password) const
       return false;
     }
 
-    return constantTimeStringEquals(password, d_credentials);
+    return constantTimeStringEquals(password, d_credentials.getString());
   }
 }
 
 bool CredentialsHolder::isHashingAvailable()
 {
-#ifdef HAVE_CRYPTO_PWHASH_STR
+#ifdef HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT
   return true;
 #else
   return false;
@@ -164,7 +374,7 @@ bool CredentialsHolder::isHashingAvailable()
 #include <signal.h>
 #include <termios.h>
 
-std::string CredentialsHolder::readFromTerminal()
+SensitiveData CredentialsHolder::readFromTerminal()
 {
   struct termios term;
   struct termios oterm;
@@ -240,9 +450,5 @@ std::string CredentialsHolder::readFromTerminal()
     sigaction(sig.first, &sig.second, nullptr);
   }
 
-#ifdef HAVE_LIBSODIUM
-  sodium_mlock(buffer.data(), buffer.size());
-#endif
-
-  return buffer;
+  return SensitiveData(std::move(buffer));
 }
index 2ed4ef938aa1980a63951108b6828fca68780068..dd0c801e6a51c92500a0d0823dd401019e7f0d36 100644 (file)
 #include <memory>
 #include <string>
 
+class SensitiveData
+{
+public:
+  SensitiveData(size_t bytes);
+  SensitiveData(std::string&& data);
+  ~SensitiveData();
+  void clear();
+  const std::string& getString() const
+  {
+    return d_data;
+  }
+  std::string& getString()
+  {
+    return d_data;
+  }
+
+private:
+  std::string d_data;
+};
+
 std::string hashPassword(const std::string& password);
 bool verifyPassword(const std::string& hash, const std::string& password);
+bool verifyPassword(const std::string& binaryHash, const std::string& salt, uint64_t workFactor, uint64_t parallelFactor, uint64_t blockSize, const std::string& binaryPassword);
 bool isPasswordHashed(const std::string& password);
 
 class CredentialsHolder
 {
 public:
-  /* if the password is in cleartext and hashing is available,
-     the hashed form will be kept in memory */
+  /* if hashPlaintext is true, the password is in cleartext and hashing is available,
+     the hashed form will be kept in memory.
+     Note that accepting hashed password from an untrusted source might open
+     us to a denial of service, since we currently don't cap the the parameters,
+     including the work factor */
   CredentialsHolder(std::string&& password, bool hashPlaintext);
   ~CredentialsHolder();
 
@@ -52,10 +76,21 @@ public:
   }
 
   static bool isHashingAvailable();
-  static std::string readFromTerminal();
+  static SensitiveData readFromTerminal();
+
+  static uint64_t const s_defaultWorkFactor;
+  static uint64_t const s_defaultParallelFactor;
+  static uint64_t const s_defaultBlockSize;
 
 private:
-  std::string d_credentials;
+  SensitiveData d_credentials;
+  /* if the password is hashed, we only extract
+     the salt and parameters once */
+  std::string d_salt;
+  uint64_t d_workFactor{0};
+  uint64_t d_parallelFactor{0};
+  uint64_t d_blockSize{0};
+  /* seed our hash so it's not predictable */
   uint32_t d_fallbackHashPerturb;
   uint32_t d_fallbackHash{0};
   /* whether it was constructed from a hashed and salted string */
index e55782e0ea1178a44bb81b8feae32b35a41b40f2..ac32efc0e000c03063ff0ae87a2f0ad4275eb1a6 100644 (file)
@@ -2485,7 +2485,7 @@ try
   }
   else if (cmds[0]=="hash-password") {
     auto password = CredentialsHolder::readFromTerminal();
-    cout<<hashPassword(password)<<endl;
+    cout<<hashPassword(password.getString())<<endl;
     return 0;
   }
 
index 54451ec42662238f4f2a60e2b36da22db4b19dea..229229d319aa7e1377cae83181322099a8533b0a 100644 (file)
@@ -143,7 +143,7 @@ int main(int argc, char** argv)
       }
       else if (commands.at(i) == "hash-password") {
         auto password = CredentialsHolder::readFromTerminal();
-        cout << hashPassword(password) << endl;
+        cout << hashPassword(password.getString()) << endl;
         return 0;
       }
       ++i;
index 3af8225c946bfbac2c78fe7137c4d012e5bffbde..b80d554f50eaf6c560bfe192eb63156b89c6a156 100644 (file)
@@ -446,6 +446,7 @@ endif
 
 rec_control_SOURCES = \
        arguments.cc arguments.hh \
+       base64.cc base64.hh \
        credentials.cc credentials.hh \
        dnslabeltext.cc \
        dnsname.hh dnsname.cc \
index 145ab311b567a17fe72b249dbdeb5604e4ab226d..48b27c8b847283f1e3257921c1209c956975e800 100644 (file)
@@ -9,12 +9,12 @@
 
 BOOST_AUTO_TEST_SUITE(credentials_cc)
 
-#ifdef HAVE_CRYPTO_PWHASH_STR
+#ifdef HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT
 BOOST_AUTO_TEST_CASE(test_CredentialsUtils)
 {
   const std::string plaintext("test");
   /* generated with hashPassword("test") */
-  const std::string sampleHash("$argon2id$v=19$m=65536,t=2,p=1$ndQKu3+ZsWedqRrlNFUaNw$tnb0MJVe5C2hlqkDt0Ln3R6VKCYkfMYdxDy+puXes3s");
+  const std::string sampleHash("$scrypt$ln=10,p=1,r=8$1GZ10YdmSGtTmKK9jTH85Q==$JHeICW1mUCnTC+nnULDr7QFQ3kRrZ7u12djruJdrPhI=");
 
   auto hashed = hashPassword(plaintext);
   BOOST_CHECK(!hashed.empty());
@@ -42,9 +42,9 @@ BOOST_AUTO_TEST_CASE(test_CredentialsHolder)
   BOOST_CHECK(!holder.wasHashed());
   BOOST_CHECK(!holder.isHashed());
 
-#ifdef HAVE_CRYPTO_PWHASH_STR
+#ifdef HAVE_EVP_PKEY_CTX_SET1_SCRYPT_SALT
   BOOST_CHECK(CredentialsHolder::isHashingAvailable());
-  const std::string sampleHash("$argon2id$v=19$m=65536,t=2,p=1$ndQKu3+ZsWedqRrlNFUaNw$tnb0MJVe5C2hlqkDt0Ln3R6VKCYkfMYdxDy+puXes3s");
+  const std::string sampleHash("$scrypt$ln=10,p=1,r=8$1GZ10YdmSGtTmKK9jTH85Q==$JHeICW1mUCnTC+nnULDr7QFQ3kRrZ7u12djruJdrPhI=");
 
   auto fromHashedHolder = CredentialsHolder(std::string(sampleHash), true);
   BOOST_CHECK(fromHashedHolder.wasHashed());