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.git

commit 4761f15b3ec81885281499976d81f3a50d72ebd2
Author: Abhishek Mishra <[email protected]>
AuthorDate: Wed Jul 15 15:44:37 2026 +0000

    !tools/mkpasswd: PBKDF2 host tool and ROMFS passwd build integration
    
    Add standalone host PBKDF2-HMAC-SHA256 mkpasswd, board_romfs_mkpasswd.sh,
    and promptpasswd.sh with confirm-password support. Integrate ROMFS passwd
    generation in Board.mk and CMake. Drop TEA key checks from passwd_keys.mk.
    
    Signed-off-by: Abhishek Mishra <[email protected]>
---
 boards/Board.mk                |  16 +-
 cmake/nuttx_add_romfs.cmake    |  91 +----
 cmake/savedefconfig.cmake      |   5 +-
 tools/Makefile.host            |   2 +-
 tools/Unix.mk                  |   4 +-
 tools/board_romfs_mkpasswd.sh  |  62 +++
 tools/configure.sh             |   2 +-
 tools/mkpasswd.c               | 831 ++++++++++++++++++++++++-----------------
 tools/passwd_keys.mk           |  71 ++--
 tools/promptpasswd.sh          | 180 +++++++++
 tools/update_romfs_password.sh |   2 +-
 11 files changed, 784 insertions(+), 482 deletions(-)

diff --git a/boards/Board.mk b/boards/Board.mk
index a882433aa82..aee2a13aef5 100644
--- a/boards/Board.mk
+++ b/boards/Board.mk
@@ -36,18 +36,16 @@ $(ETCSRC): $(foreach raw,$(RCRAWS), $(if $(wildcard 
$(BOARD_DIR)$(DELIM)src$(DEL
          $(shell mkdir -p $(dir $(ETCDIR)$(DELIM)$(raw))) \
          $(shell cp -rfp $(if $(wildcard 
$(BOARD_DIR)$(DELIM)src$(DELIM)$(raw)), $(BOARD_DIR)$(DELIM)src$(DELIM)$(raw), 
$(if $(wildcard $(BOARD_COMMON_DIR)$(DELIM)$(raw)), 
$(BOARD_COMMON_DIR)$(DELIM)$(raw), $(BOARD_DIR)$(DELIM)src$(DELIM)$(raw))) 
$(ETCDIR)$(DELIM)$(raw)))
 ifeq ($(CONFIG_BOARD_ETC_ROMFS_PASSWD_ENABLE),y)
-       $(Q) mkdir -p $(ETCDIR)$(DELIM)$(CONFIG_ETC_ROMFSMOUNTPT)
-       $(Q) $(TOPDIR)$(DELIM)tools$(DELIM)mkpasswd$(HOSTEXEEXT) \
+       $(Q) set -e; \
+       mkdir -p $(ETCDIR)$(DELIM)$(CONFIG_ETC_ROMFSMOUNTPT); \
+       $(TOPDIR)$(DELIM)tools$(DELIM)board_romfs_mkpasswd.sh \
+               $(TOPDIR) $(ETCDIR)$(DELIM).romfs_passwd.txt \
+               $(TOPDIR)$(DELIM)tools$(DELIM)mkpasswd$(HOSTEXEEXT) \
+               $(ETCDIR)$(DELIM)$(CONFIG_ETC_ROMFSMOUNTPT)$(DELIM)passwd \
                --user $(CONFIG_BOARD_ETC_ROMFS_PASSWD_USER) \
-               --password $(CONFIG_BOARD_ETC_ROMFS_PASSWD_PASSWORD) \
                --uid $(CONFIG_BOARD_ETC_ROMFS_PASSWD_UID) \
                --gid $(CONFIG_BOARD_ETC_ROMFS_PASSWD_GID) \
-               --home $(CONFIG_BOARD_ETC_ROMFS_PASSWD_HOME) \
-               --key1 $(CONFIG_FSUTILS_PASSWD_KEY1) \
-               --key2 $(CONFIG_FSUTILS_PASSWD_KEY2) \
-               --key3 $(CONFIG_FSUTILS_PASSWD_KEY3) \
-               --key4 $(CONFIG_FSUTILS_PASSWD_KEY4) \
-               -o $(ETCDIR)$(DELIM)$(CONFIG_ETC_ROMFSMOUNTPT)$(DELIM)passwd
+               --home $(CONFIG_BOARD_ETC_ROMFS_PASSWD_HOME)
 endif
        $(Q) genromfs -f romfs.img -d 
$(ETCDIR)$(DELIM)$(CONFIG_ETC_ROMFSMOUNTPT) -V "NSHInitVol"
        $(Q) echo "#include <nuttx/compiler.h>" > $@
diff --git a/cmake/nuttx_add_romfs.cmake b/cmake/nuttx_add_romfs.cmake
index 49cfc515242..404cbd7638e 100644
--- a/cmake/nuttx_add_romfs.cmake
+++ b/cmake/nuttx_add_romfs.cmake
@@ -322,10 +322,10 @@ function(process_all_directory_romfs)
       message(
         FATAL_ERROR
           "\n"
-          "  BUILD ERROR: Admin password not set.\n"
+          "  BUILD ERROR: Root password not set.\n"
           "\n"
           "  Run make menuconfig and set:\n"
-          "    Board Selection -> Auto-generate /etc/passwd -> Admin 
password\n"
+          "    Board Selection -> Auto-generate /etc/passwd -> Root password\n"
           "\n"
           "  For TEA keys, either enable random generation in the same menu,\n"
           "  or set CONFIG_FSUTILS_PASSWD_KEY1..4 under Application 
Configuration\n"
@@ -334,6 +334,12 @@ function(process_all_directory_romfs)
           "  Password and keys are not saved in defconfig.\n")
     endif()
 
+    if(CONFIG_FSUTILS_PASSWD_PBKDF2_ITERATIONS)
+      set(MKPASSWD_ITERATIONS ${CONFIG_FSUTILS_PASSWD_PBKDF2_ITERATIONS})
+    else()
+      set(MKPASSWD_ITERATIONS 10000)
+    endif()
+
     # Determine host executable suffix (.exe on Windows, empty elsewhere)
     if(CMAKE_HOST_WIN32)
       set(HOST_EXE_SUFFIX .exe)
@@ -360,79 +366,6 @@ function(process_all_directory_romfs)
     endif()
 
     set(GENPASSWD_OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/etc/passwd)
-
-    # Delegate detection and generation to the shell helpers so the logic is
-    # testable outside of cmake.  check_passwd_keys.sh prints "yes" when keys
-    # are absent or at insecure defaults; gen_passwd_keys.sh writes fresh
-    # /dev/urandom values in-place.
-    #
-    # RANDOMIZE_KEYS — single-invocation path: 1. gen_passwd_keys.sh writes new
-    # keys to .config at configure time. 2. We re-read .config below so
-    # CONFIG_FSUTILS_PASSWD_KEY1..4 carry the new values for the rest of this
-    # cmake configure run. 3. add_custom_command is registered with the updated
-    # key values, so both mkpasswd (passwd hash) and the firmware use the same
-    # keys — login works without a second cmake invocation.
-    #
-    # Note: config.h is regenerated at build time from .config (its 
dependency),
-    # so the firmware uses the correct keys automatically.
-
-    execute_process(
-      COMMAND ${NUTTX_POSIX_SHELL} "${NUTTX_DIR}/tools/check_passwd_keys.sh"
-              "${NUTTX_DIR}/.config"
-      OUTPUT_VARIABLE _passwd_keys_need_setup
-      OUTPUT_STRIP_TRAILING_WHITESPACE
-      RESULT_VARIABLE _check_rc)
-    if(NOT _check_rc EQUAL 0)
-      message(
-        FATAL_ERROR
-          "check_passwd_keys.sh failed — check 
${NUTTX_DIR}/tools/check_passwd_keys.sh"
-      )
-    endif()
-
-    if(_passwd_keys_need_setup STREQUAL "yes")
-      if(CONFIG_BOARD_ETC_ROMFS_PASSWD_RANDOMIZE_KEYS)
-        # Generate keys and write to .config (no key values printed here)
-        execute_process(
-          COMMAND ${NUTTX_POSIX_SHELL} "${NUTTX_DIR}/tools/gen_passwd_keys.sh"
-                  "${NUTTX_DIR}/.config"
-          RESULT_VARIABLE _gen_rc
-          OUTPUT_QUIET ERROR_QUIET)
-        if(NOT _gen_rc EQUAL 0)
-          message(
-            FATAL_ERROR
-              "gen_passwd_keys.sh failed — check ${NUTTX_DIR}/.config 
permissions"
-          )
-        endif()
-        message(
-          WARNING "[passwd] TEA keys auto-generated in .config. "
-                  "View: search .config for CONFIG_FSUTILS_PASSWD_KEY. "
-                  "Change: menuconfig -> Application Configuration -> "
-                  "File System Utilities -> Password file support.")
-
-        # Re-read .config so the new key values are live for this configure run
-        # (mirrors Board.mk's second -include).
-        file(STRINGS "${NUTTX_DIR}/.config" _fresh_config
-             REGEX "^CONFIG_FSUTILS_PASSWD_KEY[1-4]=")
-        foreach(_line ${_fresh_config})
-          if(_line MATCHES "^CONFIG_FSUTILS_PASSWD_KEY([1-4])=(.+)$")
-            set(CONFIG_FSUTILS_PASSWD_KEY${CMAKE_MATCH_1} "${CMAKE_MATCH_2}")
-          endif()
-        endforeach()
-      else()
-        message(
-          FATAL_ERROR
-            "\n"
-            "  BUILD ERROR: TEA encryption keys not configured.\n"
-            "\n"
-            "  Run make menuconfig and either:\n"
-            "    - enable Generate random TEA keys automatically, or\n"
-            "    - set CONFIG_FSUTILS_PASSWD_KEY1..4 under Application 
Configuration\n"
-            "      -> File System Utilities -> Password file support\n")
-      endif()
-    endif()
-
-    # At this point KEY1..4 are guaranteed to be correct (either freshly
-    # generated above, or manually set by the user).
     add_custom_command(
       OUTPUT ${GENPASSWD_OUTPUT}
       COMMAND ${CMAKE_COMMAND} -E make_directory 
${CMAKE_CURRENT_BINARY_DIR}/etc
@@ -441,12 +374,10 @@ function(process_all_directory_romfs)
         --password "${CONFIG_BOARD_ETC_ROMFS_PASSWD_PASSWORD}" --uid
         ${CONFIG_BOARD_ETC_ROMFS_PASSWD_UID} --gid
         ${CONFIG_BOARD_ETC_ROMFS_PASSWD_GID} --home
-        "${CONFIG_BOARD_ETC_ROMFS_PASSWD_HOME}" --key1
-        ${CONFIG_FSUTILS_PASSWD_KEY1} --key2 ${CONFIG_FSUTILS_PASSWD_KEY2}
-        --key3 ${CONFIG_FSUTILS_PASSWD_KEY3} --key4
-        ${CONFIG_FSUTILS_PASSWD_KEY4} -o ${GENPASSWD_OUTPUT}
+        "${CONFIG_BOARD_ETC_ROMFS_PASSWD_HOME}" --iterations
+        ${MKPASSWD_ITERATIONS} -o ${GENPASSWD_OUTPUT}
       DEPENDS ${MKPASSWD_BIN} ${NUTTX_DIR}/.config
-      COMMENT "Generating /etc/passwd from .config TEA keys")
+      COMMENT "Generating /etc/passwd with PBKDF2 hash")
 
     add_custom_target(generate_passwd DEPENDS ${GENPASSWD_OUTPUT})
     add_dependencies(generate_passwd build_host_mkpasswd)
diff --git a/cmake/savedefconfig.cmake b/cmake/savedefconfig.cmake
index 692a5f1b2d8..db6742a3621 100644
--- a/cmake/savedefconfig.cmake
+++ b/cmake/savedefconfig.cmake
@@ -76,7 +76,7 @@ list(SORT LINES)
 foreach(LINE IN LISTS LINES)
   decode_brackets(LINE)
   decode_semicolon(LINE)
-  if(NOT "${LINE}" MATCHES "^CONFIG_FSUTILS_PASSWD_KEY[0-9]"
+  if(NOT "${LINE}" MATCHES "^CONFIG_FSUTILS_PASSWD_PBKDF2_ITERATIONS="
      AND NOT "${LINE}" MATCHES "^CONFIG_BOARD_ETC_ROMFS_PASSWD_PASSWORD=")
     file(APPEND ${OUTPUT_FILE} "${LINE}\n")
   endif()
@@ -85,7 +85,8 @@ endforeach()
 if(PASSWD_AUTOGEN_ENABLED)
   message(
     WARNING
-      "CONFIG_BOARD_ETC_ROMFS_PASSWD_PASSWORD and CONFIG_FSUTILS_PASSWD_KEY1-4 
"
+      "CONFIG_BOARD_ETC_ROMFS_PASSWD_PASSWORD and "
+      "CONFIG_FSUTILS_PASSWD_PBKDF2_ITERATIONS "
       "were intentionally excluded from defconfig by savedefconfig. Add them "
       "manually in local defconfig if needed.")
 endif()
diff --git a/tools/Makefile.host b/tools/Makefile.host
index 410dca776ad..3a2926cd659 100644
--- a/tools/Makefile.host
+++ b/tools/Makefile.host
@@ -107,7 +107,7 @@ ifdef HOSTEXEEXT
 mkversion: mkversion$(HOSTEXEEXT)
 endif
 
-# mkpasswd - Generate a NuttX /etc/passwd entry with TEA-encrypted password
+# mkpasswd - Generate a NuttX /etc/passwd entry with PBKDF2-HMAC-SHA256 hash
 
 mkpasswd$(HOSTEXEEXT): mkpasswd.c
        $(Q) $(HOSTCC) $(HOSTCFLAGS) -o mkpasswd$(HOSTEXEEXT) mkpasswd.c
diff --git a/tools/Unix.mk b/tools/Unix.mk
index d421205c1e6..8424763d8d8 100644
--- a/tools/Unix.mk
+++ b/tools/Unix.mk
@@ -780,7 +780,7 @@ savedefconfig: apps_preconfig
        $(Q) ${KCONFIG_ENV} ${KCONFIG_SAVEDEFCONFIG}
        $(Q) $(call kconfig_tweak_disable,defconfig.tmp,CONFIG_APPS_DIR)
        $(Q) $(call kconfig_tweak_disable,defconfig.tmp,CONFIG_BASE_DEFCONFIG)
-       $(Q) sed -i.bak -e '/^CONFIG_FSUTILS_PASSWD_KEY[0-9]/d' defconfig.tmp
+       $(Q) sed -i.bak -e '/^CONFIG_FSUTILS_PASSWD_PBKDF2_ITERATIONS=/d' 
defconfig.tmp
        $(Q) sed -i.bak -e '/^CONFIG_BOARD_ETC_ROMFS_PASSWD_PASSWORD=/d' 
defconfig.tmp
        $(Q) grep "CONFIG_ARCH=" .config >> defconfig.tmp
        $(Q) grep "^CONFIG_ARCH_CHIP_" .config >> defconfig.tmp; true
@@ -803,7 +803,7 @@ savedefconfig: apps_preconfig
        $(Q) rm -f sortedconfig.tmp
        $(Q) if grep -q '^CONFIG_BOARD_ETC_ROMFS_PASSWD_ENABLE=y' .config; then 
\
                echo "WARNING: CONFIG_BOARD_ETC_ROMFS_PASSWD_PASSWORD was not 
saved in defconfig."; \
-               echo "WARNING: CONFIG_FSUTILS_PASSWD_KEY1-4 were not saved in 
defconfig."; \
+               echo "WARNING: CONFIG_FSUTILS_PASSWD_PBKDF2_ITERATIONS was not 
saved in defconfig."; \
                echo "WARNING: This is intentional to avoid leaking 
credentials. Add them manually in local defconfig if needed."; \
        fi
 
diff --git a/tools/board_romfs_mkpasswd.sh b/tools/board_romfs_mkpasswd.sh
new file mode 100755
index 00000000000..2256bf91c98
--- /dev/null
+++ b/tools/board_romfs_mkpasswd.sh
@@ -0,0 +1,62 @@
+#!/usr/bin/env bash
+# tools/board_romfs_mkpasswd.sh
+#
+# 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.
+#
+# Ensure the ROMFS root password is configured, then run mkpasswd.
+# Arguments:
+#   board_romfs_mkpasswd.sh <nuttx-topdir> <passfile> <mkpasswd> <output> 
[mkpasswd args...]
+
+set -e
+
+TOPDIR=$1
+PASSFILE=$2
+MKPASSWD=$3
+OUTPUT=$4
+shift 4
+
+CONFIG_FILE="${TOPDIR}/.config"
+
+read_int_config() {
+  local symbol=$1
+  local default=$2
+  local value
+
+  value=$(grep "^${symbol}=" "${CONFIG_FILE}" 2>/dev/null | cut -d= -f2- | tr 
-d '"')
+  if [ -z "${value}" ]; then
+    echo "${default}"
+  else
+    echo "${value}"
+  fi
+}
+
+ITERATIONS=$(read_int_config CONFIG_FSUTILS_PASSWD_PBKDF2_ITERATIONS 10000)
+
+"${TOPDIR}/tools/promptpasswd.sh" \
+  --min 8 \
+  --config CONFIG_BOARD_ETC_ROMFS_PASSWD_PASSWORD \
+  --config-file "${CONFIG_FILE}" \
+  --update-config \
+  --prompt "ROMFS root password (min 8 characters): " \
+  --output-file "${PASSFILE}"
+
+PASSWORD=$(cat "${PASSFILE}")
+"${MKPASSWD}" --password "${PASSWORD}" \
+  --iterations "${ITERATIONS}" \
+  "$@" -o "${OUTPUT}"
+rm -f "${PASSFILE}"
diff --git a/tools/configure.sh b/tools/configure.sh
index 47a53e4176e..1fdaeabae80 100755
--- a/tools/configure.sh
+++ b/tools/configure.sh
@@ -356,7 +356,7 @@ echo "CONFIG_BASE_DEFCONFIG=\"$posboardconfig\"" >> 
"${dest_config}"
 
 ${TOPDIR}/tools/sethost.sh $host $*
 
-# Supply ROMFS admin password from NUTTX_ROMFS_PASSWD_PASSWORD when absent
+# Supply ROMFS root password from NUTTX_ROMFS_PASSWD_PASSWORD when absent
 "${TOPDIR}/tools/update_romfs_password.sh" "${dest_config}"
 
 # Save the original configuration file without CONFIG_BASE_DEFCONFIG
diff --git a/tools/mkpasswd.c b/tools/mkpasswd.c
index 9eb2fe55525..bd5a9f8fc75 100644
--- a/tools/mkpasswd.c
+++ b/tools/mkpasswd.c
@@ -22,46 +22,28 @@
 
 /****************************************************************************
  * Description:
- *   Host tool that writes one NuttX /etc/passwd line with a TEA-encrypted
- *   password hash.  The plaintext password is never stored in the output.
+ *   Host build tool that generates a NuttX /etc/passwd entry with a
+ *   PBKDF2-HMAC-SHA256 password hash.  This is a pure C replacement for the
+ *   former tools/mkpasswd.py, removing the Python dependency from the build.
  *
- *   Build integration:
- *     When ``CONFIG_BOARD_ETC_ROMFS_PASSWD_ENABLE=y``, ``boards/Board.mk``
- *     invokes this program during the ROMFS etc/ image build.  The password
- *     and TEA keys are taken from ``.config`` (see ``tools/passwd_keys.mk``,
- *     ``tools/update_romfs_password.sh``, and
- *     Documentation/components/tools/index.rst).
+ *   The hash format is identical to that used at runtime by:
+ *     apps/fsutils/passwd/passwd_encrypt.c
+ *     apps/fsutils/passwd/passwd_verify.c
  *
- *   Runtime compatibility:
- *     The encryption algorithm and base64 encoding match:
- *       libs/libc/misc/lib_tea_encrypt.c
- *       apps/fsutils/passwd/passwd_encrypt.c
- *
- *   Security (enforced before writing output):
- *     - Password must be non-empty and at least 8 characters.
- *     - Password ``Administrator`` is rejected (legacy insecure default).
- *     - The published default TEA key set (0x12345678 / 0x9abcdef0)
- *       is rejected.  Use Kconfig keys or explicit --key options.
- *
- * Standalone usage (advanced / debugging only):
- *   mkpasswd --user <name> --password <pass> \\
- *            --key1 <hex> --key2 <hex> --key3 <hex> --key4 <hex> \\
- *            [-o <output>]
+ * Usage:
+ *   mkpasswd --user <name> --password <pass> [options] [-o <output>]
  *
  * Options:
- *   --user     <str>  Username (required)
- *   --password <str>  Plaintext password (required, min 8 characters)
- *   --uid      <int>  User ID          (default: 0)
- *   --gid      <int>  Group ID         (default: 0)
- *   --home     <str>  Home directory   (default: /)
- *   --key1     <hex>  TEA key word 1   (required for standalone use)
- *   --key2     <hex>  TEA key word 2   (required for standalone use)
- *   --key3     <hex>  TEA key word 3   (required for standalone use)
- *   --key4     <hex>  TEA key word 4   (required for standalone use)
- *   -o         <path> Output file      (default: stdout)
+ *   --user       <str>  Username (required)
+ *   --password   <str>  Plaintext password (required, not stored in output)
+ *   --uid        <int>  User ID          (default: 0)
+ *   --gid        <int>  Group ID         (default: 0)
+ *   --home       <str>  Home directory   (default: /)
+ *   --iterations <int>  PBKDF2 iterations (default: 10000)
+ *   -o           <path> Output file      (default: stdout)
  *
- * Output format:
- *   username:encrypted_hash:uid:gid:home
+ * Output format (matches NuttX passwd file format):
+ *   username:$pbkdf2-sha256$<iter>$<salt>$<hash>:uid:gid:home
  *
  ****************************************************************************/
 
@@ -69,11 +51,6 @@
  * Included Files
  ****************************************************************************/
 
-/* Expose strdup(), mkdir() and other POSIX.1-2008 extensions when
- * compiling with strict C99 mode (-std=c99). Has no effect on C11/GNU
- * builds or MSVC.
- */
-
 #ifndef _POSIX_C_SOURCE
 #  define _POSIX_C_SOURCE 200809L
 #endif
@@ -82,9 +59,13 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <ctype.h>
 #include <errno.h>
 #ifndef CONFIG_WINDOWS_NATIVE
+#  include <fcntl.h>
+#  include <sys/random.h>
 #  include <sys/stat.h>
+#  include <unistd.h>
 #else
 #  include <direct.h>
 #endif
@@ -93,266 +74,503 @@
  * Pre-processor Definitions
  ****************************************************************************/
 
-/* TEA key schedule constant (derived from the golden ratio) */
-
-#define TEA_KEY_SCHEDULE_CONSTANT  0x9e3779b9u
-
-/* Password size limits - must match apps/fsutils/passwd/passwd.h */
-
-#define MAX_ENCRYPTED  48                      /* Max size of encrypted 
password (ASCII) */
-#define MAX_PASSWORD   (3 * MAX_ENCRYPTED / 4) /* Max plaintext length */
-#define MIN_PASSWORD   8                       /* Minimum plaintext length for 
security */
-
-/* Known-insecure TEA key values from legacy NuttX releases.  Used only to
- * detect and reject the published default set in main(); normal builds pass
- * keys from CONFIG_FSUTILS_PASSWD_KEY1..4 via boards/Board.mk.
- */
+#define MKPASSWD_NL              "\n\n"
+#define PASSWD_MCF_PREFIX        "$pbkdf2-sha256$"
+#define PASSWD_SALT_BYTES        16
+#define PASSWD_HASH_BYTES        32
+#define MAX_ENCRYPTED            96
+#define MAX_PASSWORD             256
+#define MIN_PASSWORD             8
+#define DEFAULT_ITERATIONS       10000
+#define MIN_ITERATIONS           1000
+#define MAX_ITERATIONS           200000
 
-#define DEFAULT_KEY1   0x12345678u
-#define DEFAULT_KEY2   0x9abcdef0u
-#define DEFAULT_KEY3   0x12345678u
-#define DEFAULT_KEY4   0x9abcdef0u
+static const char g_base64url[] =
+  "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
 
 /****************************************************************************
- * Private Types
+ * Private Types (minimal SHA-256)
  ****************************************************************************/
 
-/* 8-byte block interpreted as bytes, 16-bit halves, or 32-bit words */
-
-union block_u
+struct sha256_ctx
 {
-  char     b[8];
-  uint16_t h[4];
-  uint32_t l[2];
+  uint32_t state[8];
+  uint64_t bitlen;
+  uint8_t  data[64];
+  uint32_t datalen;
 };
 
 /****************************************************************************
- * Private Functions
+ * Private Functions (SHA-256 + HMAC-SHA256 + PBKDF2)
  ****************************************************************************/
 
-/****************************************************************************
- * Name: tea_encrypt
- *
- * Description:
- *   Encrypt two 32-bit words in-place using the Tiny Encryption Algorithm.
- *   This is an exact copy of the algorithm in
- *   libs/libc/misc/lib_tea_encrypt.c (public-domain TEA by Wheeler &
- *   Needham), inlined here so that the host tool has no NuttX dependencies.
- *
- * Input Parameters:
- *   value - Two-element array [v0, v1] to encrypt (modified in-place)
- *   key   - Four-element 128-bit key array
- *
- ****************************************************************************/
+static uint32_t rotr32(uint32_t x, uint32_t n)
+{
+  return (x >> n) | (x << (32 - n));
+}
 
-static void tea_encrypt(uint32_t *value, const uint32_t *key)
+static void sha256_transform(struct sha256_ctx *ctx,
+                             const uint8_t data[64])
 {
-  uint32_t v0  = value[0];
-  uint32_t v1  = value[1];
-  uint32_t sum = 0;
+  static const uint32_t k[64] =
+  {
+    0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
+    0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
+    0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
+    0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
+    0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
+    0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
+    0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
+    0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
+    0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
+    0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
+    0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
+    0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
+    0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
+    0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
+    0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
+    0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
+  };
+
+  uint32_t m[64];
+  uint32_t a;
+  uint32_t b;
+  uint32_t c;
+  uint32_t d;
+  uint32_t e;
+  uint32_t f;
+  uint32_t g;
+  uint32_t h;
+  uint32_t t1;
+  uint32_t t2;
+  uint32_t s0;
+  uint32_t s1;
   int i;
 
-  for (i = 0; i < 32; i++)
+  for (i = 0; i < 16; i++)
     {
-      sum += TEA_KEY_SCHEDULE_CONSTANT;
-      v0  += ((v1 << 4) + key[0]) ^ (v1 + sum) ^ ((v1 >> 5) + key[1]);
-      v1  += ((v0 << 4) + key[2]) ^ (v0 + sum) ^ ((v0 >> 5) + key[3]);
+      m[i] = ((uint32_t)data[i * 4] << 24) |
+             ((uint32_t)data[i * 4 + 1] << 16) |
+             ((uint32_t)data[i * 4 + 2] << 8) |
+             ((uint32_t)data[i * 4 + 3]);
+    }
+
+  for (i = 16; i < 64; i++)
+    {
+      s0 = rotr32(m[i - 15], 7) ^ rotr32(m[i - 15], 18) ^
+           (m[i - 15] >> 3);
+      s1 = rotr32(m[i - 2], 17) ^ rotr32(m[i - 2], 19) ^
+           (m[i - 2] >> 10);
+
+      m[i] = m[i - 16] + s0 + m[i - 7] + s1;
     }
 
-  value[0] = v0;
-  value[1] = v1;
+  a = ctx->state[0];
+  b = ctx->state[1];
+  c = ctx->state[2];
+  d = ctx->state[3];
+  e = ctx->state[4];
+  f = ctx->state[5];
+  g = ctx->state[6];
+  h = ctx->state[7];
+
+  for (i = 0; i < 64; i++)
+    {
+      t1 = h + (rotr32(e, 6) ^ rotr32(e, 11) ^ rotr32(e, 25)) +
+           ((e & f) ^ ((~e) & g)) + k[i] + m[i];
+      t2 = (rotr32(a, 2) ^ rotr32(a, 13) ^ rotr32(a, 22)) +
+           ((a & b) ^ (a & c) ^ (b & c));
+      h = g;
+      g = f;
+      f = e;
+      e = d + t1;
+      d = c;
+      c = b;
+      b = a;
+      a = t1 + t2;
+    }
+
+  ctx->state[0] += a;
+  ctx->state[1] += b;
+  ctx->state[2] += c;
+  ctx->state[3] += d;
+  ctx->state[4] += e;
+  ctx->state[5] += f;
+  ctx->state[6] += g;
+  ctx->state[7] += h;
 }
 
-/****************************************************************************
- * Name: passwd_base64
- *
- * Description:
- *   Encode the low 6 bits of a byte as a custom base64 character.
- *   Alphabet: A-Z (0-25), a-z (26-51), 0-9 (52-61), + (62), / (63).
- *   The colon ':' is deliberately absent so it never collides with the
- *   passwd field separator.
- *
- *   This matches passwd_base64() in apps/fsutils/passwd/passwd_encrypt.c.
- *
- ****************************************************************************/
+static void sha256_init(struct sha256_ctx *ctx)
+{
+  ctx->datalen = 0;
+  ctx->bitlen  = 0;
+  ctx->state[0] = 0x6a09e667;
+  ctx->state[1] = 0xbb67ae85;
+  ctx->state[2] = 0x3c6ef372;
+  ctx->state[3] = 0xa54ff53a;
+  ctx->state[4] = 0x510e527f;
+  ctx->state[5] = 0x9b05688c;
+  ctx->state[6] = 0x1f83d9ab;
+  ctx->state[7] = 0x5be0cd19;
+}
 
-static char passwd_base64(uint8_t binary)
+static void sha256_update(struct sha256_ctx *ctx,
+                          const uint8_t *data, size_t len)
 {
-  binary &= 63;
+  size_t i;
 
-  if (binary < 26)
+  for (i = 0; i < len; i++)
     {
-      return (char)('A' + binary);
+      ctx->data[ctx->datalen] = data[i];
+      ctx->datalen++;
+      if (ctx->datalen == 64)
+        {
+          sha256_transform(ctx, ctx->data);
+          ctx->bitlen += 512;
+          ctx->datalen = 0;
+        }
     }
+}
+
+static void sha256_final(struct sha256_ctx *ctx, uint8_t hash[32])
+{
+  uint32_t i;
+  uint32_t j;
+
+  i = ctx->datalen;
 
-  binary -= 26;
-  if (binary < 26)
+  if (ctx->datalen < 56)
+    {
+      ctx->data[i++] = 0x80;
+      while (i < 56)
+        {
+          ctx->data[i++] = 0x00;
+        }
+    }
+  else
     {
-      return (char)('a' + binary);
+      ctx->data[i++] = 0x80;
+      while (i < 64)
+        {
+          ctx->data[i++] = 0x00;
+        }
+
+      sha256_transform(ctx, ctx->data);
+      memset(ctx->data, 0, 56);
     }
 
-  binary -= 26;
-  if (binary < 10)
+  ctx->bitlen += (uint64_t)ctx->datalen * 8;
+  ctx->data[63] = (uint8_t)(ctx->bitlen);
+  ctx->data[62] = (uint8_t)(ctx->bitlen >> 8);
+  ctx->data[61] = (uint8_t)(ctx->bitlen >> 16);
+  ctx->data[60] = (uint8_t)(ctx->bitlen >> 24);
+  ctx->data[59] = (uint8_t)(ctx->bitlen >> 32);
+  ctx->data[58] = (uint8_t)(ctx->bitlen >> 40);
+  ctx->data[57] = (uint8_t)(ctx->bitlen >> 48);
+  ctx->data[56] = (uint8_t)(ctx->bitlen >> 56);
+  sha256_transform(ctx, ctx->data);
+
+  for (i = 0; i < 4; i++)
     {
-      return (char)('0' + binary);
+      for (j = 0; j < 8; j++)
+        {
+          hash[i + (j * 4)] = (uint8_t)((ctx->state[j] >>
+                                         (24 - i * 8)) & 0xff);
+        }
     }
+}
 
-  binary -= 10;
-  if (binary == 0)
+static void hmac_sha256(const uint8_t *key, size_t keylen,
+                        const uint8_t *data, size_t datalen,
+                        uint8_t mac[32])
+{
+  struct sha256_ctx ctx;
+  uint8_t k_ipad[64];
+  uint8_t k_opad[64];
+  uint8_t tk[32];
+  size_t i;
+
+  if (keylen > 64)
     {
-      return '+';
+      sha256_init(&ctx);
+      sha256_update(&ctx, key, keylen);
+      sha256_final(&ctx, tk);
+      key    = tk;
+      keylen = 32;
     }
 
-  return '/';
-}
+  memset(k_ipad, 0, sizeof(k_ipad));
+  memset(k_opad, 0, sizeof(k_opad));
+  memcpy(k_ipad, key, keylen);
+  memcpy(k_opad, key, keylen);
 
-/****************************************************************************
- * Name: passwd_encrypt
- *
- * Description:
- *   Encrypt a plaintext password string and store the result as a
- *   NUL-terminated base64 string in `encrypted`.
- *
- *   Algorithm (identical to apps/fsutils/passwd/passwd_encrypt.c):
- *     1. Process the password in 8-byte gulps, padding short gulps with
- *        ASCII spaces.
- *     2. TEA-encrypt each 8-byte gulp as two uint32_t words.
- *     3. Interpret the result as four uint16_t half-words.
- *     4. Stream-encode those half-words 6 bits at a time using the custom
- *        base64 alphabet above.
- *
- * Input Parameters:
- *   password  - NUL-terminated plaintext password
- *   key       - Four-element 128-bit TEA key
- *   encrypted - Output buffer (at least MAX_ENCRYPTED + 1 bytes)
- *
- * Returned Value:
- *   0 on success, -1 on error (password too long).
- *
- ****************************************************************************/
+  for (i = 0; i < 64; i++)
+    {
+      k_ipad[i] ^= 0x36;
+      k_opad[i] ^= 0x5c;
+    }
+
+  sha256_init(&ctx);
+  sha256_update(&ctx, k_ipad, 64);
+  sha256_update(&ctx, data, datalen);
+  sha256_final(&ctx, mac);
+
+  sha256_init(&ctx);
+  sha256_update(&ctx, k_opad, 64);
+  sha256_update(&ctx, mac, 32);
+  sha256_final(&ctx, mac);
+}
 
-static int passwd_encrypt(const char *password,
-                          const uint32_t *key,
-                          char encrypted[MAX_ENCRYPTED + 1])
+static int pbkdf2_hmac_sha256(const uint8_t *pass, size_t passlen,
+                              const uint8_t *salt, size_t saltlen,
+                              uint32_t iterations,
+                              uint8_t *out, size_t outlen)
 {
-  union block_u value;
-  const char   *src;
-  char         *dest;
-  uint32_t      tmp;
-  uint8_t       remainder;
-  int           remaining;
-  int           gulpsize;
-  int           nbits;
-  int           i;
-
-  remaining = (int)strlen(password);
-  if (remaining > MAX_PASSWORD)
-    {
-      fprintf(stderr, "mkpasswd: password too long (max %d characters)\n",
-              MAX_PASSWORD);
+  uint8_t u[32];
+  uint8_t t[32];
+  uint8_t saltblk[64];
+  size_t generated = 0;
+  uint32_t block;
+  uint32_t i;
+  uint32_t j;
+
+  if (iterations == 0 || outlen == 0 || saltlen + 4 > sizeof(saltblk))
+    {
       return -1;
     }
 
-  src       = password;
-  dest      = encrypted;
-  *dest     = '\0';
-  remainder = 0;
-  nbits     = 0;
-
-  for (; remaining > 0; remaining -= gulpsize)
+  for (block = 1; generated < outlen; block++)
     {
-      /* Copy up to 8 bytes into the block, padding the rest with spaces */
+      memcpy(saltblk, salt, saltlen);
+      saltblk[saltlen + 0] = (uint8_t)((block >> 24) & 0xff);
+      saltblk[saltlen + 1] = (uint8_t)((block >> 16) & 0xff);
+      saltblk[saltlen + 2] = (uint8_t)((block >> 8) & 0xff);
+      saltblk[saltlen + 3] = (uint8_t)(block & 0xff);
+
+      hmac_sha256(pass, passlen, saltblk, saltlen + 4, u);
+      memcpy(t, u, sizeof(t));
 
-      gulpsize = 8;
-      if (gulpsize > remaining)
+      for (i = 1; i < iterations; i++)
         {
-          gulpsize = remaining;
+          hmac_sha256(pass, passlen, u, sizeof(u), u);
+          for (j = 0; j < 32; j++)
+            {
+              t[j] ^= u[j];
+            }
         }
 
-      for (i = 0; i < gulpsize; i++)
+      if (outlen - generated >= 32)
         {
-          value.b[i] = *src++;
+          memcpy(out + generated, t, 32);
+          generated += 32;
         }
-
-      for (; i < 8; i++)
+      else
         {
-          value.b[i] = ' ';
+          memcpy(out + generated, t, outlen - generated);
+          generated = outlen;
         }
+    }
+
+  return 0;
+}
+
+static int fill_random(uint8_t *buf, size_t len)
+{
+#ifndef CONFIG_WINDOWS_NATIVE
+  ssize_t nread;
+  int fd;
 
-      /* TEA-encrypt the block in-place */
+#  ifdef SYS_getrandom
+  nread = getrandom(buf, len, 0);
+  if (nread == (ssize_t)len)
+    {
+      return 0;
+    }
+#  endif
 
-      tea_encrypt(value.l, key);
+  fd = open("/dev/urandom", O_RDONLY);
+  if (fd < 0)
+    {
+      return -1;
+    }
 
-      /* Stream-encode the four 16-bit half-words into base64 */
+  nread = read(fd, buf, len);
+  close(fd);
 
-      tmp = remainder;
+  return nread == (ssize_t)len ? 0 : -1;
+#else
+  (void)buf;
+  (void)len;
+  return -1;
+#endif
+}
 
-      for (i = 0; i < 4; i++)
-        {
-          tmp    = ((uint32_t)value.h[i] << nbits) | tmp;
-          nbits += 16;
+static int base64url_encode(const uint8_t *in, size_t inlen,
+                            char *out, size_t outlen)
+{
+  uint32_t acc = 0;
+  size_t i;
+  size_t o = 0;
+  int bits = 0;
+
+  for (i = 0; i < inlen; i++)
+    {
+      acc = (acc << 8) | in[i];
+      bits += 8;
 
-          while (nbits >= 6)
+      while (bits >= 6)
+        {
+          if (o + 1 >= outlen)
             {
-              *dest++ = passwd_base64((uint8_t)(tmp & 0x3f));
-              tmp   >>= 6;
-              nbits  -= 6;
+              return -1;
             }
-        }
 
-      remainder = (uint8_t)tmp;
-      *dest     = '\0';
+          bits -= 6;
+          out[o++] = g_base64url[(acc >> bits) & 0x3f];
+        }
     }
 
-  /* Flush any remaining bits */
+  if (bits > 0)
+    {
+      if (o + 1 >= outlen)
+        {
+          return -1;
+        }
 
-  if (nbits > 0)
+      out[o++] = g_base64url[(acc << (6 - bits)) & 0x3f];
+    }
+
+  if (o >= outlen)
     {
-      *dest++ = passwd_base64(remainder);
-      *dest   = '\0';
+      return -1;
     }
 
+  out[o] = '\0';
   return 0;
 }
 
-/****************************************************************************
- * Name: parse_uint32_hex
- *
- * Description:
- *   Parse a hex string (with or without leading "0x"/"0X") into a uint32_t.
- *   Returns 0 on success, -1 on parse error.
- *
- ****************************************************************************/
-
-static int parse_uint32_hex(const char *str, uint32_t *out)
+static int validate_password_complexity(const char *password)
 {
-  char *endptr;
-  unsigned long val;
+  const char *specials = "!@#$%^&*()_+-=[]{}|;:,.<>?";
+  const char *p;
+  int has_upper   = 0;
+  int has_lower   = 0;
+  int has_digit   = 0;
+  int has_special = 0;
+
+  if (strlen(password) < MIN_PASSWORD)
+    {
+      fprintf(stderr, "\nError: password must be at least 8 characters\n\n");
+      return -1;
+    }
+
+  if (strlen(password) > MAX_PASSWORD)
+    {
+      fprintf(stderr, "\nError: password must be at most %d characters\n\n",
+              MAX_PASSWORD);
+      return -1;
+    }
+
+  for (p = password; *p; 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(specials, *p))
+        {
+          has_special = 1;
+        }
+    }
+
+  if (!has_upper)
+    {
+      fprintf(stderr,
+              "\nError: password must contain at least one uppercase "
+              "letter (A-Z)\n\n");
+      return -1;
+    }
 
-  if (str == NULL || *str == '\0')
+  if (!has_lower)
     {
+      fprintf(stderr,
+              "\nError: password must contain at least one lowercase "
+              "letter (a-z)\n\n");
       return -1;
     }
 
-  errno = 0;
-  val   = strtoul(str, &endptr, 0);  /* base 0: auto-detect 0x prefix */
-  if (errno != 0 || *endptr != '\0')
+  if (!has_digit)
     {
+      fprintf(stderr,
+              "\nError: password must contain at least one digit "
+              "(0-9)\n\n");
+      return -1;
+    }
+
+  if (!has_special)
+    {
+      fprintf(stderr,
+              "\nError: password must contain at least one special "
+              "character (!@#$%%^&*()_+-=[]{}|;:,.<>?)\n\n");
       return -1;
     }
 
-  *out = (uint32_t)val;
   return 0;
 }
 
-/****************************************************************************
- * Name: mkdir_p
- *
- * Description:
- *   Create all directory components in `path`, like "mkdir -p".
- *   Returns 0 on success, -1 on error.
- *
- ****************************************************************************/
+static int passwd_hash(const char *password,
+                       uint32_t iterations,
+                       char encrypted[MAX_ENCRYPTED + 1])
+{
+  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;
+
+  passlen = strlen(password);
+
+  if (fill_random(salt, sizeof(salt)) < 0)
+    {
+      fputs(MKPASSWD_NL, stderr);
+      fprintf(stderr, "mkpasswd: cannot obtain random salt\n");
+      return -1;
+    }
+
+  if (pbkdf2_hmac_sha256((const uint8_t *)password, passlen,
+                         salt, sizeof(salt), iterations,
+                         hash, sizeof(hash)) < 0)
+    {
+      return -1;
+    }
+
+  if (base64url_encode(salt, sizeof(salt), salt_b64,
+                       sizeof(salt_b64)) < 0 ||
+      base64url_encode(hash, sizeof(hash), hash_b64,
+                       sizeof(hash_b64)) < 0)
+    {
+      return -1;
+    }
+
+  ret = snprintf(encrypted, MAX_ENCRYPTED + 1,
+                 PASSWD_MCF_PREFIX "%u$%s$%s",
+                 iterations, salt_b64, hash_b64);
+  if (ret < 0 || (size_t)ret > MAX_ENCRYPTED)
+    {
+      return -1;
+    }
+
+  return 0;
+}
 
 static int mkdir_p(const char *path)
 {
@@ -368,8 +586,6 @@ static int mkdir_p(const char *path)
 
   len = strlen(tmp);
 
-  /* Strip trailing slash */
-
   if (len > 0 && tmp[len - 1] == '/')
     {
       tmp[len - 1] = '\0';
@@ -398,35 +614,22 @@ static int mkdir_p(const char *path)
   return 0;
 }
 
-/****************************************************************************
- * Name: show_usage
- ****************************************************************************/
-
 static void show_usage(const char *progname)
 {
   fprintf(stderr,
-          "Usage: %s --user <name> --password <pass>\n"
-          "          --key1 <hex> --key2 <hex> --key3 <hex> --key4 <hex>\n"
-          "          [options] [-o <file>]\n"
+          "Usage: %s --user <name> --password <pass> [options] [-o <file>]\n"
           "\n"
           "Options:\n"
-          "  --user     <str>  Username (required)\n"
-          "  --password <str>  Plaintext password (required, min %d chars)\n"
-          "  --uid      <int>  User ID          (default: 0)\n"
-          "  --gid      <int>  Group ID         (default: 0)\n"
-          "  --home     <str>  Home directory   (default: /)\n"
-          "  --key1     <hex>  TEA key word 1\n"
-          "  --key2     <hex>  TEA key word 2   (all four required;\n"
-          "  --key3     <hex>  TEA key word 3    legacy defaults rejected)\n"
-          "  --key4     <hex>  TEA key word 4\n"
-          "  -o         <path> Output file      (default: stdout)\n"
-          "\n"
-          "Rejected: empty password, \"Administrator\", default TEA keys.\n"
-          "See Documentation/components/tools/index.rst for normal builds.\n"
+          "  --user       <str>  Username (required)\n"
+          "  --password   <str>  Plaintext password (required)\n"
+          "  --uid        <int>  User ID          (default: 0)\n"
+          "  --gid        <int>  Group ID         (default: 0)\n"
+          "  --home       <str>  Home directory   (default: /)\n"
+          "  --iterations <int>  PBKDF2 iterations (default: %d)\n"
+          "  -o           <path> Output file      (default: stdout)\n"
           "\n"
-          "Output format:  username:encrypted_hash:uid:gid:home\n",
-          progname,
-          MIN_PASSWORD);
+          "Output format:  username:$pbkdf2-sha256$...:uid:gid:home\n",
+          progname, DEFAULT_ITERATIONS);
 }
 
 /****************************************************************************
@@ -435,23 +638,25 @@ static void show_usage(const char *progname)
 
 int main(int argc, char **argv)
 {
-  const char *user     = NULL;
-  const char *password = NULL;
-  const char *home     = "/";
-  const char *outpath  = NULL;
-  int         uid      = 0;
-  int         gid      = 0;
-  uint32_t    key[4]   =
-    {
-      DEFAULT_KEY1, DEFAULT_KEY2, DEFAULT_KEY3, DEFAULT_KEY4
-    };
-
-  char encrypted[MAX_ENCRYPTED + 1];
+  const char *user;
+  const char *password;
+  const char *home;
+  const char *outpath;
   FILE *out;
-  int   i;
-  int   ret;
+  char encrypted[MAX_ENCRYPTED + 1];
+  int uid;
+  int gid;
+  uint32_t iterations;
+  int i;
+  int ret;
 
-  /* Simple long-option parser (avoids getopt_long portability concerns) */
+  user       = NULL;
+  password   = NULL;
+  home       = "/";
+  outpath    = NULL;
+  uid        = 0;
+  gid        = 0;
+  iterations = DEFAULT_ITERATIONS;
 
   for (i = 1; i < argc; i++)
     {
@@ -475,41 +680,9 @@ int main(int argc, char **argv)
         {
           home = argv[++i];
         }
-      else if (strcmp(argv[i], "--key1") == 0 && i + 1 < argc)
-        {
-          if (parse_uint32_hex(argv[++i], &key[0]) < 0)
-            {
-                    fprintf(stderr, "mkpasswd: invalid --key1 value: %s\n",
-                      argv[i]);
-              return 1;
-            }
-        }
-      else if (strcmp(argv[i], "--key2") == 0 && i + 1 < argc)
-        {
-          if (parse_uint32_hex(argv[++i], &key[1]) < 0)
-            {
-                    fprintf(stderr, "mkpasswd: invalid --key2 value: %s\n",
-                      argv[i]);
-              return 1;
-            }
-        }
-      else if (strcmp(argv[i], "--key3") == 0 && i + 1 < argc)
+      else if (strcmp(argv[i], "--iterations") == 0 && i + 1 < argc)
         {
-          if (parse_uint32_hex(argv[++i], &key[2]) < 0)
-            {
-                    fprintf(stderr, "mkpasswd: invalid --key3 value: %s\n",
-                      argv[i]);
-              return 1;
-            }
-        }
-      else if (strcmp(argv[i], "--key4") == 0 && i + 1 < argc)
-        {
-          if (parse_uint32_hex(argv[++i], &key[3]) < 0)
-            {
-                    fprintf(stderr, "mkpasswd: invalid --key4 value: %s\n",
-                      argv[i]);
-              return 1;
-            }
+          iterations = (uint32_t)strtoul(argv[++i], NULL, 10);
         }
       else if ((strcmp(argv[i], "-o") == 0 ||
                 strcmp(argv[i], "--output") == 0) && i + 1 < argc)
@@ -524,16 +697,16 @@ int main(int argc, char **argv)
         }
       else
         {
+          fputs(MKPASSWD_NL, stderr);
           fprintf(stderr, "mkpasswd: unknown option: %s\n", argv[i]);
           show_usage(argv[0]);
           return 1;
         }
     }
 
-  /* Validate required arguments */
-
   if (user == NULL)
     {
+      fputs(MKPASSWD_NL, stderr);
       fprintf(stderr, "mkpasswd: --user is required\n");
       show_usage(argv[0]);
       return 1;
@@ -541,78 +714,39 @@ int main(int argc, char **argv)
 
   if (password == NULL)
     {
+      fputs(MKPASSWD_NL, stderr);
       fprintf(stderr, "mkpasswd: --password is required\n");
       show_usage(argv[0]);
       return 1;
     }
 
-  if (password[0] == '\0')
-    {
-      fprintf(stderr,
-              "mkpasswd: ERROR: password must not be empty.\n"
-              "  Set it in menuconfig: Board Selection -> "
-              "Auto-generate /etc/passwd -> Admin password\n");
-      return 1;
-    }
-
-  if (strlen(password) < MIN_PASSWORD)
+  if (validate_password_complexity(password) < 0)
     {
-      fprintf(stderr,
-              "mkpasswd: --password must be at least %d characters\n",
-              MIN_PASSWORD);
       return 1;
     }
 
-  /* Reject the well-known default password.  The build system should have
-   * caught this already; mkpasswd is the last line of defence.
-   */
-
-  if (strcmp(password, "Administrator") == 0)
+  if (iterations < MIN_ITERATIONS || iterations > MAX_ITERATIONS)
     {
+      fputs(MKPASSWD_NL, stderr);
       fprintf(stderr,
-              "mkpasswd: ERROR: password \"Administrator\" is not allowed.\n"
-              "  Set a unique password in menuconfig: Board Selection -> "
-              "Auto-generate /etc/passwd -> Admin password\n");
+              "mkpasswd: --iterations must be between %d and %d\n",
+              MIN_ITERATIONS, MAX_ITERATIONS);
       return 1;
     }
 
-  /* Reject the default TEA keys.  Using the published defaults means any
-   * attacker who has a copy of the NuttX source can decrypt the password
-   * hash directly from the firmware image (CWE-321).
-   */
-
-  if (key[0] == DEFAULT_KEY1 && key[1] == DEFAULT_KEY2 &&
-      key[2] == DEFAULT_KEY3 && key[3] == DEFAULT_KEY4)
-    {
-      fprintf(stderr,
-              "mkpasswd: ERROR: default TEA encryption keys "
-              "are not allowed.\n"
-              "  Set keys in menuconfig: Application Configuration -> "
-              "File System Utilities -> Password file support\n"
-              "  Or enable random key generation under Board Selection -> "
-              "Auto-generate /etc/passwd\n");
-      return 1;
-    }
-
-  /* Encrypt the password using TEA + custom base64.
-   * Only the hash is written to the output file; the plaintext is never
-   * stored in firmware.
-   */
-
-  ret = passwd_encrypt(password, key, encrypted);
+  ret = passwd_hash(password, iterations, encrypted);
   if (ret < 0)
     {
       return 1;
     }
 
-  /* Open the output stream */
-
   if (outpath != NULL)
     {
-      /* Create parent directory if it does not exist */
+      char *dir;
+      char *last;
 
-      char *dir  = strdup(outpath);
-      char *last = strrchr(dir, '/');
+      dir  = strdup(outpath);
+      last = strrchr(dir, '/');
 
       if (last != NULL && last != dir)
         {
@@ -625,6 +759,7 @@ int main(int argc, char **argv)
       out = fopen(outpath, "w");
       if (out == NULL)
         {
+          fputs(MKPASSWD_NL, stderr);
           fprintf(stderr, "mkpasswd: cannot open output file '%s': %s\n",
                   outpath, strerror(errno));
           return 1;
@@ -635,12 +770,6 @@ int main(int argc, char **argv)
       out = stdout;
     }
 
-  /* Write the passwd entry.
-   * Format: username:encrypted_hash:uid:gid:home
-   * This matches the format expected by apps/fsutils/passwd/passwd_find.c
-   * and the existing NuttX /etc/passwd files.
-   */
-
   fprintf(out, "%s:%s:%d:%d:%s\n", user, encrypted, uid, gid, home);
 
   if (outpath != NULL)
diff --git a/tools/passwd_keys.mk b/tools/passwd_keys.mk
index e91e945fa38..2042c19653d 100644
--- a/tools/passwd_keys.mk
+++ b/tools/passwd_keys.mk
@@ -3,13 +3,9 @@
 #
 # SPDX-License-Identifier: Apache-2.0
 #
-# Passwd / TEA-key validation and generation.  Included from the top-level
-# Makefile immediately after .config is loaded, BEFORE tools/Unix.mk builds
-# include/nuttx/config.h.  This ordering guarantees that freshly generated
-# keys are present in .config when config.h is created, so the firmware and
-# mkpasswd always agree on the same key values in a single make invocation.
-#
-# Board.mk only consumes CONFIG_FSUTILS_PASSWD_KEY1..4 in the ROMFS recipe.
+# ROMFS password validation.  Included from the top-level Makefile
+# immediately after .config is loaded, BEFORE tools/Unix.mk builds
+# include/nuttx/config.h.
 ############################################################################
 
 TOPDIR ?= .
@@ -28,6 +24,30 @@ else
 _PASSWD_ENFORCE := $(if $(filter-out $(PASSWD_SKIP_GOALS),$(MAKECMDGOALS)),y,)
 endif
 
+ifeq ($(_PASSWD_ENFORCE),y)
+
+# Reject removed fixed-login symbols left in stale .config or defconfig files.
+ifneq ($(shell grep -c '^CONFIG_NSH_LOGIN_FIXED=y' $(TOPDIR)/.config 
2>/dev/null),0)
+$(error CONFIG_NSH_LOGIN_FIXED was removed. Enable CONFIG_FSUTILS_PASSWD and 
CONFIG_NSH_LOGIN_PASSWD, or use CONFIG_NSH_LOGIN_PLATFORM with 
platform_user_verify().)
+endif
+ifneq ($(shell grep -c '^CONFIG_NSH_LOGIN_PASSWORD=' $(TOPDIR)/.config 
2>/dev/null),0)
+$(error CONFIG_NSH_LOGIN_PASSWORD was removed. Set 
CONFIG_BOARD_ETC_ROMFS_PASSWD_PASSWORD in menuconfig or export 
NUTTX_ROMFS_PASSWD_PASSWORD.)
+endif
+
+ifeq ($(CONFIG_NSH_CONSOLE_LOGIN),y)
+ifeq ($(CONFIG_FSUTILS_PASSWD),)
+$(error NSH console login requires CONFIG_FSUTILS_PASSWD. Fixed login was 
removed; enable password file support and CONFIG_NSH_LOGIN_PASSWD.)
+endif
+endif
+
+ifeq ($(CONFIG_NSH_TELNET_LOGIN),y)
+ifeq ($(CONFIG_FSUTILS_PASSWD),)
+$(error NSH telnet login requires CONFIG_FSUTILS_PASSWD. Fixed login was 
removed; enable password file support and CONFIG_NSH_LOGIN_PASSWD.)
+endif
+endif
+
+endif
+
 ifeq ($(CONFIG_BOARD_ETC_ROMFS_PASSWD_ENABLE),y)
 ifeq ($(_PASSWD_ENFORCE),y)
 
@@ -35,43 +55,24 @@ ifeq ($(_PASSWD_ENFORCE),y)
 $(shell $(TOPDIR)/tools/update_romfs_password.sh $(TOPDIR)/.config >/dev/null 
2>&1)
 include $(TOPDIR)/.config
 
-# --- password check ---
 ifeq ($(strip $(patsubst "%",%,$(CONFIG_BOARD_ETC_ROMFS_PASSWD_PASSWORD))),)
+_PASSWD_HAS_TTY := $(shell test -r /dev/tty && test -w /dev/tty && echo 1)
+ifneq ($(_PASSWD_HAS_TTY),1)
 $(info )
-$(info   BUILD ERROR: Admin password not set.)
+$(info   BUILD ERROR: Root password not set.)
 $(info )
 $(info   Run make menuconfig and set:)
-$(info     Board Selection -> Auto-generate /etc/passwd -> Admin password)
+$(info     Board Selection -> Auto-generate /etc/passwd -> Root password)
 $(info )
-$(info   For TEA keys, either enable random generation in the same menu,)
-$(info   or set CONFIG_FSUTILS_PASSWD_KEY1..4 under Application Configuration)
-$(info   -> File System Utilities -> Password file support.)
+$(info   For CI or scripted builds, export NUTTX_ROMFS_PASSWD_PASSWORD)
+$(info   (see tools/update_romfs_password.sh).)
 $(info )
-$(info   Password and keys are not saved in defconfig.)
+$(info   Password is not saved in defconfig.)
 $(info )
 $(error Aborting: CONFIG_BOARD_ETC_ROMFS_PASSWD_PASSWORD is not set)
 endif
-
-# --- TEA key check / generation ---
-_PASSWD_KEYS_NEED_SETUP := $(shell \
-  $(TOPDIR)/tools/check_passwd_keys.sh $(TOPDIR)/.config 2>/dev/null)
-
-ifneq ($(_PASSWD_KEYS_NEED_SETUP),no)
-ifeq ($(CONFIG_BOARD_ETC_ROMFS_PASSWD_RANDOMIZE_KEYS),y)
-$(shell $(TOPDIR)/tools/gen_passwd_keys.sh $(TOPDIR)/.config >/dev/null)
-include $(TOPDIR)/.config
-else
-$(info )
-$(info   BUILD ERROR: TEA encryption keys not configured.)
-$(info )
-$(info   Run make menuconfig and either:)
-$(info     - enable Generate random TEA keys automatically, or)
-$(info     - set CONFIG_FSUTILS_PASSWD_KEY1..4 under Application Configuration)
-$(info       -> File System Utilities -> Password file support)
-$(info )
-$(error Aborting: CONFIG_FSUTILS_PASSWD_KEY1..4 must be set to non-default 
values)
-endif
-endif
+# Interactive builds: board_romfs_mkpasswd.sh / promptpasswd.sh will prompt.
 endif
 
 endif
+endif
diff --git a/tools/promptpasswd.sh b/tools/promptpasswd.sh
new file mode 100755
index 00000000000..bb4f24fa19d
--- /dev/null
+++ b/tools/promptpasswd.sh
@@ -0,0 +1,180 @@
+#!/usr/bin/env bash
+# tools/promptpasswd.sh
+#
+# 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.
+#
+# Prompt for a Kconfig password when it is unset or invalid.  If no
+# terminal is available, print an error and exit.
+#
+# Usage:
+#   promptpasswd.sh --min <n> [--config <symbol>] [--config-file <file>]
+#                   [--update-config] [--prompt <text>] [--output-file <file>]
+
+set -e
+
+MIN=8
+VALUE=""
+PROMPT="Password: "
+CONFIG_SYMBOL=""
+CONFIG_FILE=".config"
+UPDATE_CONFIG=0
+OUTPUT_FILE=""
+
+while [ $# -gt 0 ]; do
+  case "$1" in
+    --min)
+      MIN=$2
+      shift 2
+      ;;
+    --value)
+      VALUE=$2
+      shift 2
+      ;;
+    --prompt)
+      PROMPT=$2
+      shift 2
+      ;;
+    --config)
+      CONFIG_SYMBOL=$2
+      shift 2
+      ;;
+    --config-file)
+      CONFIG_FILE=$2
+      shift 2
+      ;;
+    --update-config)
+      UPDATE_CONFIG=1
+      shift
+      ;;
+    --output-file)
+      OUTPUT_FILE=$2
+      shift 2
+      ;;
+    *)
+      echo "promptpasswd.sh: unknown option: $1" >&2
+      exit 1
+      ;;
+  esac
+done
+
+validate_password() {
+  local pw="$1"
+  local ok=0
+
+  if [ ${#pw} -lt "${MIN}" ]; then
+    echo "Error: password must be at least ${MIN} characters" >&2
+    ok=1
+  fi
+
+  if ! printf '%s' "$pw" | grep -q '[A-Z]'; then
+    echo "Error: password must contain at least one uppercase letter (A-Z)" >&2
+    ok=1
+  fi
+
+  if ! printf '%s' "$pw" | grep -q '[a-z]'; then
+    echo "Error: password must contain at least one lowercase letter (a-z)" >&2
+    ok=1
+  fi
+
+  if ! printf '%s' "$pw" | grep -q '[0-9]'; then
+    echo "Error: password must contain at least one digit (0-9)" >&2
+    ok=1
+  fi
+
+  if ! printf '%s' "$pw" | grep -q '[^a-zA-Z0-9]'; then
+    echo "Error: password must contain at least one special character" \
+         "(!@#\$%^&*()_+-=[]{}|;:,.<>?)" >&2
+    ok=1
+  fi
+
+  return "${ok}"
+}
+
+if [ -n "${CONFIG_SYMBOL}" ] && [ -z "${VALUE}" ] && [ -f "${CONFIG_FILE}" ]; 
then
+  VALUE=$(grep "^${CONFIG_SYMBOL}=" "${CONFIG_FILE}" 2>/dev/null | cut -d= 
-f2- | tr -d '"')
+fi
+
+if [ -n "${VALUE}" ] && validate_password "${VALUE}"; then
+  if [ -n "${OUTPUT_FILE}" ]; then
+    umask 077
+    printf '%s' "${VALUE}" > "${OUTPUT_FILE}"
+  else
+    printf '%s' "${VALUE}"
+  fi
+  exit 0
+fi
+
+# Make recipe shells are not connected to the terminal on stdin, so test 
/dev/tty
+# instead of [ -t 0 ] when deciding whether an interactive prompt is possible.
+
+INTERACTIVE=0
+if [ -r /dev/tty ] && [ -w /dev/tty ]; then
+  INTERACTIVE=1
+fi
+
+if [ "${INTERACTIVE}" -eq 0 ]; then
+  echo "" >&2
+  if [ -n "${CONFIG_SYMBOL}" ]; then
+    echo "ERROR: ${CONFIG_SYMBOL} must be at least ${MIN} characters and" >&2
+    echo "contain uppercase, lowercase, digit, and special character." >&2
+  else
+    echo "ERROR: Password must be at least ${MIN} characters and contain" >&2
+    echo "uppercase, lowercase, digit, and special character." >&2
+  fi
+  echo "Set it with 'make menuconfig' or edit .config, then rebuild." >&2
+  exit 1
+fi
+
+PASSWORD=""
+while true; do
+  printf '%s' "${PROMPT}" >/dev/tty
+  IFS= read -r -s PASSWORD </dev/tty
+  echo "" >/dev/tty
+  if ! validate_password "${PASSWORD}"; then
+    echo "Please try again." >&2
+    PASSWORD=""
+    continue
+  fi
+
+  while true; do
+    printf 'Confirm password: ' >/dev/tty
+    IFS= read -r -s PASSWORD2 </dev/tty
+    echo "" >/dev/tty
+    if [ "${PASSWORD}" = "${PASSWORD2}" ]; then
+      break
+    fi
+    echo "Passwords do not match. Please try again." >&2
+    PASSWORD=""
+    break
+  done
+
+  if [ -n "${PASSWORD}" ]; then
+    break
+  fi
+done
+
+if [ "${UPDATE_CONFIG}" -eq 1 ] && [ -n "${CONFIG_SYMBOL}" ]; then
+  kconfig-tweak --file "${CONFIG_FILE}" --set-str "${CONFIG_SYMBOL}" 
"${PASSWORD}"
+fi
+
+if [ -n "${OUTPUT_FILE}" ]; then
+  umask 077
+  printf '%s' "${PASSWORD}" > "${OUTPUT_FILE}"
+else
+  printf '%s' "${PASSWORD}"
+fi
diff --git a/tools/update_romfs_password.sh b/tools/update_romfs_password.sh
index 8b246094263..8e8be3ae42b 100755
--- a/tools/update_romfs_password.sh
+++ b/tools/update_romfs_password.sh
@@ -19,7 +19,7 @@
 # Usage:
 #   update_romfs_password.sh <path-to-.config>
 #
-# When CONFIG_BOARD_ETC_ROMFS_PASSWD_ENABLE=y and the admin password is not
+# When CONFIG_BOARD_ETC_ROMFS_PASSWD_ENABLE=y and the root password is not
 # set in .config, copy NUTTX_ROMFS_PASSWD_PASSWORD into .config.  This is the
 # supported way to supply build-time credentials that must not live in 
defconfig
 # (CI, automation, local scripts).  No-op when the password is already set or

Reply via email to