HashPassword.patch
| 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); |