Skip to content

Commit d971461

Browse files
committed
feat: Add ARGON2ID Hash Algorithm
1 parent cde7c16 commit d971461

6 files changed

Lines changed: 105 additions & 7 deletions

File tree

authme-core/src/main/java/fr/xephi/authme/datasource/converter/LibreLoginConverter.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
* <b>Algorithm mapping:</b>
4343
* <ul>
4444
* <li>{@code BCrypt-2A} → configure AuthMe with {@code passwordHash: BCRYPT}</li>
45-
* <li>{@code Argon2-ID} → configure AuthMe with {@code passwordHash: ARGON2}</li>
45+
* <li>{@code Argon2-ID} → configure AuthMe with {@code passwordHash: ARGON2ID}</li>
4646
* <li>{@code SHA-256} → configure AuthMe with {@code passwordHash: SHA256}</li>
4747
* <li>{@code SHA-512} → configure AuthMe with {@code passwordHash: DOUBLE_SHA512}</li>
4848
* <li>{@code LOGIT-SHA-256} → configure AuthMe with {@code passwordHash: SALTEDSHA256}</li>

authme-core/src/main/java/fr/xephi/authme/datasource/converter/NLoginConverter.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
* <p>
3636
* nLogin reuses AuthMe's password hash formats for BCrypt, SHA-256 and SHA-512, so hashes are
3737
* copied as-is. Configure AuthMe's {@code passwordHash} to match nLogin's algorithm (default:
38-
* {@code BCRYPT}). Argon2 hashes are also supported via {@code ARGON2}.
38+
* {@code BCRYPT}). Argon2 (argon2id) hashes are supported via {@code ARGON2ID}.
3939
*/
4040
public class NLoginConverter implements Converter {
4141

authme-core/src/main/java/fr/xephi/authme/security/HashAlgorithm.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
public enum HashAlgorithm {
99

1010
ARGON2(fr.xephi.authme.security.crypts.Argon2.class),
11+
ARGON2ID(fr.xephi.authme.security.crypts.Argon2Id.class),
1112
BCRYPT(fr.xephi.authme.security.crypts.BCrypt.class),
1213
BCRYPT2Y(fr.xephi.authme.security.crypts.BCrypt2y.class),
1314
CMW(fr.xephi.authme.security.crypts.CmwCrypt.class),
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package fr.xephi.authme.security.crypts;
2+
3+
import fr.xephi.authme.security.crypts.description.HasSalt;
4+
import fr.xephi.authme.security.crypts.description.Recommendation;
5+
import fr.xephi.authme.security.crypts.description.SaltType;
6+
import fr.xephi.authme.security.crypts.description.Usage;
7+
import org.bouncycastle.crypto.generators.Argon2BytesGenerator;
8+
import org.bouncycastle.crypto.params.Argon2Parameters;
9+
10+
import java.security.MessageDigest;
11+
import java.security.SecureRandom;
12+
import java.util.Base64;
13+
14+
@Recommendation(Usage.RECOMMENDED)
15+
@HasSalt(value = SaltType.TEXT, length = 16)
16+
// Note: Argon2id is actually a salted algorithm but salt generation is handled internally
17+
// and isn't exposed to the outside, so we treat it as an unsalted implementation
18+
public class Argon2Id extends UnsaltedMethod {
19+
20+
private static final int ITERATIONS = 2;
21+
private static final int MEMORY_KB = 65536;
22+
private static final int PARALLELISM = 1;
23+
private static final int SALT_BYTES = 16;
24+
private static final int HASH_BYTES = 32;
25+
26+
private final SecureRandom random = new SecureRandom();
27+
28+
@Override
29+
public String computeHash(String password) {
30+
byte[] salt = new byte[SALT_BYTES];
31+
random.nextBytes(salt);
32+
byte[] hash = derive(password.toCharArray(), salt, ITERATIONS, MEMORY_KB, PARALLELISM, HASH_BYTES);
33+
Base64.Encoder enc = Base64.getEncoder().withoutPadding();
34+
return "$argon2id$v=19$m=" + MEMORY_KB + ",t=" + ITERATIONS + ",p=" + PARALLELISM
35+
+ "$" + enc.encodeToString(salt)
36+
+ "$" + enc.encodeToString(hash);
37+
}
38+
39+
@Override
40+
public boolean comparePassword(String password, HashedPassword hashedPassword, String name) {
41+
String[] parts = hashedPassword.getHash().split("\\$");
42+
// Expected: ["", "argon2id", "v=19", "m=65536,t=2,p=1", "<salt_b64>", "<hash_b64>"]
43+
if (parts.length != 6 || !"argon2id".equals(parts[1])) {
44+
return false;
45+
}
46+
try {
47+
int[] params = parseParams(parts[3]); // m, t, p
48+
byte[] salt = decodeNoPadding(parts[4]);
49+
byte[] expected = decodeNoPadding(parts[5]);
50+
byte[] computed = derive(password.toCharArray(), salt, params[1], params[0], params[2], expected.length);
51+
return MessageDigest.isEqual(computed, expected);
52+
} catch (IllegalArgumentException e) {
53+
return false;
54+
}
55+
}
56+
57+
private static byte[] derive(char[] password, byte[] salt, int iterations, int memoryKb, int parallelism, int hashLen) {
58+
Argon2Parameters params = new Argon2Parameters.Builder(Argon2Parameters.ARGON2_id)
59+
.withVersion(Argon2Parameters.ARGON2_VERSION_13)
60+
.withIterations(iterations)
61+
.withMemoryAsKB(memoryKb)
62+
.withParallelism(parallelism)
63+
.withSalt(salt)
64+
.build();
65+
Argon2BytesGenerator generator = new Argon2BytesGenerator();
66+
generator.init(params);
67+
byte[] result = new byte[hashLen];
68+
generator.generateBytes(password, result);
69+
return result;
70+
}
71+
72+
/** Parses "m=65536,t=2,p=1" → [m, t, p]. */
73+
private static int[] parseParams(String paramStr) {
74+
int[] result = new int[3];
75+
for (String kv : paramStr.split(",")) {
76+
String[] pair = kv.split("=");
77+
int v = Integer.parseInt(pair[1]);
78+
switch (pair[0]) {
79+
case "m": result[0] = v; break;
80+
case "t": result[1] = v; break;
81+
case "p": result[2] = v; break;
82+
}
83+
}
84+
return result;
85+
}
86+
87+
/** Decodes base64 without padding (PHC format omits '='). */
88+
private static byte[] decodeNoPadding(String s) {
89+
switch (s.length() % 4) {
90+
case 2: s += "=="; break;
91+
case 3: s += "="; break;
92+
default: break;
93+
}
94+
return Base64.getDecoder().decode(s);
95+
}
96+
}

docs/converters.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ Migrates accounts from the **LibreLogin** plugin.
4646
| LibreLogin algorithm | AuthMe `passwordHash` |
4747
|---|---|
4848
| `BCrypt-2A` (default) | `BCRYPT` |
49-
| `Argon2-ID` | `ARGON2` |
49+
| `Argon2-ID` | `ARGON2ID` |
5050
| `SHA-256` | `SHA256` |
5151
| `SHA-512` | `DOUBLE_SHA512` |
5252
| `LOGIT-SHA-256` | `SALTEDSHA256` |
@@ -80,7 +80,7 @@ Migrates accounts from the **NexAuth** plugin (a fork of LibreLogin).
8080
| NexAuth algorithm | AuthMe `passwordHash` |
8181
|---|---|
8282
| `BCrypt-2A` (default) | `BCRYPT` |
83-
| `Argon-2ID` | `ARGON2` |
83+
| `Argon-2ID` | `ARGON2ID` |
8484
| `SHA-256` | `SHA256` |
8585
| `SHA-512` | `DOUBLE_SHA512` |
8686
| `LOGIT-SHA-256` | `SALTEDSHA256` |
@@ -139,7 +139,7 @@ Migrates accounts from the **nLogin** plugin.
139139
|---|---|
140140
| BCrypt (default) | `BCRYPT` or `BCRYPT2Y` |
141141
| SHA-256 (`$SHA$…`) | `SHA256` |
142-
| Argon2 | `ARGON2` |
142+
| Argon2 (argon2id) | `ARGON2ID` |
143143

144144
**Notes:**
145145
- Email addresses and last-login timestamps are migrated.

docs/hash_algorithms.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<!-- AUTO-GENERATED FILE! Do not edit this directly -->
2-
<!-- File auto-generated on Wed May 06 10:48:06 CEST 2026. See authme-tools/src/test/java/tools/docs/hashmethods/hash_algorithms.tpl.md -->
2+
<!-- File auto-generated on Sat May 23 08:31:23 CEST 2026. See authme-tools/src/test/java/tools/docs/hashmethods/hash_algorithms.tpl.md -->
33

44
## Hash Algorithms
55
AuthMe supports the following hash algorithms for storing your passwords safely.
@@ -8,6 +8,7 @@ AuthMe supports the following hash algorithms for storing your passwords safely.
88
Algorithm | Recommendation | Hash length | ASCII | | Salt type | Length | Separate?
99
--------- | -------------- | ----------- | ----- | --- | --------- | ------ | ---------
1010
ARGON2 | Recommended | 96 | | | Text | 16 |
11+
ARGON2ID | Recommended | 97 | | | Text | 16 |
1112
BCRYPT | Recommended | 60 | | | Text | 22 |
1213
BCRYPT2Y | Recommended | 60 | | | Text | 22 |
1314
CMW | Do not use | 32 | | | None | |
@@ -82,4 +83,4 @@ or bad.
8283

8384
---
8485

85-
This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Wed May 06 10:48:06 CEST 2026
86+
This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Sat May 23 08:31:23 CEST 2026

0 commit comments

Comments
 (0)