=== modified file 'src/com/goldencode/p2j/main/CreateAccountTask.java'
--- old/src/com/goldencode/p2j/main/CreateAccountTask.java	2026-05-26 19:40:32 +0000
+++ new/src/com/goldencode/p2j/main/CreateAccountTask.java	2026-08-13 11:29:47 +0000
@@ -155,6 +155,12 @@
    
    /** Stores the temporary account details upon successful creation. */
    private TemporaryAccount account = null;
+
+   /** Definition of the user to create, prepared by the posting thread. */
+   private TempUserDef user = null;
+
+   /** Clear text password of the user to create, prepared by the posting thread. */
+   private String password = null;
    
    /** Static ID generator shared across all temporary accounts and groups. */
    private static TempIdManager idGenerator;
@@ -174,15 +180,30 @@
    }
    
    /**
-    * Constructs a new task set to <b>creation mode</b>.
-    * <p>
-    * When executed, this task will generate and add a new temporary user.
-    * </p>
+    * Constructs a new task set to <b>creation mode</b>.  The credentials of the new user are
+    * generated and hashed here, on the posting thread, so that the deliberately expensive
+    * password hashing is not serialized on the single worker thread.
     */
    public CreateAccountTask()
    {
       this.create  = true;
       this.account = null;
+
+      // Generate random values for account
+      String subject = RandomWordGenerator.create(GEN_LENGTH, GEN_DIGITS, 0);
+
+      password = RandomWordGenerator.create(GEN_LENGTH, GEN_DIGITS, GEN_SYMBOLS);
+
+      // Fill account structure
+      user = new TempUserDef();
+      user.enabled = true;
+      user.protect = true;
+      user.person = "chui";
+      user.subjectId = subject;
+      user.password = HashPassword.hashPassword(password, HashPassword.EPHEMERAL_ITERATIONS);
+      user.mode = 1;
+      user.groups = new String[] { TEMP_GROUP };
+      user.orderId = idGenerator.getNextId();
    }
    
    /**
@@ -248,9 +269,7 @@
     * This method performs the following steps:
     * <ol>
     *   <li>Ensures the temporary group exists.</li>
-    *   <li>Generates a random subject ID (username) and password.</li>
-    *   <li>Creates a {@code UserDef} populated with these credentials and default settings.</li>
-    *   <li>Persists the temporary user via the Admin Server.</li>
+    *   <li>Persists the temporary user prepared by the constructor via the Admin Server.</li>
     *   <li>Caches the resulting credentials in the {@code account} field.</li>
     *   <li>Triggers a cache refresh in the {@code SecurityManager}.</li>
     * </ol>
@@ -260,20 +279,6 @@
    {
       createGroupIfNotExists();
       
-      // Generate random values for account
-      String subject = RandomWordGenerator.create(GEN_LENGTH, GEN_DIGITS, 0);
-      String password = RandomWordGenerator.create(GEN_LENGTH, GEN_DIGITS, GEN_SYMBOLS);
-      // Fill account structure
-      TempUserDef user = new TempUserDef();
-      user.enabled = true;
-      user.protect = true;
-      user.person = "chui";
-      user.subjectId = subject;
-      user.password = HashPassword.hashPassword(password);
-      user.mode = 1;
-      user.groups = new String[] { TEMP_GROUP };
-      user.orderId = idGenerator.getNextId();
-      
       synchronized (mutex)
       {         
          // Create temporary account
@@ -293,6 +298,11 @@
                return;
             }
          }
+         else
+         {
+            // the ID was allocated by the constructor and is not in use, make it available again
+            idGenerator.recycleId(user.orderId);
+         }
       }
    }
    

=== modified file 'src/com/goldencode/p2j/security/HashPassword.java'
--- old/src/com/goldencode/p2j/security/HashPassword.java	2026-07-21 08:10:16 +0000
+++ new/src/com/goldencode/p2j/security/HashPassword.java	2026-08-13 10:50:43 +0000
@@ -104,6 +104,17 @@
    /** PBKDF2 iteration count. */
    private static final int PBKDF2_ITERATIONS = 210000;
 
+   /**
+    * PBKDF2 iteration count for ephemeral, high entropy secrets.
+    * <p>
+    * The full iteration count exists to make offline cracking of user chosen passwords expensive.
+    * It buys nothing for a randomly generated, single use, in-memory credential, while costing
+    * hundreds of milliseconds on both the creation and the authentication path.  Only use this
+    * for secrets which come from {@link RandomWordGenerator} and never outlive a session.
+    * </p>
+    */
+   public static final int EPHEMERAL_ITERATIONS = 1000;
+
    /** Salt length for new password hashes, in bytes. */
    private static final int SALT_LENGTH = 16;
 
@@ -124,6 +135,26 @@
     */
    public static byte[] hashPassword(String plain)
    {
+      return hashPassword(plain, PBKDF2_ITERATIONS);
+   }
+
+   /**
+    * Hashes string into a digest used as an internal password represenation, using the given
+    * iteration count.  The count is stored in the hash, so {@link #verifyPassword} needs no
+    * knowledge of it.
+    *
+    * @param     plain
+    *            plain text password
+    * @param     iterations
+    *            PBKDF2 iteration count;  must be greater than 0.  Anything below
+    *            {@link #PBKDF2_ITERATIONS} is only appropriate for a high entropy secret, see
+    *            {@link #EPHEMERAL_ITERATIONS}.
+    *
+    * @return    array of bytes representing the hashed string or
+    *            <code>null</code>, if the PBKDF2 algorithm is unavailable.
+    */
+   public static byte[] hashPassword(String plain, int iterations)
+   {
       if (plain == null)
       {
          return null;
@@ -132,7 +163,7 @@
       byte[] salt = new byte[SALT_LENGTH];
       RANDOM.nextBytes(salt);
 
-      byte[] hash = pbkdf2(plain, salt, PBKDF2_ITERATIONS, DERIVED_KEY_LENGTH);
+      byte[] hash = pbkdf2(plain, salt, iterations, DERIVED_KEY_LENGTH);
       if (hash == null)
       {
          return null;
@@ -141,7 +172,7 @@
       ByteBuffer buffer = ByteBuffer.allocate(CURRENT_MAGIC.length + Integer.BYTES * 3 +
                                               salt.length + hash.length);
       buffer.put(CURRENT_MAGIC);
-      buffer.putInt(PBKDF2_ITERATIONS);
+      buffer.putInt(iterations);
       buffer.putInt(salt.length);
       buffer.putInt(hash.length);
       buffer.put(salt);


