This is an automated email from the ASF dual-hosted git repository.

xiaoxiang781216 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/nuttx-apps.git

commit 608f13fd4be86e8538e26ac855dedc5d4545cbd1
Author: Abhishek Mishra <[email protected]>
AuthorDate: Thu Jun 25 16:55:03 2026 +0000

    !fsutils/passwd: Replace TEA with PBKDF2-HMAC-SHA256
    
    Migrate passwd encrypt/verify to PBKDF2 modular crypt format using
    kernel cryptodev (CRYPTO_PBKDF2_HMAC_SHA256 via /dev/crypto).  Add
    passwd_pbkdf2 wrapper, base64url helpers, complexity validation, and
    pbkdf2_test for RFC 6070 vector coverage.  FSUTILS_PASSWD selects
    CRYPTO, ALLOW_BSD_COMPONENTS, and CRYPTO_CRYPTODEV so existing sim
    defconfigs keep building.  Change NSH_LOGIN_USERNAME default to root and
    remove fixed-login password defaults.
    
    BREAKING CHANGE: TEA-encoded /etc/passwd entries no longer verify.
    Regenerate each entry after upgrading.  Pair with the nuttx host mkpasswd
    changes in apache/nuttx#19209.  Boards must enable the appropriate
    software or hardware crypto backend for PBKDF2 at runtime.  When
    CONFIG_NSH_LOGIN_FIXED=y, set CONFIG_NSH_LOGIN_PASSWORD in the board
    defconfig or menuconfig; there is no default password.
    
    Signed-off-by: Abhishek Mishra <[email protected]>
---
 fsutils/passwd/CMakeLists.txt                      |   3 +-
 fsutils/passwd/Kconfig                             |  38 +--
 fsutils/passwd/Makefile                            |   1 +
 fsutils/passwd/passwd.h                            |  26 +-
 fsutils/passwd/passwd_encrypt.c                    | 277 ++++++++++----------
 fsutils/passwd/passwd_pbkdf2.c                     | 114 ++++++++
 fsutils/passwd/passwd_pbkdf2.h                     |  44 ++++
 fsutils/passwd/passwd_verify.c                     | 126 +++++++--
 include/fsutils/passwd.h                           |  11 +-
 include/netutils/base64.h                          |  26 +-
 netutils/codecs/Kconfig                            |   3 +-
 netutils/codecs/base64.c                           | 147 +++++++++++
 netutils/dropbear/Kconfig                          |   4 +-
 nshlib/Kconfig                                     |  13 +-
 nshlib/nsh.h                                       |  11 +-
 {fsutils/passwd => testing/crypto}/CMakeLists.txt  |  25 +-
 .../passwd/Makefile => testing/crypto/Make.defs    |  20 +-
 {fsutils => testing/crypto}/passwd/CMakeLists.txt  |  36 ++-
 testing/crypto/passwd/Kconfig                      |  34 +++
 .../Makefile => testing/crypto/passwd/Make.defs    |  20 +-
 {fsutils => testing/crypto}/passwd/Makefile        |  21 +-
 testing/crypto/passwd/pbkdf2_test.c                | 287 +++++++++++++++++++++
 22 files changed, 983 insertions(+), 304 deletions(-)

diff --git a/fsutils/passwd/CMakeLists.txt b/fsutils/passwd/CMakeLists.txt
index e2e0ebce4..16caf1258 100644
--- a/fsutils/passwd/CMakeLists.txt
+++ b/fsutils/passwd/CMakeLists.txt
@@ -23,7 +23,8 @@
 if(CONFIG_FSUTILS_PASSWD)
   set(CSRCS)
 
-  list(APPEND CSRCS passwd_verify.c passwd_find.c passwd_encrypt.c)
+  list(APPEND CSRCS passwd_verify.c passwd_find.c passwd_encrypt.c
+       passwd_pbkdf2.c)
 
   if(NOT CONFIG_FSUTILS_PASSWD_READONLY)
     list(
diff --git a/fsutils/passwd/Kconfig b/fsutils/passwd/Kconfig
index 42a8301e2..198bb2df0 100644
--- a/fsutils/passwd/Kconfig
+++ b/fsutils/passwd/Kconfig
@@ -6,8 +6,19 @@
 config FSUTILS_PASSWD
        bool "Password file support"
        default n
+       depends on CRYPTO_CRYPTODEV
+       depends on NETUTILS_CODECS
+       depends on CODECS_BASE64
        ---help---
-               Enables support for /etc/passwd file access routines
+               Enables support for /etc/passwd file access routines.
+
+               Requires CONFIG_CRYPTO=y, CRYPTO_CRYPTODEV (and
+               ALLOW_BSD_COMPONENTS), plus NETUTILS_CODECS/CODECS_BASE64 for
+               base64url hash encoding.
+
+               NOTE: Password hashes use PBKDF2-HMAC-SHA256 (modular crypt 
format).
+               Existing TEA-encrypted /etc/passwd entries are NOT compatible 
and
+               must be regenerated.
 
 if FSUTILS_PASSWD
 
@@ -23,23 +34,14 @@ config FSUTILS_PASSWD_IOBUFFER_SIZE
        int "Allocated I/O buffer size"
        default 512
 
-config FSUTILS_PASSWD_KEY1
-       hex "Encryption key value 1"
-       default 0
+config FSUTILS_PASSWD_PBKDF2_ITERATIONS
+       int "Default PBKDF2 iteration count for new passwords"
+       default 10000
+       range 1000 200000
        ---help---
-               Leave at 0 if random key generation is enabled under Board
-               Selection. Otherwise set all four keys to unique non-zero 
values.
-
-config FSUTILS_PASSWD_KEY2
-       hex "Encryption key value 2"
-       default 0
-
-config FSUTILS_PASSWD_KEY3
-       hex "Encryption key value 3"
-       default 0
-
-config FSUTILS_PASSWD_KEY4
-       hex "Encryption key value 4"
-       default 0
+               Number of PBKDF2-HMAC-SHA256 iterations applied when setting a 
new
+               password.  Higher values slow brute-force attacks but also 
increase
+               login latency on low-MHz MCUs.  The iteration count is stored 
in each
+               hash string, so changing this option only affects newly-set 
passwords.
 
 endif # FSUTILS_PASSWD
diff --git a/fsutils/passwd/Makefile b/fsutils/passwd/Makefile
index b11148ffd..d15b1c6db 100644
--- a/fsutils/passwd/Makefile
+++ b/fsutils/passwd/Makefile
@@ -26,6 +26,7 @@ include $(APPDIR)/Make.defs
 
 ifeq ($(CONFIG_FSUTILS_PASSWD),y)
 CSRCS += passwd_verify.c passwd_find.c passwd_encrypt.c
+CSRCS += passwd_pbkdf2.c
 ifneq ($(CONFIG_FSUTILS_PASSWD_READONLY),y)
 CSRCS += passwd_adduser.c passwd_deluser.c passwd_update.c passwd_append.c
 CSRCS += passwd_delete.c passwd_lock.c
diff --git a/fsutils/passwd/passwd.h b/fsutils/passwd/passwd.h
index 6dd1785e3..e617cef37 100644
--- a/fsutils/passwd/passwd.h
+++ b/fsutils/passwd/passwd.h
@@ -36,17 +36,18 @@
  * Pre-processor Definitions
  ****************************************************************************/
 
-#define MAX_ENCRYPTED 48         /* Maximum size of a password (encrypted, 
ASCII) */
-#define MAX_USERNAME  48         /* Maximum size of a username */
-#define MAX_RECORD    (MAX_USERNAME + MAX_ENCRYPTED + 1)
+/* MCF format: $pbkdf2-sha256$<iter>$<salt>$<hash> */
 
-/* The TEA incryption algorithm generates 8 bytes of encrypted data per
- * 8 bytes of unencrypted data.  The encrypted presentation is base64 which
- * is 8-bits of ASCII for each 6 bits of data.  That is a 3-to-4 expansion
- * ratio.  MAX_ENCRYPTED must be a multiple of 8 bytes.
- */
+#define PASSWD_MCF_PREFIX           "$pbkdf2-sha256$"
+#define PASSWD_SALT_BYTES           16
+#define PASSWD_HASH_BYTES           32
 
-#define MAX_PASSWORD  (3 * MAX_ENCRYPTED / 4)
+/* 15 + 6 + 1 + 22 + 1 + 43 = 88 bytes for default parameters */
+
+#define MAX_ENCRYPTED               96
+#define MAX_USERNAME                48
+#define MAX_RECORD                  (MAX_USERNAME + MAX_ENCRYPTED + 1)
+#define MAX_PASSWORD                256
 
 /****************************************************************************
  * Public Types
@@ -55,7 +56,7 @@
 struct passwd_s
 {
   off_t offset;                      /* File offset (start of record) */
-  char encrypted[MAX_ENCRYPTED + 1]; /* Encrtyped password in file */
+  char encrypted[MAX_ENCRYPTED + 1]; /* Password hash in file */
 };
 
 /****************************************************************************
@@ -94,10 +95,11 @@ void passwd_unlock(FAR sem_t *sem);
  * Name: passwd_encrypt
  *
  * Description:
- *   Encrypt a password.  Currently uses the Tiny Encryption Algorithm.
+ *   Hash a password with PBKDF2-HMAC-SHA256 and encode the result in modular
+ *   crypt format for storage in /etc/passwd.
  *
  * Input Parameters:
- *   password -- The password string to be encrypted
+ *   password -- The password string to be hashed
  *
  * Returned Value:
  *   Zero (OK) is returned on success; a negated errno value is returned on
diff --git a/fsutils/passwd/passwd_encrypt.c b/fsutils/passwd/passwd_encrypt.c
index 50e516f77..c1e8b941e 100644
--- a/fsutils/passwd/passwd_encrypt.c
+++ b/fsutils/passwd/passwd_encrypt.c
@@ -25,85 +25,145 @@
  ****************************************************************************/
 
 #include <nuttx/config.h>
+#include <nuttx/debug.h>
 
-#include <stdint.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
 #include <stdio.h>
 #include <string.h>
-#include <errno.h>
+#include <sys/random.h>
+#include <unistd.h>
 
-#include <nuttx/crypto/tea.h>
+#include <netutils/base64.h>
 
 #include "passwd.h"
+#include "passwd_pbkdf2.h"
 
 /****************************************************************************
- * Private Data
+ * Pre-processor Definitions
  ****************************************************************************/
 
-/* This should be better protected */
+#ifndef CONFIG_FSUTILS_PASSWD_PBKDF2_ITERATIONS
+#  define CONFIG_FSUTILS_PASSWD_PBKDF2_ITERATIONS 10000
+#endif
 
-static uint32_t g_tea_key[4] =
-{
-  CONFIG_FSUTILS_PASSWD_KEY1,
-  CONFIG_FSUTILS_PASSWD_KEY2,
-  CONFIG_FSUTILS_PASSWD_KEY3,
-  CONFIG_FSUTILS_PASSWD_KEY4
-};
+#define PASSWD_MIN_LENGTH  8
 
-/****************************************************************************
- * Private Functions
- ****************************************************************************/
+static const char g_password_specials[] =
+  "!@#$%^&*()_+-=[]{}|;:,.<>?";
 
 /****************************************************************************
- * Name: passwd_base64
- *
- * Description:
- *   Encode a 5 bit value as a base64 character.
- *
- * Input Parameters:
- *   binary - 5 bit value
- *
- * Returned Value:
- *   The ASCII base64 character.  Must not return the field delimiter ':'
- *
+ * Private Functions
  ****************************************************************************/
 
-static char passwd_base64(uint8_t binary)
+static int validate_password_complexity(FAR const char *password)
 {
-  /* 0-26 -> 'A'-'Z' */
+  FAR const char *p;
+  size_t passlen;
+  int has_upper   = 0;
+  int has_lower   = 0;
+  int has_digit   = 0;
+  int has_special = 0;
+
+  passlen = strlen(password);
+  if (passlen < PASSWD_MIN_LENGTH)
+    {
+      _err("ERROR: password must be at least %d characters\n",
+           PASSWD_MIN_LENGTH);
+      return -EINVAL;
+    }
 
-  binary &= 63;
-  if (binary < 26)
+  if (passlen > MAX_PASSWORD)
     {
-      return 'A' + binary;
+      _err("ERROR: password must be at most %d characters\n", MAX_PASSWORD);
+      return -EINVAL;
     }
 
-  /* 26-51 -> 'a'-'z' */
+  for (p = password; *p != '\0'; p++)
+    {
+      if (isupper((unsigned char)*p))
+        {
+          has_upper = 1;
+        }
+      else if (islower((unsigned char)*p))
+        {
+          has_lower = 1;
+        }
+      else if (isdigit((unsigned char)*p))
+        {
+          has_digit = 1;
+        }
+      else if (strchr(g_password_specials, *p) != NULL)
+        {
+          has_special = 1;
+        }
+    }
 
-  binary -= 26;
-  if (binary < 26)
+  if (!has_upper)
     {
-      return 'a' + binary;
+      _err("ERROR: password must contain at least one uppercase "
+           "letter (A-Z)\n");
+      return -EINVAL;
     }
 
-  /* 52->61 -> '0'-'9' */
+  if (!has_lower)
+    {
+      _err("ERROR: password must contain at least one lowercase "
+           "letter (a-z)\n");
+      return -EINVAL;
+    }
+
+  if (!has_digit)
+    {
+      _err("ERROR: password must contain at least one digit (0-9)\n");
+      return -EINVAL;
+    }
 
-  binary -= 26;
-  if (binary < 10)
+  if (!has_special)
     {
-      return '0' + binary;
+      _err("ERROR: password must contain at least one special "
+           "character (!@#$%%^&*()_+-=[]{}|;:,.<>?)\n");
+      return -EINVAL;
     }
 
-  /* 62 -> '+' */
+  return OK;
+}
+
+/****************************************************************************
+ * Name: passwd_fill_random
+ *
+ * Description:
+ *   Fill a buffer with random bytes using getrandom() or /dev/urandom.
+ *
+ ****************************************************************************/
+
+static int passwd_fill_random(FAR uint8_t *buf, size_t len)
+{
+  ssize_t nread;
+  int fd;
+
+  nread = getrandom(buf, len, 0);
+  if (nread == (ssize_t)len)
+    {
+      return OK;
+    }
 
-  binary -= 10;
-  if (binary == 0)
+  fd = open("/dev/urandom", O_RDONLY);
+  if (fd < 0)
     {
-      return '+';
+      return -errno;
     }
 
-  /* 63 -> '/' */
+  nread = read(fd, buf, len);
+  close(fd);
 
-  return '/';
+  if (nread != (ssize_t)len)
+    {
+      return nread < 0 ? -errno : -EIO;
+    }
+
+  return OK;
 }
 
 /****************************************************************************
@@ -114,108 +174,65 @@ static char passwd_base64(uint8_t binary)
  * Name: passwd_encrypt
  *
  * Description:
- *   Encrypt a password.  Currently uses the Tiny Encryption Algorithm.
- *
- * Input Parameters:
- *   password -- The password string to be encrypted
- *
- * Returned Value:
- *   Zero (OK) is returned on success; a negated errno value is returned on
- *   failure.
+ *   Hash a password with PBKDF2-HMAC-SHA256 and encode as modular crypt
+ *   format: $pbkdf2-sha256$<iter>$<base64url-salt>$<base64url-hash>
  *
  ****************************************************************************/
 
 int passwd_encrypt(FAR const char *password,
                    char encrypted[MAX_ENCRYPTED + 1])
 {
-  union
-  {
-    char     b[8];
-    uint16_t h[4];
-    uint32_t l[2];
-  } value;
-
-  FAR const char *src;
-  FAR char *bptr;
-  FAR char *dest;
-  uint32_t tmp;
-  uint8_t remainder;
-  int remaining;
-  int gulpsize;
-  int nbits;
-  int i;
-
-  /* How long is the password? */
-
-  remaining = strlen(password);
-  if (remaining > MAX_PASSWORD)
+  uint8_t salt[PASSWD_SALT_BYTES];
+  uint8_t hash[PASSWD_HASH_BYTES];
+  char salt_b64[32];
+  char hash_b64[48];
+  size_t passlen;
+  int ret;
+
+  ret = validate_password_complexity(password);
+  if (ret < 0)
     {
-      return -E2BIG;
+      return ret;
     }
 
-  /* Convert the password in 8-byte TEA cycles */
-
-  src        = password;
-  dest       = encrypted;
-  *dest      = '\0';
+  passlen = strlen(password);
 
-  remainder  = 0;
-  nbits      = 0;
-
-  for (; remaining > 0; remaining -= gulpsize)
+  ret = passwd_fill_random(salt, sizeof(salt));
+  if (ret < 0)
     {
-      /* Copy bytes */
-
-      gulpsize = sizeof(value.b);
-      if (gulpsize > remaining)
-        {
-          gulpsize = remaining;
-        }
-
-      bptr = value.b;
-      for (i = 0; i < gulpsize; i++)
-        {
-          *bptr++ = *src++;
-        }
-
-      /* Pad with spaces if necessary */
-
-      for (; i < sizeof(value.b); i++)
-        {
-          *bptr++ = ' ';
-        }
-
-      /* Perform the conversion for this cycle */
-
-      tea_encrypt(value.l, g_tea_key);
-
-      /* Generate the base64 output string from this cycle */
-
-      tmp = remainder;
+      return ret;
+    }
 
-      for (i = 0; i < 4; i++)
-        {
-          tmp    = (uint32_t)value.h[i] << nbits | tmp;
-          nbits += 16;
-
-          while (nbits >= 6)
-            {
-              *dest++ = passwd_base64((uint8_t)(tmp & 0x3f));
-              tmp   >>= 6;
-              nbits  -= 6;
-            }
-        }
+  ret = passwd_pbkdf2_hmac_sha256((FAR const uint8_t *)password, passlen,
+                                  salt, sizeof(salt),
+                                  CONFIG_FSUTILS_PASSWD_PBKDF2_ITERATIONS,
+                                  hash, sizeof(hash));
+  if (ret < 0)
+    {
+      return ret;
+    }
 
-      remainder = (uint8_t)tmp;
-      *dest     = '\0';
+  ret = base64url_encode(salt, sizeof(salt), salt_b64,
+                                sizeof(salt_b64));
+  if (ret < 0)
+    {
+      return ret;
     }
 
-  /* Handle any remainder */
+  ret = base64url_encode(hash, sizeof(hash), hash_b64,
+                                sizeof(hash_b64));
+  if (ret < 0)
+    {
+      return ret;
+    }
 
-  if (nbits > 0)
+  ret = snprintf(encrypted, MAX_ENCRYPTED + 1,
+                 PASSWD_MCF_PREFIX "%u$%s$%s",
+                 CONFIG_FSUTILS_PASSWD_PBKDF2_ITERATIONS,
+                 salt_b64, hash_b64);
+  if (ret < 0 || (size_t)ret > MAX_ENCRYPTED)
     {
-      *dest++ = passwd_base64(remainder);
-      *dest   = '\0';
+      return -E2BIG;
     }
 
   return OK;
diff --git a/fsutils/passwd/passwd_pbkdf2.c b/fsutils/passwd/passwd_pbkdf2.c
new file mode 100644
index 000000000..e070387ab
--- /dev/null
+++ b/fsutils/passwd/passwd_pbkdf2.c
@@ -0,0 +1,114 @@
+/****************************************************************************
+ * apps/fsutils/passwd/passwd_pbkdf2.c
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.  The
+ * ASF licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ ****************************************************************************/
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+#include <crypto/cryptodev.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdint.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+
+#include "passwd_pbkdf2.h"
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+int passwd_pbkdf2_hmac_sha256(FAR const uint8_t *pass, size_t passlen,
+                              FAR const uint8_t *salt, size_t saltlen,
+                              uint32_t iterations,
+                              FAR uint8_t *out, size_t outlen)
+{
+  struct session_op session;
+  struct crypt_op cryp;
+  int cryptodev_fd = -1;
+  int fd = -1;
+  int ret = 0;
+
+  if (pass == NULL || salt == NULL || out == NULL || passlen == 0 ||
+      saltlen == 0 || iterations == 0 || outlen == 0)
+    {
+      return -EINVAL;
+    }
+
+  fd = open("/dev/crypto", O_RDWR, 0);
+  if (fd < 0)
+    {
+      return -errno;
+    }
+
+  if (ioctl(fd, CRIOGET, &cryptodev_fd) < 0)
+    {
+      ret = -errno;
+      goto errout;
+    }
+
+  memset(&session, 0, sizeof(session));
+  session.cipher = 0;
+  session.mac = CRYPTO_PBKDF2_HMAC_SHA256;
+  session.mackey = (caddr_t)pass;
+  session.mackeylen = passlen;
+
+  if (ioctl(cryptodev_fd, CIOCGSESSION, &session) < 0)
+    {
+      ret = -errno;
+      goto errout;
+    }
+
+  memset(&cryp, 0, sizeof(cryp));
+  cryp.ses = session.ses;
+  cryp.op = COP_ENCRYPT;
+  cryp.src = (caddr_t)salt;
+  cryp.len = saltlen;
+  cryp.mac = (caddr_t)out;
+  cryp.iterations = iterations;
+  cryp.olen = outlen;
+
+  if (ioctl(cryptodev_fd, CIOCCRYPT, &cryp) < 0)
+    {
+      ret = -errno;
+      goto errout_with_session;
+    }
+
+errout_with_session:
+  ioctl(cryptodev_fd, CIOCFSESSION, &session.ses);
+
+errout:
+  if (cryptodev_fd >= 0)
+    {
+      close(cryptodev_fd);
+    }
+
+  if (fd >= 0)
+    {
+      close(fd);
+    }
+
+  return ret;
+}
diff --git a/fsutils/passwd/passwd_pbkdf2.h b/fsutils/passwd/passwd_pbkdf2.h
new file mode 100644
index 000000000..1a66109fa
--- /dev/null
+++ b/fsutils/passwd/passwd_pbkdf2.h
@@ -0,0 +1,44 @@
+/****************************************************************************
+ * apps/fsutils/passwd/passwd_pbkdf2.h
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.  The
+ * ASF licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ ****************************************************************************/
+
+#ifndef __APPS_FSUTILS_PASSWD_PASSWD_PBKDF2_H
+#define __APPS_FSUTILS_PASSWD_PASSWD_PBKDF2_H
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/compiler.h>
+
+#include <sys/types.h>
+#include <stdint.h>
+
+/****************************************************************************
+ * Public Function Prototypes
+ ****************************************************************************/
+
+int passwd_pbkdf2_hmac_sha256(FAR const uint8_t *pass, size_t passlen,
+                              FAR const uint8_t *salt, size_t saltlen,
+                              uint32_t iterations,
+                              FAR uint8_t *out, size_t outlen);
+
+#endif /* __APPS_FSUTILS_PASSWD_PASSWD_PBKDF2_H */
diff --git a/fsutils/passwd/passwd_verify.c b/fsutils/passwd/passwd_verify.c
index 2c63f3759..fef8da6df 100644
--- a/fsutils/passwd/passwd_verify.c
+++ b/fsutils/passwd/passwd_verify.c
@@ -24,11 +24,105 @@
  * Included Files
  ****************************************************************************/
 
+#include <nuttx/config.h>
+
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
 #include <string.h>
-#include <semaphore.h>
 
-#include "fsutils/passwd.h"
+#include <netutils/base64.h>
+#include <fsutils/passwd.h>
+
 #include "passwd.h"
+#include "passwd_pbkdf2.h"
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: passwd_verify_hash
+ *
+ * Description:
+ *   Verify a password against a stored PBKDF2-SHA256 modular crypt hash.
+ *   Rejects any hash not in $pbkdf2-sha256$ format.
+ *
+ * Returned Value:
+ *   0 on match, -1 on mismatch or parse error.
+ *
+ ****************************************************************************/
+
+static int passwd_verify_hash(FAR const char *stored,
+                              FAR const char *password)
+{
+  FAR const char *p;
+  FAR const char *salt_b64;
+  FAR const char *hash_b64;
+  char *endptr;
+  uint8_t salt[PASSWD_SALT_BYTES];
+  uint8_t expected[PASSWD_HASH_BYTES];
+  uint8_t actual[PASSWD_HASH_BYTES];
+  size_t saltlen;
+  size_t hashlen;
+  size_t passlen;
+  unsigned long iterations;
+  int ret;
+
+  if (strncmp(stored, PASSWD_MCF_PREFIX, strlen(PASSWD_MCF_PREFIX)) != 0)
+    {
+      return -1;
+    }
+
+  p = stored + strlen(PASSWD_MCF_PREFIX);
+  iterations = strtoul(p, &endptr, 10);
+  if (endptr == p || *endptr != '$' || iterations < 1 ||
+      iterations > 200000)
+    {
+      return -1;
+    }
+
+  salt_b64 = endptr + 1;
+  hash_b64 = strchr(salt_b64, '$');
+  if (hash_b64 == NULL)
+    {
+      return -1;
+    }
+
+  ret = base64url_decode(salt_b64, salt, sizeof(salt), &saltlen);
+  if (ret < 0 || saltlen == 0)
+    {
+      return -1;
+    }
+
+  ret = base64url_decode(hash_b64 + 1, expected, sizeof(expected),
+                                &hashlen);
+  if (ret < 0 || hashlen != PASSWD_HASH_BYTES)
+    {
+      return -1;
+    }
+
+  passlen = strlen(password);
+  if (passlen == 0 || passlen > MAX_PASSWORD)
+    {
+      return -1;
+    }
+
+  ret = passwd_pbkdf2_hmac_sha256((FAR const uint8_t *)password, passlen,
+                                  salt, saltlen, (uint32_t)iterations,
+                                  actual, sizeof(actual));
+  if (ret < 0)
+    {
+      return -1;
+    }
+
+  if (timingsafe_bcmp(actual, expected, sizeof(expected)) != 0)
+    {
+      return -1;
+    }
+
+  return 0;
+}
 
 /****************************************************************************
  * Public Functions
@@ -39,53 +133,33 @@
  *
  * Description:
  *   Return true if the username exists in the /etc/passwd file and if the
- *   password matches the user password in that failed.
- *
- * Input Parameters:
+ *   password matches the user password in that file.
  *
  * Returned Value:
- *   One (1) is returned on success match, Zero (OK) is returned on an
- *   unsuccessful match; a negated errno value is returned on any other
- *   failure.
+ *   Zero (0) is returned on a successful match, -1 on mismatch or invalid
+ *   hash format; a negated errno value is returned on other failures.
  *
  ****************************************************************************/
 
 int passwd_verify(FAR const char *username, FAR const char *password)
 {
   struct passwd_s passwd;
-  char encrypted[MAX_ENCRYPTED + 1];
   PASSWD_SEM_DECL(sem);
   int ret;
 
-  /* Get exclusive access to the /etc/passwd file */
-
   ret = passwd_lock(&sem);
   if (ret < 0)
     {
       return ret;
     }
 
-  /* Verify that the username exists in the /etc/passwd file */
-
   ret = passwd_find(username, &passwd);
   if (ret < 0)
     {
-      /* The username does not exist in the /etc/passwd file */
-
       goto errout_with_lock;
     }
 
-  /* Encrypt the provided password */
-
-  ret = passwd_encrypt(password, encrypted);
-  if (ret < 0)
-    {
-      goto errout_with_lock;
-    }
-
-  /* Compare the encrypted passwords */
-
-  ret = (strcmp(passwd.encrypted, encrypted) == 0) ? 1 : 0;
+  ret = passwd_verify_hash(passwd.encrypted, password);
 
 errout_with_lock:
   passwd_unlock(sem);
diff --git a/include/fsutils/passwd.h b/include/fsutils/passwd.h
index 996bcf509..93fed255d 100644
--- a/include/fsutils/passwd.h
+++ b/include/fsutils/passwd.h
@@ -36,9 +36,9 @@
 
 /* passwd_verify() return value tests */
 
-#define PASSWORD_VERIFY_MATCH(ret)   (ret == 1)
-#define PASSWORD_VERIFY_NOMATCH(ret) (ret == 0)
-#define PASSWORD_VERIFY_ERROR(ret)   (ret < 0)
+#define PASSWORD_VERIFY_MATCH(ret)   ((ret) == 0)
+#define PASSWORD_VERIFY_NOMATCH(ret) ((ret) == -1)
+#define PASSWORD_VERIFY_ERROR(ret)   ((ret) < -1)
 
 /****************************************************************************
  * Public Function Prototypes
@@ -115,9 +115,8 @@ int passwd_update(FAR const char *username, FAR const char 
*password);
  *   password - The password to be verified
  *
  * Returned Value:
- *   One (1) is returned on success match, Zero (OK) is returned on an
- *   unsuccessful match; a negated errno value is returned on any other
- *   failure.
+ *   Zero (0) is returned on a successful match, -1 on mismatch or invalid
+ *   hash format; a negated errno value is returned on other failures.
  *
  ****************************************************************************/
 
diff --git a/include/netutils/base64.h b/include/netutils/base64.h
index e5869d8ac..5f146d7e8 100644
--- a/include/netutils/base64.h
+++ b/include/netutils/base64.h
@@ -30,17 +30,17 @@
  *      may be used to endorse or promote products derived from this software
  *      without specific prior written permission.
  *
- *   THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
- *   ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- *   IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- *   ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
- *   FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- *   DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
- *   OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- *   HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
- *   LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
- *   OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- *   SUCH DAMAGE.
+ *   THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS''
+ *   AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ *   THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ *   PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS
+ *   BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ *   CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ *   SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ *   INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ *   CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ *   ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ *   THE POSSIBILITY OF SUCH DAMAGE.
  *
  ****************************************************************************/
 
@@ -73,6 +73,10 @@ FAR void *base64w_encode(FAR const void *src, size_t len, 
FAR void *dst,
                          FAR size_t *out_len);
 FAR void *base64w_decode(FAR const void *src, size_t len, FAR void *dst,
                          FAR size_t *out_len);
+int base64url_encode(FAR const void *src, size_t len, FAR char *dst,
+                     size_t dstlen);
+int base64url_decode(FAR const char *src, FAR void *dst, size_t dstmax,
+                     FAR size_t *out_len);
 #endif /* CONFIG_CODECS_BASE64 */
 
 #ifdef __cplusplus
diff --git a/netutils/codecs/Kconfig b/netutils/codecs/Kconfig
index 4b22e1d3f..08f5ed816 100644
--- a/netutils/codecs/Kconfig
+++ b/netutils/codecs/Kconfig
@@ -16,7 +16,8 @@ config CODECS_BASE64
        default n
        ---help---
                Enables support for the following interfaces: base64_encode(),
-               base64_decode(), base64w_encode(), and base64w_decode(),
+               base64_decode(), base64w_encode(), base64w_decode(),
+               base64url_encode(), and base64url_decode(),
 
                Contributed NuttX by Darcy Gong.
 
diff --git a/netutils/codecs/base64.c b/netutils/codecs/base64.c
index 36485b85a..731283857 100644
--- a/netutils/codecs/base64.c
+++ b/netutils/codecs/base64.c
@@ -55,6 +55,7 @@
 
 #include <stdlib.h>
 #include <stdbool.h>
+#include <errno.h>
 #include <string.h>
 
 #include "netutils/base64.h"
@@ -317,4 +318,150 @@ FAR void *base64w_decode(FAR const void *src, size_t len, 
FAR void *dst,
   return _base64_decode(src, len, dst, out_len, true);
 }
 
+/****************************************************************************
+ * Name: base64url_val
+ ****************************************************************************/
+
+static int base64url_val(char c)
+{
+  if (c >= 'A' && c <= 'Z')
+    {
+      return c - 'A';
+    }
+
+  if (c >= 'a' && c <= 'z')
+    {
+      return c - 'a' + 26;
+    }
+
+  if (c >= '0' && c <= '9')
+    {
+      return c - '0' + 52;
+    }
+
+  if (c == '-')
+    {
+      return 62;
+    }
+
+  if (c == '_')
+    {
+      return 63;
+    }
+
+  return -1;
+}
+
+/****************************************************************************
+ * Name: base64url_encode
+ *
+ * Description:
+ *   Encode binary data as unpadded base64url (RFC 4648 section 5).
+ *
+ ****************************************************************************/
+
+int base64url_encode(FAR const void *src, size_t len, FAR char *dst,
+                     size_t dstlen)
+{
+  FAR const uint8_t *in = src;
+  uint32_t acc = 0;
+  size_t i;
+  size_t o = 0;
+  int bits = 0;
+
+  static const char g_base64url[] =
+    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
+
+  for (i = 0; i < len; i++)
+    {
+      acc = (acc << 8) | in[i];
+      bits += 8;
+
+      while (bits >= 6)
+        {
+          if (o + 1 >= dstlen)
+            {
+              return -E2BIG;
+            }
+
+          bits -= 6;
+          dst[o++] = g_base64url[(acc >> bits) & 0x3f];
+        }
+    }
+
+  if (bits > 0)
+    {
+      if (o + 1 >= dstlen)
+        {
+          return -E2BIG;
+        }
+
+      dst[o++] = g_base64url[(acc << (6 - bits)) & 0x3f];
+    }
+
+  if (o >= dstlen)
+    {
+      return -E2BIG;
+    }
+
+  dst[o] = '\0';
+  return 0;
+}
+
+/****************************************************************************
+ * Name: base64url_decode
+ *
+ * Description:
+ *   Decode unpadded base64url (RFC 4648 section 5).
+ *
+ ****************************************************************************/
+
+int base64url_decode(FAR const char *src, FAR void *dst, size_t dstmax,
+                     FAR size_t *out_len)
+{
+  FAR uint8_t *out = dst;
+  uint32_t acc = 0;
+  size_t o = 0;
+  int bits = 0;
+  int v;
+
+  *out_len = 0;
+
+  while (*src != '\0')
+    {
+      if (*src == '$' || *src == ':')
+        {
+          break;
+        }
+
+      v = base64url_val(*src++);
+      if (v < 0)
+        {
+          return -EINVAL;
+        }
+
+      acc = (acc << 6) | (uint32_t)v;
+      bits += 6;
+
+      if (bits >= 8)
+        {
+          bits -= 8;
+          if (o >= dstmax)
+            {
+              return -E2BIG;
+            }
+
+          out[o++] = (uint8_t)((acc >> bits) & 0xff);
+        }
+    }
+
+  if (bits >= 6)
+    {
+      return -EINVAL;
+    }
+
+  *out_len = o;
+  return 0;
+}
+
 #endif /* CONFIG_CODECS_BASE64 */
diff --git a/netutils/dropbear/Kconfig b/netutils/dropbear/Kconfig
index abf9f4574..43a2c4a51 100644
--- a/netutils/dropbear/Kconfig
+++ b/netutils/dropbear/Kconfig
@@ -19,8 +19,8 @@ menuconfig NETUTILS_DROPBEAR
        depends on DEV_URANDOM
        depends on LIBC_NETDB
        depends on LIBC_GAISTRERROR
-       select CRYPTO
-       select CRYPTO_RANDOM_POOL
+       depends on CRYPTO
+       depends on CRYPTO_RANDOM_POOL
        ---help---
                Enable a minimal Dropbear SSH server port for NuttX.  This 
initial
                port is based on the ESP-IDF MCU test port and provides a single
diff --git a/nshlib/Kconfig b/nshlib/Kconfig
index 1f78a50ae..7a39d96b4 100644
--- a/nshlib/Kconfig
+++ b/nshlib/Kconfig
@@ -1287,17 +1287,20 @@ endchoice # Verification method
 
 config NSH_LOGIN_USERNAME
        string "Login username"
-       default "admin"
+       default "root"
        depends on !NSH_LOGIN_PASSWD
        ---help---
-               Login user name.  Default: "admin"
+               Login user name.  Default: "root"
 
 config NSH_LOGIN_PASSWORD
        string "Login password"
-       default "Administrator"
-       depends on !NSH_LOGIN_PASSWD
+       depends on NSH_LOGIN_FIXED
        ---help---
-               Login password:  Default: "Administrator"
+               The plaintext login password used when fixed username/password
+               verification is selected (CONFIG_NSH_LOGIN_FIXED).  There is no
+               default; set CONFIG_NSH_LOGIN_PASSWORD in the board defconfig,
+               via menuconfig, or enter it when prompted during an interactive
+               build.
 
 config NSH_LOGIN_FAILDELAY
        int "Login failure delay"
diff --git a/nshlib/nsh.h b/nshlib/nsh.h
index 872c1e4af..5950c4fce 100644
--- a/nshlib/nsh.h
+++ b/nshlib/nsh.h
@@ -266,8 +266,9 @@
  * If CONFIG_NSH_TELNET_LOGIN is defined, then these additional
  * options may be specified:
  *
- * CONFIG_NSH_LOGIN_USERNAME - Login user name.  Default: "admin"
- * CONFIG_NSH_LOGIN_PASSWORD - Login password:  Default: "Administrator"
+ * CONFIG_NSH_LOGIN_USERNAME - Login user name.  Default: "root"
+ * CONFIG_NSH_LOGIN_PASSWORD - Login password (required when using fixed
+ *   credentials; no default is provided).
  * CONFIG_NSH_LOGIN_FAILCOUNT - Number of login retry attempts.
  *   Default 3.
  */
@@ -275,11 +276,7 @@
 #ifdef CONFIG_NSH_TELNET_LOGIN
 
 #  ifndef CONFIG_NSH_LOGIN_USERNAME
-#    define CONFIG_NSH_LOGIN_USERNAME  "admin"
-#  endif
-
-#  ifndef CONFIG_NSH_LOGIN_PASSWORD
-#    define CONFIG_NSH_LOGIN_PASSWORD  "nuttx"
+#    define CONFIG_NSH_LOGIN_USERNAME  "root"
 #  endif
 
 #  ifndef CONFIG_NSH_LOGIN_FAILCOUNT
diff --git a/fsutils/passwd/CMakeLists.txt b/testing/crypto/CMakeLists.txt
similarity index 67%
copy from fsutils/passwd/CMakeLists.txt
copy to testing/crypto/CMakeLists.txt
index e2e0ebce4..0b2030ebe 100644
--- a/fsutils/passwd/CMakeLists.txt
+++ b/testing/crypto/CMakeLists.txt
@@ -1,5 +1,5 @@
 # 
##############################################################################
-# apps/fsutils/passwd/CMakeLists.txt
+# apps/testing/crypto/CMakeLists.txt
 #
 # SPDX-License-Identifier: Apache-2.0
 #
@@ -20,24 +20,5 @@
 #
 # 
##############################################################################
 
-if(CONFIG_FSUTILS_PASSWD)
-  set(CSRCS)
-
-  list(APPEND CSRCS passwd_verify.c passwd_find.c passwd_encrypt.c)
-
-  if(NOT CONFIG_FSUTILS_PASSWD_READONLY)
-    list(
-      APPEND
-      CSRCS
-      passwd_adduser.c
-      passwd_deluser.c
-      passwd_update.c
-      passwd_append.c
-      passwd_delete.c
-      passwd_lock.c)
-  endif()
-
-  set_property(GLOBAL APPEND PROPERTY NUTTX_APPS_LIBRARIES ${TARGET})
-  target_sources(apps PRIVATE ${CSRCS})
-
-endif()
+nuttx_add_subdirectory()
+nuttx_generate_kconfig(MENUDESC "crypto")
diff --git a/fsutils/passwd/Makefile b/testing/crypto/Make.defs
similarity index 69%
copy from fsutils/passwd/Makefile
copy to testing/crypto/Make.defs
index b11148ffd..12e81164e 100644
--- a/fsutils/passwd/Makefile
+++ b/testing/crypto/Make.defs
@@ -1,5 +1,5 @@
 ############################################################################
-# apps/fsutils/passwd/Makefile
+# apps/testing/crypto/Make.defs
 #
 # SPDX-License-Identifier: Apache-2.0
 #
@@ -15,21 +15,9 @@
 # Unless required by applicable law or agreed to in writing, software
 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
-# License for the specific language governing permissions and limitations
-# under the License.
+# License for the specific language governing permissions and limitations under
+# the License.
 #
 ############################################################################
 
-include $(APPDIR)/Make.defs
-
-# Password file access library
-
-ifeq ($(CONFIG_FSUTILS_PASSWD),y)
-CSRCS += passwd_verify.c passwd_find.c passwd_encrypt.c
-ifneq ($(CONFIG_FSUTILS_PASSWD_READONLY),y)
-CSRCS += passwd_adduser.c passwd_deluser.c passwd_update.c passwd_append.c
-CSRCS += passwd_delete.c passwd_lock.c
-endif
-endif
-
-include $(APPDIR)/Application.mk
+include $(wildcard $(APPDIR)/testing/crypto/*/Make.defs)
diff --git a/fsutils/passwd/CMakeLists.txt 
b/testing/crypto/passwd/CMakeLists.txt
similarity index 67%
copy from fsutils/passwd/CMakeLists.txt
copy to testing/crypto/passwd/CMakeLists.txt
index e2e0ebce4..2a5c205c6 100644
--- a/fsutils/passwd/CMakeLists.txt
+++ b/testing/crypto/passwd/CMakeLists.txt
@@ -1,5 +1,5 @@
 # 
##############################################################################
-# apps/fsutils/passwd/CMakeLists.txt
+# apps/testing/crypto/passwd/CMakeLists.txt
 #
 # SPDX-License-Identifier: Apache-2.0
 #
@@ -20,24 +20,18 @@
 #
 # 
##############################################################################
 
-if(CONFIG_FSUTILS_PASSWD)
-  set(CSRCS)
-
-  list(APPEND CSRCS passwd_verify.c passwd_find.c passwd_encrypt.c)
-
-  if(NOT CONFIG_FSUTILS_PASSWD_READONLY)
-    list(
-      APPEND
-      CSRCS
-      passwd_adduser.c
-      passwd_deluser.c
-      passwd_update.c
-      passwd_append.c
-      passwd_delete.c
-      passwd_lock.c)
-  endif()
-
-  set_property(GLOBAL APPEND PROPERTY NUTTX_APPS_LIBRARIES ${TARGET})
-  target_sources(apps PRIVATE ${CSRCS})
-
+if(CONFIG_TESTING_PBKDF2)
+  nuttx_add_application(
+    NAME
+    pbkdf2_test
+    PRIORITY
+    ${CONFIG_TESTING_PBKDF2_PRIORITY}
+    STACKSIZE
+    ${CONFIG_TESTING_PBKDF2_STACKSIZE}
+    MODULE
+    ${CONFIG_TESTING_PBKDF2}
+    SRCS
+    pbkdf2_test.c
+    INCLUDE_DIRECTORIES
+    ${CMAKE_CURRENT_LIST_DIR}/../../../fsutils/passwd)
 endif()
diff --git a/testing/crypto/passwd/Kconfig b/testing/crypto/passwd/Kconfig
new file mode 100644
index 000000000..b15276e64
--- /dev/null
+++ b/testing/crypto/passwd/Kconfig
@@ -0,0 +1,34 @@
+#
+# For a description of the syntax of this configuration file,
+# see the file kconfig-language.txt in the NuttX tools repository.
+#
+
+config TESTING_PBKDF2
+       tristate "PBKDF2 and passwd hash test"
+       default n
+       depends on FSUTILS_PASSWD
+       ---help---
+               Enable the PBKDF2-HMAC-SHA256 unit test
+               (apps/testing/crypto/passwd).  Always runs RFC 6070 SHA-256
+               vectors.  The passwd_encrypt / passwd_verify round-trip runs 
only
+               when FSUTILS_PASSWD_READONLY is disabled and DEV_URANDOM is
+               enabled; otherwise it is skipped with an explanatory message.
+
+if TESTING_PBKDF2
+
+config TESTING_PBKDF2_PRIORITY
+       int "pbkdf2_test task priority"
+       default 100
+
+config TESTING_PBKDF2_STACKSIZE
+       int "pbkdf2_test stack size"
+       default DEFAULT_TASK_STACKSIZE
+
+config TESTING_PBKDF2_SLOW_VECTOR
+       bool "Run RFC 6070 vector with 16777216 iterations"
+       default n
+       ---help---
+               Include RFC 6070 test vector #4 (16,777,216 iterations).  This
+               takes a long time on embedded targets; enable only for manual 
runs.
+
+endif
diff --git a/fsutils/passwd/Makefile b/testing/crypto/passwd/Make.defs
similarity index 69%
copy from fsutils/passwd/Makefile
copy to testing/crypto/passwd/Make.defs
index b11148ffd..2e7cac968 100644
--- a/fsutils/passwd/Makefile
+++ b/testing/crypto/passwd/Make.defs
@@ -1,5 +1,5 @@
 ############################################################################
-# apps/fsutils/passwd/Makefile
+# apps/testing/crypto/passwd/Make.defs
 #
 # SPDX-License-Identifier: Apache-2.0
 #
@@ -15,21 +15,11 @@
 # Unless required by applicable law or agreed to in writing, software
 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
-# License for the specific language governing permissions and limitations
-# under the License.
+# License for the specific language governing permissions and limitations under
+# the License.
 #
 ############################################################################
 
-include $(APPDIR)/Make.defs
-
-# Password file access library
-
-ifeq ($(CONFIG_FSUTILS_PASSWD),y)
-CSRCS += passwd_verify.c passwd_find.c passwd_encrypt.c
-ifneq ($(CONFIG_FSUTILS_PASSWD_READONLY),y)
-CSRCS += passwd_adduser.c passwd_deluser.c passwd_update.c passwd_append.c
-CSRCS += passwd_delete.c passwd_lock.c
+ifneq ($(CONFIG_TESTING_PBKDF2),)
+CONFIGURED_APPS += $(APPDIR)/testing/crypto/passwd
 endif
-endif
-
-include $(APPDIR)/Application.mk
diff --git a/fsutils/passwd/Makefile b/testing/crypto/passwd/Makefile
similarity index 74%
copy from fsutils/passwd/Makefile
copy to testing/crypto/passwd/Makefile
index b11148ffd..2b726cbd6 100644
--- a/fsutils/passwd/Makefile
+++ b/testing/crypto/passwd/Makefile
@@ -1,5 +1,5 @@
 ############################################################################
-# apps/fsutils/passwd/Makefile
+# apps/testing/crypto/passwd/Makefile
 #
 # SPDX-License-Identifier: Apache-2.0
 #
@@ -15,21 +15,20 @@
 # Unless required by applicable law or agreed to in writing, software
 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
-# License for the specific language governing permissions and limitations
-# under the License.
+# License for the specific language governing permissions and limitations under
+# the License.
 #
 ############################################################################
 
 include $(APPDIR)/Make.defs
 
-# Password file access library
+PRIORITY  = $(CONFIG_TESTING_PBKDF2_PRIORITY)
+STACKSIZE = $(CONFIG_TESTING_PBKDF2_STACKSIZE)
+MODULE    = $(CONFIG_TESTING_PBKDF2)
 
-ifeq ($(CONFIG_FSUTILS_PASSWD),y)
-CSRCS += passwd_verify.c passwd_find.c passwd_encrypt.c
-ifneq ($(CONFIG_FSUTILS_PASSWD_READONLY),y)
-CSRCS += passwd_adduser.c passwd_deluser.c passwd_update.c passwd_append.c
-CSRCS += passwd_delete.c passwd_lock.c
-endif
-endif
+MAINSRC   = pbkdf2_test.c
+PROGNAME  = pbkdf2_test
+
+CFLAGS += ${INCDIR_PREFIX}$(APPDIR)/fsutils/passwd
 
 include $(APPDIR)/Application.mk
diff --git a/testing/crypto/passwd/pbkdf2_test.c 
b/testing/crypto/passwd/pbkdf2_test.c
new file mode 100644
index 000000000..d1f0e718d
--- /dev/null
+++ b/testing/crypto/passwd/pbkdf2_test.c
@@ -0,0 +1,287 @@
+/****************************************************************************
+ * apps/testing/crypto/passwd/pbkdf2_test.c
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.  The
+ * ASF licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ ****************************************************************************/
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <fsutils/passwd.h>
+
+#include "passwd.h"
+#include "passwd_pbkdf2.h"
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define TEST_USERNAME  "testuser"
+#define TEST_PASSWORD  "MySecret1!"
+#define WRONG_PASSWORD "WrongPass"
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+struct pbkdf2_vector_s
+{
+  FAR const char *password;
+  size_t          passwordlen;
+  FAR const char *salt;
+  size_t          saltlen;
+  uint32_t        iterations;
+  size_t          dklen;
+  FAR const uint8_t *expected;
+};
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+/* PBKDF2-HMAC-SHA256 test vectors from RFC 6070 (appendix B). */
+
+static const uint8_t g_vector1[] =
+{
+  0x12, 0x0f, 0xb6, 0xcf, 0xfc, 0xf8, 0xb3, 0x2c,
+  0x43, 0xe7, 0x22, 0x52, 0x56, 0xc4, 0xf8, 0x37,
+  0xa8, 0x65, 0x48, 0xc9
+};
+
+static const uint8_t g_vector2[] =
+{
+  0xae, 0x4d, 0x0c, 0x95, 0xaf, 0x6b, 0x46, 0xd3,
+  0x2d, 0x0a, 0xdf, 0xf9, 0x28, 0xf0, 0x6d, 0xd0,
+  0x2a, 0x30, 0x3f, 0x8e
+};
+
+static const uint8_t g_vector3[] =
+{
+  0xc5, 0xe4, 0x78, 0xd5, 0x92, 0x88, 0xc8, 0x41,
+  0xaa, 0x53, 0x0d, 0xb6, 0x84, 0x5c, 0x4c, 0x8d,
+  0x96, 0x28, 0x93, 0xa0
+};
+
+static const uint8_t g_vector4[] =
+{
+  0xcf, 0x81, 0xc6, 0x6f, 0xe8, 0xcf, 0xc0, 0x4d,
+  0x1f, 0x31, 0xec, 0xb6, 0x5d, 0xab, 0x40, 0x89,
+  0xf7, 0xf1, 0x79, 0xe8
+};
+
+static const uint8_t g_vector5[] =
+{
+  0x34, 0x8c, 0x89, 0xdb, 0xcb, 0xd3, 0x2b, 0x2f,
+  0x32, 0xd8, 0x14, 0xb8, 0x11, 0x6e, 0x84, 0xcf,
+  0x2b, 0x17, 0x34, 0x7e, 0xbc, 0x18, 0x00, 0x18,
+  0x1c
+};
+
+static const uint8_t g_vector6[] =
+{
+  0x89, 0xb6, 0x9d, 0x05, 0x16, 0xf8, 0x29, 0x89,
+  0x3c, 0x69, 0x62, 0x26, 0x65, 0x0a, 0x86, 0x87
+};
+
+static const struct pbkdf2_vector_s g_vectors[] =
+{
+  {
+    "password", 8,
+    "salt", 4,
+    1, 20,
+    g_vector1
+  },
+  {
+    "password", 8,
+    "salt", 4,
+    2, 20,
+    g_vector2
+  },
+  {
+    "password", 8,
+    "salt", 4,
+    4096, 20,
+    g_vector3
+  },
+  {
+    "password", 8,
+    "salt", 4,
+    16777216, 20,
+    g_vector4
+  },
+  {
+    "passwordPASSWORDpassword", 24,
+    "saltSALTsaltSALTsaltSALTsaltSALTsalt", 36,
+    4096, 25,
+    g_vector5
+  },
+  {
+    "pass\0word", 9,
+    "sa\0lt", 5,
+    4096, 16,
+    g_vector6
+  },
+};
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+static int test_pbkdf2_vectors(void)
+{
+  FAR const struct pbkdf2_vector_s *vec;
+  uint8_t output[32];
+  int failures = 0;
+  int i;
+  int ret;
+
+  for (i = 0; i < (int)(sizeof(g_vectors) / sizeof(g_vectors[0])); i++)
+    {
+      vec = &g_vectors[i];
+
+#ifndef CONFIG_TESTING_PBKDF2_SLOW_VECTOR
+      if (vec->iterations > 100000)
+        {
+          printf("pbkdf2_test: skipping slow vector %d (enable "
+                 "TESTING_PBKDF2_SLOW_VECTOR)\n", i);
+          continue;
+        }
+#endif
+
+      ret = passwd_pbkdf2_hmac_sha256((FAR const uint8_t *)vec->password,
+                                      vec->passwordlen,
+                                      (FAR const uint8_t *)vec->salt,
+                                      vec->saltlen,
+                                      vec->iterations,
+                                      output, vec->dklen);
+      if (ret != 0)
+        {
+          printf("pbkdf2_test: vector %d pbkdf2 failed: %d\n", i, ret);
+          failures++;
+          continue;
+        }
+
+      if (memcmp(output, vec->expected, vec->dklen) != 0)
+        {
+          printf("pbkdf2_test: vector %d output mismatch\n", i);
+          failures++;
+        }
+    }
+
+  if (failures == 0)
+    {
+      printf("pbkdf2_test: RFC 6070 SHA-256 vectors OK\n");
+    }
+
+  return failures;
+}
+
+static int test_passwd_roundtrip(void)
+{
+#if defined(CONFIG_FSUTILS_PASSWD_READONLY)
+  printf("pbkdf2_test: skipping passwd round-trip "
+         "(FSUTILS_PASSWD_READONLY)\n");
+  return 0;
+#elif !defined(CONFIG_DEV_URANDOM)
+  printf("pbkdf2_test: skipping passwd round-trip "
+         "(DEV_URANDOM)\n");
+  return 0;
+#else
+  FILE *stream;
+  char encrypted[MAX_ENCRYPTED + 1];
+  int ret;
+
+  unlink(CONFIG_FSUTILS_PASSWD_PATH);
+
+  ret = passwd_encrypt(TEST_PASSWORD, encrypted);
+  if (ret < 0)
+    {
+      printf("pbkdf2_test: passwd_encrypt failed: %d\n", ret);
+      return 1;
+    }
+
+  stream = fopen(CONFIG_FSUTILS_PASSWD_PATH, "w");
+  if (stream == NULL)
+    {
+      printf("pbkdf2_test: cannot write %s: %d\n",
+             CONFIG_FSUTILS_PASSWD_PATH, errno);
+      return 1;
+    }
+
+  if (fprintf(stream, "%s:%s:0:0:/\n", TEST_USERNAME, encrypted) < 0)
+    {
+      printf("pbkdf2_test: fprintf failed: %d\n", errno);
+      fclose(stream);
+      return 1;
+    }
+
+  fclose(stream);
+
+  ret = passwd_verify(TEST_USERNAME, TEST_PASSWORD);
+  if (ret != 0)
+    {
+      printf("pbkdf2_test: passwd_verify match failed: %d (expected 0)\n",
+             ret);
+      return 1;
+    }
+
+  ret = passwd_verify(TEST_USERNAME, WRONG_PASSWORD);
+  if (ret != -1)
+    {
+      printf("pbkdf2_test: passwd_verify mismatch failed: %d "
+             "(expected -1)\n", ret);
+      return 1;
+    }
+
+  printf("pbkdf2_test: passwd round-trip OK\n");
+  return 0;
+#endif
+}
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+int main(int argc, FAR char *argv[])
+{
+  int failures = 0;
+
+  (void)argc;
+  (void)argv;
+
+  failures += test_pbkdf2_vectors();
+  failures += test_passwd_roundtrip();
+
+  if (failures != 0)
+    {
+      printf("pbkdf2_test: FAILED (%d)\n", failures);
+      return 1;
+    }
+
+  printf("pbkdf2_test: PASSED\n");
+  return 0;
+}

Reply via email to