Skip to content
This repository has been archived by the owner on Oct 20, 2024. It is now read-only.

Commit

Permalink
java: Add argon2
Browse files Browse the repository at this point in the history
  • Loading branch information
M3DZIK committed Sep 25, 2023
1 parent 4eedba9 commit beee028
Show file tree
Hide file tree
Showing 10 changed files with 378 additions and 9 deletions.
2 changes: 1 addition & 1 deletion java/src/main/java/dev/medzik/libcrypto/Aes.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import java.security.NoSuchAlgorithmException;
import java.security.spec.AlgorithmParameterSpec;

public class Aes {
public final class Aes {
private static final String ALGORITHM = "AES";

public static final AesType GCM = AesType.GCM;
Expand Down
139 changes: 138 additions & 1 deletion java/src/main/java/dev/medzik/libcrypto/Argon2.java
Original file line number Diff line number Diff line change
@@ -1,4 +1,141 @@
package dev.medzik.libcrypto;

public class Argon2 {
import com.password4j.Argon2Function;
import com.password4j.Hash;
import com.password4j.Password;

public final class Argon2 {
private final int hashLength;
private final int parallelism;
private final int memory;
private final int iterations;
private final Argon2Type type;
private final int version;

public static final int ARGON2_VERSION_10 = 0x10;
public static final int ARGON2_VERSION_13 = 0x13;

public final static class Builder {
private int hashLength;
private int parallelism;
private int memory;
private int iterations;
private Argon2Type type;
private int version;

public Builder() {
this.hashLength = 32;
this.parallelism = 1;
this.memory = 65536;
this.iterations = 3;
this.type = Argon2Type.ID;
this.version = ARGON2_VERSION_13;
}

public Builder setHashLength(int hashLength) {
this.hashLength = hashLength;
return this;
}

public Builder setParallelism(int parallelism) {
this.parallelism = parallelism;
return this;
}

public Builder setMemory(int memory) {
this.memory = memory;
return this;
}

public Builder setIterations(int iterations) {
this.iterations = iterations;
return this;
}

public Builder setType(Argon2Type type) {
this.type = type;
return this;
}

public Builder setVersion(int version) {
this.version = version;
return this;
}

public Argon2 build() {
return new Argon2(hashLength, parallelism, memory, iterations, type, version);
}
}

/**
* Creates a new instance of Argon2 hasher with the given parameters.
* @param hashLength length of the hash
* @param parallelism number of parallel threads to use when hashing
* @param memory amount of memory to use when hashing
* @param iterations number of iterations to use when hashing
*/
public Argon2(int hashLength, int parallelism, int memory, int iterations) {
this.hashLength = hashLength;
this.parallelism = parallelism;
this.memory = memory;
this.iterations = iterations;
this.type = Argon2Type.ID;
this.version = ARGON2_VERSION_13;
}

/**
* Creates a new instance of Argon2 hasher with the given parameters.
* @param hashLength length of the hash
* @param parallelism number of parallel threads to use when hashing
* @param memory amount of memory to use when hashing
* @param iterations number of iterations to use when hashing
* @param type type argon2 to use (i, d, id) {@link Argon2Type}
* @param version hasher version
*/
public Argon2(int hashLength, int parallelism, int memory, int iterations, Argon2Type type, int version) {
this.hashLength = hashLength;
this.parallelism = parallelism;
this.memory = memory;
this.iterations = iterations;
this.type = type;
this.version = version;
}

/** Hashes the given password. */
public Argon2Hash hash(String password, byte[] salt) {
Argon2Function instance = Argon2Function.getInstance(
memory,
iterations,
parallelism,
hashLength,
type.toPassword4jType(),
version
);

Hash hash = Password
.hash(password)
.addSalt(salt)
.with(instance);

return Argon2EncodingUtils.decode(hash.getResult());
}

/** Verifies a password against a hash. */
public static boolean verify(CharSequence rawPassword, String encodedPassword) {
// decode the `encodedPassword` to get the parameters
Argon2Hash argon2Hash = Argon2EncodingUtils.decode(encodedPassword);

Argon2Function instance = Argon2Function.getInstance(
argon2Hash.getMemory(),
argon2Hash.getIterations(),
argon2Hash.getParallelism(),
argon2Hash.getHashLength(),
argon2Hash.getType().toPassword4jType(),
argon2Hash.getVersion()
);

return Password
.check(rawPassword, encodedPassword)
.with(instance);
}
}
104 changes: 104 additions & 0 deletions java/src/main/java/dev/medzik/libcrypto/Argon2EncodingUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package dev.medzik.libcrypto;

import java.util.Base64;

/**
* Utility class for encoding and decoding argon2 hashes.
*/
public final class Argon2EncodingUtils {
private static final Base64.Encoder b64encoder = Base64.getEncoder().withoutPadding();
private static final Base64.Decoder b64decoder = Base64.getDecoder();

/**
* Encodes the given hash and parameters to a string.
* @param hash hash to encode
* @return encoded hash in argon2 format
*/
public static String encode(Argon2Hash hash) {
StringBuilder sb = new StringBuilder();

switch (hash.getType()) {
case D:
sb.append("$argon2d");
break;
case I:
sb.append("$argon2i");
break;
case ID:
sb.append("$argon2id");
break;
}

sb.append("$v=").append(hash.getVersion()).append("$m=").append(hash.getMemory())
.append(",t=").append(hash.getIterations()).append(",p=").append(hash.getParallelism());

if (hash.getSalt() != null) {
sb.append("$").append(b64encoder.encodeToString(hash.getSalt()));
}

sb.append("$").append(b64encoder.encodeToString(hash.getHash()));
return sb.toString();
}

/**
* Decodes the given Argon2 encoded hash.
* @param encodedHash encoded hash in argon2 format
* @return {@link Argon2Hash}
*/
public static Argon2Hash decode(String encodedHash) {
String[] parts = encodedHash.split("\\$");
if (parts.length < 4) {
throw new IllegalArgumentException("Invalid encoded Argon2-hash");
}

int currentPart = 1;
Argon2Type type;
switch (parts[currentPart++]) {
case "argon2d":
type = Argon2Type.D;
break;
case "argon2i":
type = Argon2Type.I;
break;
case "argon2id":
type = Argon2Type.ID;
break;
default:
throw new IllegalArgumentException("Invalid algorithm type: " + parts[0]);
}

int version;
if (parts[currentPart].startsWith("v=")) {
version = Integer.parseInt(parts[currentPart].substring(2));
currentPart++;
} else {
throw new IllegalArgumentException("Invalid version parameter");
}

String[] performanceParams = parts[currentPart++].split(",");

if (performanceParams.length != 3) {
throw new IllegalArgumentException("Amount of performance parameters invalid");
}

if (!performanceParams[0].startsWith("m=")) {
throw new IllegalArgumentException("Invalid memory parameter");
}

int memory = Integer.parseInt(performanceParams[0].substring(2));
if (!performanceParams[1].startsWith("t=")) {
throw new IllegalArgumentException("Invalid iterations parameter");
}

int iterations = Integer.parseInt(performanceParams[1].substring(2));
if (!performanceParams[2].startsWith("p=")) {
throw new IllegalArgumentException("Invalid parallelity parameter");
}

int parallelism = Integer.parseInt(performanceParams[2].substring(2));
byte[] salt = b64decoder.decode(parts[currentPart++]);
byte[] hash = b64decoder.decode(parts[currentPart]);

return new Argon2Hash(type, version, memory, iterations, parallelism, salt, hash);
}
}
76 changes: 76 additions & 0 deletions java/src/main/java/dev/medzik/libcrypto/Argon2Hash.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package dev.medzik.libcrypto;

/**
* Object representation of argon2 hash.
*/
public final class Argon2Hash {
private final Argon2Type type;
private final int version;
private final int memory;
private final int iterations;
private final int parallelism;

private final byte[] salt;
private final byte[] hash;

public Argon2Hash(Argon2Type type, int version, int memory, int iterations, int parallelism, byte[] salt, byte[] hash) {
this.type = type;
this.version = version;
this.memory = memory;
this.iterations = iterations;
this.parallelism = parallelism;
this.salt = salt;
this.hash = hash;
}

/** Returns the Argon2 type of this hash. */
public Argon2Type getType() {
return type;
}

/** Returns argon2 version. */
public int getVersion() {
return version;
}

/** Returns memory parameter of this hash. */
public int getMemory() {
return memory;
}

/** Returns iteration parameter of this hash. */
public int getIterations() {
return iterations;
}

/** Returns parallelism parameter of this hash. */
public int getParallelism() {
return parallelism;
}

/** Returns salt of this hash. */
public byte[] getSalt() {
return salt;
}

/** Returns hash. */
public byte[] getHash() {
return hash;
}

/** Returns the hash length. */
public int getHashLength() {
return hash.length;
}

/** Returns hash in encoded format. */
public String toArgon2String() {
return Argon2EncodingUtils.encode(this);
}

/** Returns hash in encoded format Same as {@link #toArgon2String()}. */
@Override
public String toString() {
return toArgon2String();
}
}
22 changes: 22 additions & 0 deletions java/src/main/java/dev/medzik/libcrypto/Argon2Type.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package dev.medzik.libcrypto;

import com.password4j.types.Argon2;

public enum Argon2Type {
D,
I,
ID;

public Argon2 toPassword4jType() {
switch (this) {
case D:
return Argon2.D;
case I:
return Argon2.I;
case ID:
return Argon2.ID;
default:
throw new IllegalStateException("Unexpected value: " + this);
}
}
}
2 changes: 1 addition & 1 deletion java/src/main/java/dev/medzik/libcrypto/Random.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import java.security.SecureRandom;

public class Random {
public final class Random {
/**
* Generate a random byte array.
* @param size length of salt slice in bytes
Expand Down
5 changes: 1 addition & 4 deletions java/src/main/java/dev/medzik/libcrypto/X25519.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,7 @@
import java.security.InvalidKeyException;
import java.util.Arrays;

/**
*
*/
public class X25519 {
public final class X25519 {
/**
* Generates a 32-byte private key for Curve25519.
* @return a 32-byte private key
Expand Down
2 changes: 1 addition & 1 deletion java/src/test/java/dev/medzik/libcrypto/AesTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

import static org.junit.jupiter.api.Assertions.assertEquals;

public class AesTests {
public final class AesTests {
@Test
public void testCBCEncryptAndDecrypt() throws DecoderException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException {
byte[] secretKey = Hex.decodeHex("82fd4cefd6efde36171900b469bae4e06863cb70f80b4e216e44eeb0cf30460b");
Expand Down
Loading

0 comments on commit beee028

Please sign in to comment.