Project

General

Profile

HashPassword.patch

Șerban Bursuc, 08/14/2026 08:29 AM

Download (6.16 KB)

View differences:

new/src/com/goldencode/p2j/main/CreateAccountTask.java 2026-08-13 11:29:47 +0000
155 155
   
156 156
   /** Stores the temporary account details upon successful creation. */
157 157
   private TemporaryAccount account = null;
158

  
159
   /** Definition of the user to create, prepared by the posting thread. */
160
   private TempUserDef user = null;
161

  
162
   /** Clear text password of the user to create, prepared by the posting thread. */
163
   private String password = null;
158 164
   
159 165
   /** Static ID generator shared across all temporary accounts and groups. */
160 166
   private static TempIdManager idGenerator;
......
174 180
   }
175 181
   
176 182
   /**
177
    * Constructs a new task set to <b>creation mode</b>.
178
    * <p>
179
    * When executed, this task will generate and add a new temporary user.
180
    * </p>
183
    * Constructs a new task set to <b>creation mode</b>.  The credentials of the new user are
184
    * generated and hashed here, on the posting thread, so that the deliberately expensive
185
    * password hashing is not serialized on the single worker thread.
181 186
    */
182 187
   public CreateAccountTask()
183 188
   {
184 189
      this.create  = true;
185 190
      this.account = null;
191

  
192
      // Generate random values for account
193
      String subject = RandomWordGenerator.create(GEN_LENGTH, GEN_DIGITS, 0);
194

  
195
      password = RandomWordGenerator.create(GEN_LENGTH, GEN_DIGITS, GEN_SYMBOLS);
196

  
197
      // Fill account structure
198
      user = new TempUserDef();
199
      user.enabled = true;
200
      user.protect = true;
201
      user.person = "chui";
202
      user.subjectId = subject;
203
      user.password = HashPassword.hashPassword(password, HashPassword.EPHEMERAL_ITERATIONS);
204
      user.mode = 1;
205
      user.groups = new String[] { TEMP_GROUP };
206
      user.orderId = idGenerator.getNextId();
186 207
   }
187 208
   
188 209
   /**
......
248 269
    * This method performs the following steps:
249 270
    * <ol>
250 271
    *   <li>Ensures the temporary group exists.</li>
251
    *   <li>Generates a random subject ID (username) and password.</li>
252
    *   <li>Creates a {@code UserDef} populated with these credentials and default settings.</li>
253
    *   <li>Persists the temporary user via the Admin Server.</li>
272
    *   <li>Persists the temporary user prepared by the constructor via the Admin Server.</li>
254 273
    *   <li>Caches the resulting credentials in the {@code account} field.</li>
255 274
    *   <li>Triggers a cache refresh in the {@code SecurityManager}.</li>
256 275
    * </ol>
......
260 279
   {
261 280
      createGroupIfNotExists();
262 281
      
263
      // Generate random values for account
264
      String subject = RandomWordGenerator.create(GEN_LENGTH, GEN_DIGITS, 0);
265
      String password = RandomWordGenerator.create(GEN_LENGTH, GEN_DIGITS, GEN_SYMBOLS);
266
      // Fill account structure
267
      TempUserDef user = new TempUserDef();
268
      user.enabled = true;
269
      user.protect = true;
270
      user.person = "chui";
271
      user.subjectId = subject;
272
      user.password = HashPassword.hashPassword(password);
273
      user.mode = 1;
274
      user.groups = new String[] { TEMP_GROUP };
275
      user.orderId = idGenerator.getNextId();
276
      
277 282
      synchronized (mutex)
278 283
      {         
279 284
         // Create temporary account
......
293 298
               return;
294 299
            }
295 300
         }
301
         else
302
         {
303
            // the ID was allocated by the constructor and is not in use, make it available again
304
            idGenerator.recycleId(user.orderId);
305
         }
296 306
      }
297 307
   }
298 308
   
new/src/com/goldencode/p2j/security/HashPassword.java 2026-08-13 10:50:43 +0000
104 104
   /** PBKDF2 iteration count. */
105 105
   private static final int PBKDF2_ITERATIONS = 210000;
106 106

  
107
   /**
108
    * PBKDF2 iteration count for ephemeral, high entropy secrets.
109
    * <p>
110
    * The full iteration count exists to make offline cracking of user chosen passwords expensive.
111
    * It buys nothing for a randomly generated, single use, in-memory credential, while costing
112
    * hundreds of milliseconds on both the creation and the authentication path.  Only use this
113
    * for secrets which come from {@link RandomWordGenerator} and never outlive a session.
114
    * </p>
115
    */
116
   public static final int EPHEMERAL_ITERATIONS = 1000;
117

  
107 118
   /** Salt length for new password hashes, in bytes. */
108 119
   private static final int SALT_LENGTH = 16;
109 120

  
......
124 135
    */
125 136
   public static byte[] hashPassword(String plain)
126 137
   {
138
      return hashPassword(plain, PBKDF2_ITERATIONS);
139
   }
140

  
141
   /**
142
    * Hashes string into a digest used as an internal password represenation, using the given
143
    * iteration count.  The count is stored in the hash, so {@link #verifyPassword} needs no
144
    * knowledge of it.
145
    *
146
    * @param     plain
147
    *            plain text password
148
    * @param     iterations
149
    *            PBKDF2 iteration count;  must be greater than 0.  Anything below
150
    *            {@link #PBKDF2_ITERATIONS} is only appropriate for a high entropy secret, see
151
    *            {@link #EPHEMERAL_ITERATIONS}.
152
    *
153
    * @return    array of bytes representing the hashed string or
154
    *            <code>null</code>, if the PBKDF2 algorithm is unavailable.
155
    */
156
   public static byte[] hashPassword(String plain, int iterations)
157
   {
127 158
      if (plain == null)
128 159
      {
129 160
         return null;
......
132 163
      byte[] salt = new byte[SALT_LENGTH];
133 164
      RANDOM.nextBytes(salt);
134 165

  
135
      byte[] hash = pbkdf2(plain, salt, PBKDF2_ITERATIONS, DERIVED_KEY_LENGTH);
166
      byte[] hash = pbkdf2(plain, salt, iterations, DERIVED_KEY_LENGTH);
136 167
      if (hash == null)
137 168
      {
138 169
         return null;
......
141 172
      ByteBuffer buffer = ByteBuffer.allocate(CURRENT_MAGIC.length + Integer.BYTES * 3 +
142 173
                                              salt.length + hash.length);
143 174
      buffer.put(CURRENT_MAGIC);
144
      buffer.putInt(PBKDF2_ITERATIONS);
175
      buffer.putInt(iterations);
145 176
      buffer.putInt(salt.length);
146 177
      buffer.putInt(hash.length);
147 178
      buffer.put(salt);