loader = ServiceLoader.load(Jargon2Backend.class);
57 | for (Jargon2Backend loadedBackend : loader) {
58 | backendsFound.add(loadedBackend);
59 | }
60 |
61 | if (backendsFound.size() == 1) {
62 | backend = backendsFound.iterator().next(); // All good
63 | } else if (backendsFound.size() > 1) {
64 | StringBuilder sb = new StringBuilder();
65 | sb.append('[');
66 | for (Jargon2Backend b : backendsFound) {
67 | sb.append(b.getClass().getName()).append(", ");
68 | }
69 | sb.setLength(sb.length() - 2);
70 | sb.append(']');
71 | throw new Jargon2BackendDiscoveryException("Found more than one Jargon2Backends: " + sb.toString());
72 | } else {
73 | throw new Jargon2BackendDiscoveryException("Could not find appropriate jargon2Backend. Use either a service provider or define its class with -D" + JARGON2_BACKEND_SYSTEM_PROP_NAME);
74 | }
75 | }
76 | }
77 | }
78 | return backend;
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/main/java/com/kosprov/jargon2/internal/discovery/Jargon2BackendDiscoveryException.java:
--------------------------------------------------------------------------------
1 | package com.kosprov.jargon2.internal.discovery;
2 |
3 | import com.kosprov.jargon2.api.Jargon2Exception;
4 |
5 | class Jargon2BackendDiscoveryException extends Jargon2Exception {
6 |
7 | Jargon2BackendDiscoveryException(String message) {
8 | super(message);
9 | }
10 |
11 | Jargon2BackendDiscoveryException(String message, Throwable cause) {
12 | super(message, cause);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/main/java/com/kosprov/jargon2/spi/Jargon2Backend.java:
--------------------------------------------------------------------------------
1 | package com.kosprov.jargon2.spi;
2 |
3 | import com.kosprov.jargon2.api.Jargon2Exception;
4 |
5 | import java.util.Map;
6 |
7 | import static com.kosprov.jargon2.api.Jargon2.*;
8 |
9 | /**
10 | * Service Provider Interface (SPI) for Argon2 backend implementations.
11 | *
12 | *
13 | * Service registration can be done by one the two methods: i) Annotate the jar containing the implementation with a
14 | * {@link java.util.ServiceLoader} META-INF/services/com.kosprov.jargon2.spi.Jargon2Backend file
15 | * containing the fully qualified name of the implementation, or ii) by specifying the
16 | * -Dcom.kosprov.jargon2.spi.backend system property equal to the fully qualified name of the
17 | * implementation.
18 | *
19 | *
20 | *
21 | * Only one implementation must be found for the whole JVM by any of the discovery methods.
22 | *
23 | *
24 | *
25 | * Implementing classes must have a default constructor.
26 | *
27 | *
28 | */
29 | public interface Jargon2Backend {
30 |
31 | /**
32 | * Implementor's guides
33 | *
34 | * Implementors must validate input according to Argon2 specification. The basic validations are: minimum hashLength,
35 | * minimum salt length, minimum and maximum lanes, minimum and maximum threads, minimum memory cost and minimum time cost.
36 | *
37 | *
38 | *
39 | * Implementors must accept threads to be larger than lanes and use as many threads as lanes.
40 | *
41 | *
42 | * @param type The Argon2 {@link Type}
43 | * @param version The Argon2 {@link Version}
44 | * @param memoryCost The memory cost in kibi bytes (e.g. 65536 -> 64MB)
45 | * @param timeCost The number of passes through memory
46 | * @param lanes The number of memory lanes
47 | * @param threads The maximum number of threads to process lanes
48 | * @param hashLength The number of output bytes of the hash value
49 | * @param secret A secret for keyed hashing. Can be null
50 | * @param ad Additional authentication data to include into the hash. Can be null
51 | * @param salt The salt value to be used during hashing
52 | * @param password The password to be hashed
53 | * @param options Any options for the backend
54 | * @return A byte array of length hashLength with the hash value
55 | * @throws Jargon2Exception If required parameters are missing, are invalid or hash calculation fails unexpectedly
56 | *
57 | * @see LowLevelApi#rawHash(Type, Version, int, int, int, int, int, byte[], byte[], byte[], byte[], Map)
58 | */
59 | byte[] rawHash(Type type, Version version, int memoryCost, int timeCost, int lanes, int threads, int hashLength, byte[] secret, byte[] ad, byte[] salt, byte[] password, Map options);
60 |
61 | /**
62 | * Implementor's guides
63 | *
64 | * Implementors must validate input according to Argon2 specification. The basic validations are: minimum hashLength,
65 | * minimum salt length, minimum and maximum lanes, minimum and maximum threads, minimum memory cost and minimum time cost.
66 | *
67 | *
68 | *
69 | * Implementors must accept threads to be larger than lanes and use as many threads as lanes.
70 | *
71 | *
72 | * @param type The Argon2 {@link Type}
73 | * @param version The Argon2 {@link Version}
74 | * @param memoryCost The memory cost in kibi bytes (e.g. 65536 -> 64MB)
75 | * @param timeCost The number of passes through memory
76 | * @param lanes The number of memory lanes
77 | * @param threads The maximum number of threads to process lanes
78 | * @param hashLength The number of output bytes of the hash value
79 | * @param secret A secret for keyed hashing. Can be null
80 | * @param ad Additional authentication data to include into the hash. Can be null
81 | * @param salt The salt value to be used during hashing
82 | * @param password The password to be hashed
83 | * @param options Any options for the backend
84 | * @return A string containing the encoded hash value
85 | * @throws Jargon2Exception If required parameters are missing, are invalid or hash calculation fails unexpectedly
86 | *
87 | * @see LowLevelApi#encodedHash(Type, Version, int, int, int, int, int, byte[], byte[], byte[], byte[], Map)
88 | */
89 | String encodedHash(Type type, Version version, int memoryCost, int timeCost, int lanes, int threads, int hashLength, byte[] secret, byte[] ad, byte[] salt, byte[] password, Map options);
90 |
91 | /**
92 | * Implementor's guides
93 | *
94 | * Implementors must validate input according to Argon2 specification. The basic validations are: minimum raw hash length,
95 | * minimum salt length, minimum and maximum lanes, minimum and maximum threads, minimum memory cost and minimum time cost.
96 | *
97 | *
98 | *
99 | * Implementors must accept threads to be larger than lanes and use as many threads as lanes.
100 | *
101 | *
102 | * @param type The Argon2 {@link Type}
103 | * @param version The Argon2 {@link Version}
104 | * @param memoryCost The memory cost in kibi bytes (e.g. 65536 -> 64MB)
105 | * @param timeCost The number of passes through memory
106 | * @param lanes The number of memory lanes
107 | * @param threads The maximum number of threads to process lanes
108 | * @param rawHash The raw hash bytes
109 | * @param secret A secret for keyed hashing. Can be null
110 | * @param ad Additional authentication data to include into the hash. Can be null
111 | * @param salt The salt value to be used during hashing
112 | * @param password The password to be hashed
113 | * @param options Any options for the backend
114 | * @return true if recalculating the hash matches the given value
115 | * @throws Jargon2Exception If required parameters are missing, are invalid or verification fails unexpectedly
116 | *
117 | * @see LowLevelApi#verifyRaw(Type, Version, int, int, int, int, byte[], byte[], byte[], byte[], byte[], Map)
118 | */
119 | boolean verifyRaw(Type type, Version version, int memoryCost, int timeCost, int lanes, int threads, byte[] rawHash, byte[] secret, byte[] ad, byte[] salt, byte[] password, Map options);
120 |
121 | /**
122 | * Implementor's guides
123 | *
124 | *
125 | * Implementors must parse the encoded hash value and perform the same validations as {@link Jargon2Backend#verifyRaw}.
126 | *
127 | *
128 | * @param encodedHash The encoded hash
129 | * @param threads The maximum number of threads it be used during hash recalculation. -1 to derive the number of threads
130 | * from the parallelism property of the encoded hash.
131 | * @param secret The secret (keyed hashing) used during hashing. Can be null
132 | * @param ad Additional authentication data to included during hashing. Can be null
133 | * @param password The password to verify
134 | * @param options Any options for the backend
135 | * @return true if recalculating the hash matches the given value
136 | * @throws Jargon2Exception If required parameters are missing, are invalid or verification fails unexpectedly
137 | *
138 | * @see LowLevelApi#verifyEncoded(String, int, byte[], byte[], byte[], Map)
139 | */
140 | boolean verifyEncoded(String encodedHash, int threads, byte[] secret, byte[] ad, byte[] password, Map options);
141 | }
142 |
--------------------------------------------------------------------------------
/src/main/java/com/kosprov/jargon2/spi/Jargon2BackendException.java:
--------------------------------------------------------------------------------
1 | package com.kosprov.jargon2.spi;
2 |
3 | import com.kosprov.jargon2.api.Jargon2Exception;
4 |
5 | /**
6 | * Generic exception from a Jargon2 backend.
7 | */
8 | public class Jargon2BackendException extends Jargon2Exception{
9 |
10 | public Jargon2BackendException() {
11 | }
12 |
13 | public Jargon2BackendException(String message) {
14 | super(message);
15 | }
16 |
17 | public Jargon2BackendException(String message, Throwable cause) {
18 | super(message, cause);
19 | }
20 |
21 | public Jargon2BackendException(Throwable cause) {
22 | super(cause);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/test/java/com/kosprov/jargon2/api/CapturingDummyJargon2Backend.java:
--------------------------------------------------------------------------------
1 | package com.kosprov.jargon2.api;
2 |
3 | import java.util.Map;
4 |
5 | public class CapturingDummyJargon2Backend extends DummyJargon2Backend {
6 |
7 | public static class CapturedSet {
8 | public Jargon2.Type type;
9 | public Jargon2.Version version;
10 | public int memoryCost;
11 | public int timeCost;
12 | public int lanes;
13 | public int threads;
14 | public byte[] rawHash;
15 | public String encodedHash;
16 | public int hashLength;
17 | public byte[] secret;
18 | public byte[] ad;
19 | public byte[] salt;
20 | public byte[] password;
21 | public Map options;
22 | }
23 |
24 | public CapturedSet captured;
25 |
26 | @Override
27 | public byte[] rawHash(Jargon2.Type type, Jargon2.Version version, int memoryCost, int timeCost, int lanes, int threads, int hashLength, byte[] secret, byte[] ad, byte[] salt, byte[] password, Map options) {
28 | captured = new CapturedSet();
29 | captured.type = type;
30 | captured.version = version;
31 | captured.memoryCost = memoryCost;
32 | captured.timeCost = timeCost;
33 | captured.lanes = lanes;
34 | captured.threads = threads;
35 | captured.hashLength = hashLength;
36 | captured.secret = secret;
37 | captured.ad = ad;
38 | captured.salt = salt;
39 | captured.password = password;
40 | captured.options = options;
41 |
42 | return super.rawHash(type, version, memoryCost, timeCost, lanes, threads, hashLength, secret, ad, salt, password, options);
43 | }
44 |
45 | @Override
46 | public String encodedHash(Jargon2.Type type, Jargon2.Version version, int memoryCost, int timeCost, int lanes, int threads, int hashLength, byte[] secret, byte[] ad, byte[] salt, byte[] password, Map options) {
47 | captured = new CapturedSet();
48 | captured.type = type;
49 | captured.version = version;
50 | captured.memoryCost = memoryCost;
51 | captured.timeCost = timeCost;
52 | captured.lanes = lanes;
53 | captured.threads = threads;
54 | captured.hashLength = hashLength;
55 | captured.secret = secret;
56 | captured.ad = ad;
57 | captured.salt = salt;
58 | captured.password = password;
59 | captured.options = options;
60 |
61 | return super.encodedHash(type, version, memoryCost, timeCost, lanes, threads, hashLength, secret, ad, salt, password, options);
62 | }
63 |
64 | @Override
65 | public boolean verifyRaw(Jargon2.Type type, Jargon2.Version version, int memoryCost, int timeCost, int lanes, int threads, byte[] rawHash, byte[] secret, byte[] ad, byte[] salt, byte[] password, Map options) {
66 | captured = new CapturedSet();
67 | captured.type = type;
68 | captured.version = version;
69 | captured.memoryCost = memoryCost;
70 | captured.timeCost = timeCost;
71 | captured.lanes = lanes;
72 | captured.threads = threads;
73 | captured.rawHash = rawHash;
74 | captured.secret = secret;
75 | captured.ad = ad;
76 | captured.salt = salt;
77 | captured.password = password;
78 | captured.options = options;
79 |
80 | return super.verifyRaw(type, version, memoryCost, timeCost, lanes, threads, rawHash, secret, ad, salt, password, options);
81 | }
82 |
83 | @Override
84 | public boolean verifyEncoded(String encodedHash, int threads, byte[] secret, byte[] ad, byte[] password, Map options) {
85 | captured = new CapturedSet();
86 | captured.encodedHash = encodedHash;
87 | captured.threads = threads;
88 | captured.secret = secret;
89 | captured.ad = ad;
90 | captured.password = password;
91 | captured.options = options;
92 |
93 | return super.verifyEncoded(encodedHash, threads, secret, ad, password, options);
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/src/test/java/com/kosprov/jargon2/api/DummyJargon2Backend.java:
--------------------------------------------------------------------------------
1 | package com.kosprov.jargon2.api;
2 |
3 | import com.kosprov.jargon2.spi.Jargon2Backend;
4 | import org.apache.commons.codec.binary.Base64;
5 |
6 | import java.util.Arrays;
7 | import java.util.Map;
8 |
9 | public class DummyJargon2Backend implements Jargon2Backend {
10 |
11 | @Override
12 | public byte[] rawHash(Jargon2.Type type, Jargon2.Version version, int memoryCost, int timeCost, int lanes, int threads, int hashLength, byte[] secret, byte[] ad, byte[] salt, byte[] password, Map options) {
13 | return doDummyHash(hashLength, password, salt, secret, ad);
14 | }
15 |
16 | @Override
17 | public String encodedHash(Jargon2.Type type, Jargon2.Version version, int memoryCost, int timeCost, int lanes, int threads, int hashLength, byte[] secret, byte[] ad, byte[] salt, byte[] password, Map options) {
18 | StringBuilder sb = new StringBuilder();
19 | sb.append('$').append(type.getValue());
20 | if (version.getValue() > Jargon2.Version.V10.getValue()) {
21 | sb.append('$').append("v=").append(version.getValue());
22 | }
23 | sb.append('$').append("m=").append(memoryCost).append(",t=").append(timeCost).append(",p=").append(lanes);
24 | sb.append('$').append(encode(salt));
25 | sb.append('$').append(encode(rawHash(type, version, memoryCost, timeCost, lanes, threads, hashLength, secret, ad, salt, password, options)));
26 | return sb.toString();
27 | }
28 |
29 | @Override
30 | public boolean verifyRaw(Jargon2.Type type, Jargon2.Version version, int memoryCost, int timeCost, int lanes, int threads, byte[] rawHash, byte[] secret, byte[] ad, byte[] salt, byte[] password, Map options) {
31 | byte[] calcHash = doDummyHash(rawHash.length, password, salt, secret, ad);
32 | return Arrays.equals(rawHash, calcHash);
33 | }
34 |
35 | @Override
36 | public boolean verifyEncoded(String encodedHash, int threads, byte[] secret, byte[] ad, byte[] password, Map options) {
37 | int lastDollar = encodedHash.lastIndexOf('$');
38 | byte[] hash = decode(encodedHash.substring(lastDollar + 1));
39 | int lastLastDollar = encodedHash.lastIndexOf('$', lastDollar - 1);
40 | byte[] salt = decode(encodedHash.substring(lastLastDollar + 1, lastDollar));
41 | byte[] calcHash = doDummyHash(hash.length, password, salt, secret, ad);
42 | return Arrays.equals(hash, calcHash);
43 | }
44 |
45 | private String encode(byte[] data) {
46 | return Base64.encodeBase64URLSafeString(data).replaceAll("-", "+").replaceAll("_", "/");
47 | }
48 |
49 | private byte[] decode(String encoded) {
50 | return Base64.decodeBase64(encoded.replaceAll("\\+", "-").replaceAll("/", "_"));
51 | }
52 |
53 | private byte[] doDummyHash(int length, byte[]... data) {
54 | if (data == null || data.length == 0 || length == 0) return new byte[0];
55 |
56 | byte[] hash = new byte[length];
57 |
58 | int i = 0;
59 | for (byte[] d : data) {
60 | if (d != null) {
61 | for (byte b : d) {
62 | hash[i] = (byte) (hash[i] + 7 * b);
63 | i = (i + 1) % length;
64 | }
65 | }
66 | }
67 |
68 | return hash;
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/test/java/com/kosprov/jargon2/api/DummyProvider.java:
--------------------------------------------------------------------------------
1 | package com.kosprov.jargon2.api;
2 |
3 | import java.security.Provider;
4 | import java.security.SecureRandomSpi;
5 | import java.util.*;
6 |
7 | public class DummyProvider extends Provider {
8 |
9 | private static final DummyProvider INSTANCE = new DummyProvider();
10 |
11 | public static DummyProvider getInstance() {
12 | return INSTANCE;
13 | }
14 |
15 | private Map secureRandomServices = new HashMap() {
16 | {
17 | put("SHA1PRNG", new Service(DummyProvider.this, "SecureRandom", "SHA1PRNG", DummySha1PrngSecureRandomSpi.class.getName(), null, null));
18 | put("NativePRNG", new Service(DummyProvider.this, "SecureRandom", "NativePRNG", DummyNativePrngSecureRandomSpi.class.getName(), null, null));
19 | }
20 | };
21 |
22 | private DummyProvider() {
23 | super("DUMMY", 1.0, "Dummy impl");
24 | }
25 |
26 | @Override
27 | public synchronized Service getService(String type, String algorithm) {
28 | if ("SecureRandom".equals(type)) {
29 | return secureRandomServices.get(algorithm);
30 | } else {
31 | return null;
32 | }
33 | }
34 |
35 | @Override
36 | public synchronized Set getServices() {
37 | return new HashSet<>(secureRandomServices.values());
38 | }
39 |
40 | public static class DummySha1PrngSecureRandomSpi extends SecureRandomSpi {
41 | static byte DUMMY_BYTE = (byte) 0b00000001;
42 |
43 | @Override
44 | protected void engineSetSeed(byte[] seed) { }
45 |
46 | @Override
47 | protected void engineNextBytes(byte[] bytes) {
48 | Arrays.fill(bytes, DUMMY_BYTE);
49 | }
50 |
51 | @Override
52 | protected byte[] engineGenerateSeed(int numBytes) {
53 | return new byte[numBytes];
54 | }
55 | }
56 |
57 | public static class DummyNativePrngSecureRandomSpi extends SecureRandomSpi {
58 | static byte DUMMY_BYTE = (byte) 0b00000010;
59 |
60 | @Override
61 | protected void engineSetSeed(byte[] seed) { }
62 |
63 | @Override
64 | protected void engineNextBytes(byte[] bytes) {
65 | Arrays.fill(bytes, DUMMY_BYTE);
66 | }
67 |
68 | @Override
69 | protected byte[] engineGenerateSeed(int numBytes) {
70 | return new byte[numBytes];
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/test/java/com/kosprov/jargon2/api/DummySaltGenerator.java:
--------------------------------------------------------------------------------
1 | package com.kosprov.jargon2.api;
2 |
3 | import java.util.Arrays;
4 |
5 | class DummySaltGenerator implements Jargon2.SaltGenerator {
6 |
7 | static byte DUMMY_BYTE = (byte) 0b00000011;
8 |
9 | private static final DummySaltGenerator INSTANCE = new DummySaltGenerator();
10 |
11 | static DummySaltGenerator getInstance() {
12 | return INSTANCE;
13 | }
14 |
15 | @Override
16 | public void generate(byte[] salt) {
17 | Arrays.fill(salt, DUMMY_BYTE );
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/test/java/com/kosprov/jargon2/api/Jargon2Test.java:
--------------------------------------------------------------------------------
1 | package com.kosprov.jargon2.api;
2 |
3 | import org.junit.Test;
4 |
5 | import java.io.ByteArrayInputStream;
6 | import java.io.CharArrayReader;
7 | import java.io.InputStream;
8 | import java.io.InputStreamReader;
9 | import java.nio.charset.StandardCharsets;
10 | import java.util.Arrays;
11 | import java.util.HashMap;
12 | import java.util.Map;
13 |
14 | import static com.kosprov.jargon2.api.Jargon2.*;
15 | import static org.hamcrest.Matchers.equalTo;
16 | import static org.hamcrest.Matchers.is;
17 | import static org.hamcrest.Matchers.not;
18 | import static org.hamcrest.collection.IsMapWithSize.anEmptyMap;
19 | import static org.hamcrest.core.StringStartsWith.startsWith;
20 | import static org.junit.Assert.*;
21 |
22 | public class Jargon2Test {
23 |
24 | @Test
25 | public void typicalFluentApiTest() throws Exception {
26 | char[] reference = new char[] {'P', '@', 's', 's', 'W', '0', 'r', 'd'};
27 |
28 | String hash;
29 |
30 | {
31 | char[] password = Arrays.copyOf(reference, reference.length);
32 |
33 | Hasher hasher = jargon2Hasher()
34 | .type(Type.ARGON2id)
35 | .memoryCost(4096)
36 | .timeCost(3)
37 | .parallelism(2)
38 | .saltLength(16)
39 | .hashLength(16);
40 |
41 | try (ByteArray passwordByteArray = toByteArray(password).clearSource()) {
42 |
43 | hash = hasher.password(passwordByteArray).encodedHash();
44 |
45 | assertNotNull(hash);
46 |
47 | assertThat(reference, is(equalTo(password)));
48 | }
49 |
50 | for (char c : password) {
51 | assertEquals(0, c);
52 | }
53 | }
54 |
55 | {
56 | char[] password = Arrays.copyOf(reference, reference.length);
57 |
58 | boolean matches;
59 |
60 | try (ByteArray passwordByteArray = toByteArray(password).clearSource()) {
61 | matches = jargon2Verifier()
62 | .hash(hash)
63 | .password(passwordByteArray)
64 | .verifyEncoded();
65 |
66 | assertTrue(matches);
67 |
68 | assertThat(reference, is(equalTo(password)));
69 | }
70 |
71 | for (char c : password) {
72 | assertEquals(0, c);
73 | }
74 | }
75 | }
76 |
77 | @Test
78 | public void allParamsPassedForEncodedHashing() {
79 | Type type = Type.ARGON2id;
80 | Version version = Version.V10;
81 | int memoryCost = 2048;
82 | int timeCost = 5;
83 | int lanes = 4;
84 | int threads = 2;
85 | int saltLength = 8;
86 | int hashLength = 8;
87 |
88 | byte[] password = "this is a password".getBytes(StandardCharsets.UTF_8);
89 | byte[] secret = "secret key".getBytes(StandardCharsets.UTF_8);
90 | byte[] ad = "some additional data".getBytes(StandardCharsets.UTF_8);
91 |
92 | Map options = new HashMap<>();
93 | options.put("key1", 1);
94 | options.put("key2", 2);
95 | options.put("key3", 3);
96 |
97 | CapturingDummyJargon2Backend backend = new CapturingDummyJargon2Backend();
98 |
99 | String hash = jargon2Hasher()
100 | .backend(backend)
101 | .options(options)
102 | .type(type)
103 | .version(version)
104 | .memoryCost(memoryCost)
105 | .timeCost(timeCost)
106 | .parallelism(lanes, threads)
107 | .saltLength(saltLength)
108 | .hashLength(hashLength)
109 | .secret(secret)
110 | .ad(ad)
111 | .password(password)
112 | .encodedHash();
113 |
114 | assertEquals(type, backend.captured.type);
115 | assertEquals(version, backend.captured.version);
116 | assertEquals(memoryCost, backend.captured.memoryCost);
117 | assertEquals(timeCost, backend.captured.timeCost);
118 | assertEquals(lanes, backend.captured.lanes);
119 | assertEquals(threads, backend.captured.threads);
120 | assertEquals(hashLength, backend.captured.hashLength);
121 | assertSame(secret, backend.captured.secret);
122 | assertSame(ad, backend.captured.ad);
123 | assertSame(saltLength, backend.captured.salt.length);
124 | assertSame(password, backend.captured.password);
125 | assertEquals(options, backend.captured.options);
126 |
127 | assertNotNull(hash);
128 | assertThat(hash, startsWith("$argon2id$m=" + memoryCost + ",t=" + timeCost + ",p=" + lanes + "$"));
129 |
130 | threads = 1;
131 |
132 | boolean matches = jargon2Verifier()
133 | .backend(backend)
134 | .options(options)
135 | .hash(hash)
136 | .threads(threads)
137 | .secret(secret)
138 | .ad(ad)
139 | .password(password)
140 | .verifyEncoded();
141 |
142 | assertSame(hash, backend.captured.encodedHash);
143 | assertEquals(threads, backend.captured.threads);
144 | assertSame(secret, backend.captured.secret);
145 | assertSame(ad, backend.captured.ad);
146 | assertSame(password, backend.captured.password);
147 | assertEquals(options, backend.captured.options);
148 |
149 | assertTrue(matches);
150 | }
151 |
152 | @Test
153 | public void noOptionsGiveEmptyOptionsToBackend() {
154 | CapturingDummyJargon2Backend backend = new CapturingDummyJargon2Backend();
155 |
156 | byte[] password = "this is a password".getBytes(StandardCharsets.UTF_8);
157 |
158 | {
159 | String hash = jargon2Hasher()
160 | .backend(backend)
161 | .password(password)
162 | .encodedHash();
163 |
164 | assertNotNull(backend.captured.options);
165 | assertThat(backend.captured.options, is(anEmptyMap()));
166 |
167 | boolean matches = jargon2Verifier()
168 | .backend(backend)
169 | .password(password)
170 | .hash(hash)
171 | .verifyEncoded();
172 |
173 | assertNotNull(backend.captured.options);
174 | assertThat(backend.captured.options, is(anEmptyMap()));
175 |
176 | assertTrue(matches);
177 | }
178 |
179 | {
180 | String hash = jargon2Hasher()
181 | .backend(backend)
182 | .options(null)
183 | .password(password)
184 | .encodedHash();
185 |
186 | assertNotNull(backend.captured.options);
187 | assertThat(backend.captured.options, is(anEmptyMap()));
188 |
189 | boolean matches = jargon2Verifier()
190 | .backend(backend)
191 | .options(null)
192 | .password(password)
193 | .hash(hash)
194 | .verifyEncoded();
195 |
196 | assertNotNull(backend.captured.options);
197 | assertThat(backend.captured.options, is(anEmptyMap()));
198 |
199 | assertTrue(matches);
200 | }
201 | }
202 |
203 | @Test
204 | public void optionsAreCopiedToBackend() {
205 | CapturingDummyJargon2Backend backend = new CapturingDummyJargon2Backend();
206 |
207 | byte[] password = "this is a password".getBytes(StandardCharsets.UTF_8);
208 |
209 | Map options = new HashMap<>();
210 | options.put("key1", 1);
211 | options.put("key2", 2);
212 | options.put("key3", 3);
213 |
214 | String hash = jargon2Hasher()
215 | .backend(backend)
216 | .options(options)
217 | .password(password)
218 | .encodedHash();
219 |
220 | assertNotNull(backend.captured.options);
221 | assertEquals(options, backend.captured.options);
222 | assertNotSame(options, backend.captured.options);
223 |
224 | boolean matches = jargon2Verifier()
225 | .backend(backend)
226 | .options(options)
227 | .password(password)
228 | .hash(hash)
229 | .verifyEncoded();
230 |
231 | assertNotNull(backend.captured.options);
232 | assertEquals(options, backend.captured.options);
233 | assertNotSame(options, backend.captured.options);
234 |
235 | assertTrue(matches);
236 | }
237 |
238 | @Test
239 | public void customSaltGeneratorTest() {
240 | int saltLength = 8;
241 | int hashLength = 8;
242 |
243 | byte[] password = "this is a password".getBytes(StandardCharsets.UTF_8);
244 |
245 | CapturingDummyJargon2Backend backend = new CapturingDummyJargon2Backend();
246 |
247 | Hasher hasher = jargon2Hasher()
248 | .backend(backend)
249 | .saltLength(saltLength)
250 | .hashLength(hashLength)
251 | .password(password);
252 |
253 | {
254 | String hash = hasher.saltGenerator("SHA1PRNG").encodedHash();
255 | assertSame(saltLength, backend.captured.salt.length);
256 | assertNotNull(hash);
257 | }
258 |
259 | {
260 | String hash = hasher.saltGenerator("SHA1PRNG", DummyProvider.getInstance()).encodedHash();
261 | assertEquals(saltLength, backend.captured.salt.length);
262 | for (byte b : backend.captured.salt) {
263 | assertEquals(DummyProvider.DummySha1PrngSecureRandomSpi.DUMMY_BYTE, b);
264 | }
265 | assertNotNull(hash);
266 | }
267 |
268 | {
269 | String hash = hasher.saltGenerator("NativePRNG", DummyProvider.getInstance()).encodedHash();
270 | assertEquals(saltLength, backend.captured.salt.length);
271 | for (byte b : backend.captured.salt) {
272 | assertEquals(DummyProvider.DummyNativePrngSecureRandomSpi.DUMMY_BYTE, b);
273 | }
274 | assertNotNull(hash);
275 | }
276 |
277 | {
278 | String hash = hasher.saltGenerator(DummySaltGenerator.getInstance()).encodedHash();
279 | assertEquals(saltLength, backend.captured.salt.length);
280 | for (byte b : backend.captured.salt) {
281 | assertEquals(DummySaltGenerator.DUMMY_BYTE, b);
282 | }
283 | assertNotNull(hash);
284 | }
285 | }
286 |
287 | @Test(expected = Jargon2Exception.class)
288 | public void erroneousSaltGeneratorTest() {
289 | jargon2Hasher().saltGenerator("WRONG");
290 | }
291 |
292 | @Test(expected = Jargon2Exception.class)
293 | public void erroneousSaltGeneratorTest2() {
294 | jargon2Hasher().saltGenerator("WRONG", "WRONG");
295 | }
296 |
297 | @Test(expected = Jargon2Exception.class)
298 | public void erroneousSaltGeneratorTest3() {
299 | jargon2Hasher().saltGenerator("WRONG", DummyProvider.getInstance());
300 | }
301 |
302 | @Test
303 | public void allParamsPassedForRawHashing() {
304 |
305 | Type type = Type.ARGON2id;
306 | Version version = Version.V10;
307 | int memoryCost = 2048;
308 | int timeCost = 5;
309 | int lanes = 4;
310 | int threads = 2;
311 | int hashLength = 8;
312 |
313 | byte[] password = "this is a password".getBytes(StandardCharsets.UTF_8);
314 | byte[] salt = "some salt".getBytes(StandardCharsets.UTF_8);
315 | byte[] secret = "secret key".getBytes(StandardCharsets.UTF_8);
316 | byte[] ad = "some additional data".getBytes(StandardCharsets.UTF_8);
317 |
318 | Map options = new HashMap<>();
319 | options.put("key1", 1);
320 | options.put("key2", 2);
321 | options.put("key3", 3);
322 |
323 | CapturingDummyJargon2Backend backend = new CapturingDummyJargon2Backend();
324 |
325 | byte[] hash = jargon2Hasher()
326 | .backend(backend)
327 | .options(options)
328 | .type(type)
329 | .version(version)
330 | .memoryCost(memoryCost)
331 | .timeCost(timeCost)
332 | .parallelism(lanes, threads)
333 | .hashLength(hashLength)
334 | .salt(salt)
335 | .secret(secret)
336 | .ad(ad)
337 | .password(password)
338 | .rawHash();
339 |
340 | assertEquals(type, backend.captured.type);
341 | assertEquals(version, backend.captured.version);
342 | assertEquals(memoryCost, backend.captured.memoryCost);
343 | assertEquals(timeCost, backend.captured.timeCost);
344 | assertEquals(lanes, backend.captured.lanes);
345 | assertEquals(threads, backend.captured.threads);
346 | assertEquals(hashLength, backend.captured.hashLength);
347 | assertSame(secret, backend.captured.secret);
348 | assertSame(ad, backend.captured.ad);
349 | assertSame(salt, backend.captured.salt);
350 | assertSame(password, backend.captured.password);
351 | assertEquals(options, backend.captured.options);
352 |
353 | assertNotNull(hash);
354 |
355 | threads = 1;
356 |
357 | boolean matches = jargon2Verifier()
358 | .backend(backend)
359 | .options(options)
360 | .type(type)
361 | .version(version)
362 | .memoryCost(memoryCost)
363 | .timeCost(timeCost)
364 | .parallelism(lanes)
365 | .parallelism(lanes, threads) // this should override threads to 1
366 | .hash(hash)
367 | .secret(secret)
368 | .ad(ad)
369 | .salt(salt)
370 | .password(password)
371 | .verifyRaw();
372 |
373 | assertEquals(type, backend.captured.type);
374 | assertEquals(version, backend.captured.version);
375 | assertEquals(memoryCost, backend.captured.memoryCost);
376 | assertEquals(timeCost, backend.captured.timeCost);
377 | assertEquals(lanes, backend.captured.lanes);
378 | assertEquals(threads, backend.captured.threads);
379 | assertSame(hash, backend.captured.rawHash);
380 | assertSame(secret, backend.captured.secret);
381 | assertSame(ad, backend.captured.ad);
382 | assertSame(salt, backend.captured.salt);
383 | assertSame(password, backend.captured.password);
384 | assertEquals(options, backend.captured.options);
385 |
386 | assertTrue(matches);
387 | }
388 |
389 | @Test(expected = Jargon2Exception.class)
390 | public void noSaltForRawHashingTest() {
391 | byte[] password = "this is a password".getBytes(StandardCharsets.UTF_8);
392 |
393 | jargon2Hasher()
394 | .password(password)
395 | .rawHash();
396 | }
397 |
398 | @Test
399 | public void lowLevelApiEncodedTest() {
400 | String password = "this is a password";
401 | byte[] passwordBytes = password.getBytes(StandardCharsets.UTF_8);
402 |
403 | String salt = "some salt";
404 | byte[] saltBytes = salt.getBytes(StandardCharsets.UTF_8);
405 |
406 | String encoded = jargon2LowLevelApi(DummyJargon2Backend.class.getName()).encodedHash(
407 | Type.ARGON2id,
408 | Version.V13,
409 | 4096,
410 | 3,
411 | 2,
412 | 16,
413 | saltBytes,
414 | passwordBytes
415 | );
416 |
417 | boolean matches = jargon2LowLevelApi().verifyEncoded(
418 | encoded,
419 | passwordBytes
420 | );
421 |
422 | assertTrue(matches);
423 | }
424 |
425 | @Test
426 | public void lowLevelApiEncodedAllParamsTest() {
427 | String secret = "this is a secret";
428 | byte[] secretBytes = secret.getBytes(StandardCharsets.UTF_8);
429 |
430 | String ad = "this is additional data";
431 | byte[] adBytes = ad.getBytes(StandardCharsets.UTF_8);
432 |
433 | String password = "this is a password";
434 | byte[] passwordBytes = password.getBytes(StandardCharsets.UTF_8);
435 |
436 | String salt = "some salt";
437 | byte[] saltBytes = salt.getBytes(StandardCharsets.UTF_8);
438 |
439 | String encoded = jargon2LowLevelApi(DummyJargon2Backend.class.getName()).encodedHash(
440 | Type.ARGON2id,
441 | Version.V13,
442 | 4096,
443 | 3,
444 | 2,
445 | 2,
446 | 16,
447 | secretBytes,
448 | adBytes,
449 | saltBytes,
450 | passwordBytes,
451 | null
452 | );
453 |
454 | boolean matches = jargon2LowLevelApi().verifyEncoded(
455 | encoded,
456 | 2,
457 | secretBytes,
458 | adBytes,
459 | passwordBytes,
460 | null
461 | );
462 |
463 | assertTrue(matches);
464 | }
465 |
466 | @Test
467 | public void lowLevelApiRawTest() {
468 | String password = "this is a password";
469 | byte[] passwordBytes = password.getBytes(StandardCharsets.UTF_8);
470 |
471 | String salt = "some salt";
472 | byte[] saltBytes = salt.getBytes(StandardCharsets.UTF_8);
473 |
474 | byte[] rawHash = jargon2LowLevelApi(DummyJargon2Backend.class.getName()).rawHash(
475 | Type.ARGON2id,
476 | Version.V13,
477 | 4096,
478 | 3,
479 | 2,
480 | 16,
481 | saltBytes,
482 | passwordBytes
483 | );
484 |
485 | boolean matches = jargon2LowLevelApi().verifyRaw(
486 | Type.ARGON2id,
487 | Version.V13,
488 | 4096,
489 | 3,
490 | 2,
491 | rawHash,
492 | saltBytes,
493 | passwordBytes
494 | );
495 |
496 | assertTrue(matches);
497 | }
498 |
499 | @Test
500 | public void lowLevelApiRawAllParamsTest() {
501 | String secret = "this is a secret";
502 | byte[] secretBytes = secret.getBytes(StandardCharsets.UTF_8);
503 |
504 | String ad = "this is additional data";
505 | byte[] adBytes = ad.getBytes(StandardCharsets.UTF_8);
506 |
507 | String password = "this is a password";
508 | byte[] passwordBytes = password.getBytes(StandardCharsets.UTF_8);
509 |
510 | String salt = "some salt";
511 | byte[] saltBytes = salt.getBytes(StandardCharsets.UTF_8);
512 |
513 | byte[] rawHash = jargon2LowLevelApi(DummyJargon2Backend.class.getName()).rawHash(
514 | Type.ARGON2id,
515 | Version.V13,
516 | 4096,
517 | 3,
518 | 2,
519 | 2,
520 | 16,
521 | secretBytes,
522 | adBytes,
523 | saltBytes,
524 | passwordBytes,
525 | null
526 | );
527 |
528 | boolean matches = jargon2LowLevelApi().verifyRaw(
529 | Type.ARGON2id,
530 | Version.V13,
531 | 4096,
532 | 3,
533 | 2,
534 | 2,
535 | rawHash,
536 | secretBytes,
537 | adBytes,
538 | saltBytes,
539 | passwordBytes,
540 | null
541 | );
542 |
543 | assertTrue(matches);
544 | }
545 |
546 | @Test(expected = Jargon2Exception.class)
547 | public void invalidBackendClassNameOnLowLevelApiTest() {
548 | jargon2LowLevelApi("invalid.class.Name");
549 | }
550 |
551 | @Test(expected = Jargon2Exception.class)
552 | public void nonConstructableBackendClassNameOnLowLevelApiTest() {
553 | jargon2LowLevelApi(NonConstructableJargon2Backend.class.getName());
554 | }
555 |
556 | @Test(expected = Jargon2Exception.class)
557 | public void nonConstructableBackendClassOnLowLevelApiTest() {
558 | jargon2LowLevelApi(NonConstructableJargon2Backend.class);
559 | }
560 |
561 | @Test(expected = Jargon2Exception.class)
562 | public void invalidBackendClassNameOnHasherTest() {
563 | jargon2Hasher().backend("invalid.class.Name");
564 | }
565 |
566 | @Test(expected = Jargon2Exception.class)
567 | public void nonConstructableBackendClassNameOnHasherTest() {
568 | jargon2Hasher().backend(NonConstructableJargon2Backend.class.getName());
569 | }
570 |
571 | @Test(expected = Jargon2Exception.class)
572 | public void nonConstructableBackendClassOnHasherTest() {
573 | jargon2Hasher().backend(NonConstructableJargon2Backend.class);
574 | }
575 |
576 | @Test(expected = Jargon2Exception.class)
577 | public void invalidBackendClassNameOnVerifierTest() {
578 | jargon2Verifier().backend("invalid.class.Name");
579 | }
580 |
581 | @Test(expected = Jargon2Exception.class)
582 | public void nonConstructableBackendClassNameOnVerifierTest() {
583 | jargon2Verifier().backend(NonConstructableJargon2Backend.class.getName());
584 | }
585 |
586 | @Test(expected = Jargon2Exception.class)
587 | public void nonConstructableBackendClassOnVerifierTest() {
588 | jargon2Verifier().backend(NonConstructableJargon2Backend.class);
589 | }
590 |
591 | @Test
592 | public void validBackendsTest() {
593 | jargon2LowLevelApi(DummyJargon2Backend.class.getName());
594 | jargon2LowLevelApi(DummyJargon2Backend.class);
595 | jargon2LowLevelApi(new DummyJargon2Backend());
596 | jargon2Hasher().backend(DummyJargon2Backend.class.getName());
597 | jargon2Hasher().backend(DummyJargon2Backend.class);
598 | jargon2Hasher().backend(new DummyJargon2Backend());
599 | jargon2Verifier().backend(DummyJargon2Backend.class.getName());
600 | jargon2Verifier().backend(DummyJargon2Backend.class);
601 | jargon2Verifier().backend(new DummyJargon2Backend());
602 | }
603 |
604 | @Test
605 | public void byteArraysTest() throws Exception {
606 | byte[] secret = "superSecret".getBytes(StandardCharsets.UTF_8);
607 |
608 | try (ByteArray secretByteArray = toByteArray(secret).clearSource()) {
609 | Hasher hasher = jargon2Hasher()
610 | .type(Type.ARGON2id)
611 | .memoryCost(8)
612 | .timeCost(1)
613 | .parallelism(1)
614 | .secret(secretByteArray)
615 | .hashLength(16);
616 |
617 | Verifier verifier = jargon2Verifier()
618 | .secret(secretByteArray);
619 |
620 | char[] ad = "additional data".toCharArray();
621 | InputStreamReader salt = new InputStreamReader(new ByteArrayInputStream("this is a salt".getBytes()));
622 | InputStream password = new ByteArrayInputStream("this is a password".getBytes());
623 |
624 | boolean matches;
625 |
626 | try (ByteArray adByteArray = toByteArray(ad).encoding("ASCII");
627 | ByteArray saltByteArray = toByteArray(salt).encoding("ASCII");
628 | ByteArray passwordByteArray = toByteArray(password)) {
629 |
630 | String hash = hasher
631 | .ad(adByteArray)
632 | .salt(saltByteArray)
633 | .password(passwordByteArray)
634 | .encodedHash();
635 |
636 | matches = verifier
637 | .hash(hash)
638 | .ad(adByteArray)
639 | .password(passwordByteArray)
640 | .verifyEncoded();
641 | }
642 | assertTrue(matches);
643 | }
644 |
645 | byte[] zeros = new byte[secret.length];
646 | assertThat(zeros, is(equalTo(secret)));
647 | }
648 |
649 | @Test
650 | public void byteArrayEncodingTest() throws Exception {
651 | String str = "Φούμπαρ";
652 |
653 | {
654 | CharSeqByteArray byteArray1 = toByteArray(str);
655 | byte[] bytes1 = byteArray1.getBytes();
656 |
657 | ByteArray byteArray2 = byteArray1.encoding("ISO8859_7");
658 | byte[] bytes2 = byteArray2.getBytes();
659 |
660 | assertThat(bytes1, not(equalTo(bytes2)));
661 | assertThat(str.getBytes("ISO8859_7"), is(equalTo(bytes2)));
662 | }
663 |
664 | {
665 | char[] chars = str.toCharArray();
666 | CharSeqByteArray byteArray1 = toByteArray(chars);
667 | byte[] bytes1 = byteArray1.getBytes();
668 |
669 | ByteArray byteArray2 = byteArray1.encoding("ISO8859_7");
670 | byte[] bytes2 = byteArray2.getBytes();
671 |
672 | assertThat(bytes1, not(equalTo(bytes2)));
673 | assertThat(new String(chars).getBytes("ISO8859_7"), is(equalTo(bytes2)));
674 | }
675 |
676 | {
677 | char[] chars = str.toCharArray();
678 |
679 | ByteArray byteArray1 = toByteArray(new CharArrayReader(chars));
680 | byte[] bytes1 = byteArray1.getBytes();
681 |
682 | ByteArray byteArray2 = toByteArray(new CharArrayReader(chars)).encoding("ISO8859_7");
683 | byte[] bytes2 = byteArray2.getBytes();
684 |
685 | assertThat(bytes1, not(equalTo(bytes2)));
686 | assertThat(new String(chars).getBytes("ISO8859_7"), is(equalTo(bytes2)));
687 | }
688 | }
689 |
690 | @Test
691 | public void byteArrayNormalizationTest() {
692 |
693 | Hasher hasher = jargon2Hasher()
694 | .type(Type.ARGON2id)
695 | .memoryCost(8)
696 | .timeCost(1)
697 | .parallelism(1)
698 | .saltLength(8)
699 | .hashLength(16);
700 |
701 | Verifier verifier = jargon2Verifier();
702 |
703 | {
704 | String password1 = "\u00C1";
705 | String password2 = "\u0041\u0301";
706 |
707 | {
708 | String hash = hasher.password(toByteArray(password1)).encodedHash();
709 |
710 | boolean matches = verifier.hash(hash).password(toByteArray(password2)).verifyEncoded();
711 | assertFalse(matches);
712 | }
713 |
714 | {
715 | String hash = hasher.password(toByteArray(password1).normalize()).encodedHash();
716 |
717 | boolean matches = verifier.hash(hash).password(toByteArray(password2).normalize()).verifyEncoded();
718 | assertTrue(matches);
719 | }
720 | }
721 |
722 | {
723 | char[] password1 = new char[] { '\u00C1' };
724 | char[] password2 = new char[] { '\u0041', '\u0301' };
725 | {
726 |
727 | String hash = hasher.password(toByteArray(password1)).encodedHash();
728 |
729 | boolean matches = verifier.hash(hash).password(toByteArray(password2)).verifyEncoded();
730 | assertFalse(matches);
731 | }
732 |
733 | {
734 | String hash = hasher.password(toByteArray(password1).normalize()).encodedHash();
735 |
736 | boolean matches = verifier.hash(hash).password(toByteArray(password2).normalize()).verifyEncoded();
737 | assertTrue(matches);
738 | }
739 | }
740 |
741 | {
742 | char[] password1 = new char[] { '\u00C1' };
743 | char[] password2 = new char[] { '\u0041', '\u0301' };
744 | {
745 |
746 | String hash = hasher.password(toByteArray(new CharArrayReader(password1))).encodedHash();
747 |
748 | boolean matches = verifier.hash(hash).password(toByteArray(new CharArrayReader(password2))).verifyEncoded();
749 | assertFalse(matches);
750 | }
751 |
752 | {
753 | String hash = hasher.password(toByteArray(new CharArrayReader(password1)).normalize()).encodedHash();
754 |
755 | boolean matches = verifier.hash(hash).password(toByteArray(new CharArrayReader(password2)).normalize()).verifyEncoded();
756 | assertTrue(matches);
757 | }
758 | }
759 | }
760 |
761 | @Test
762 | public void propertiesMatchTest() {
763 |
764 | byte[] secret = "superSecret".getBytes(StandardCharsets.UTF_8);
765 |
766 | Hasher hasher = jargon2Hasher()
767 | .type(Type.ARGON2id)
768 | .memoryCost(8)
769 | .timeCost(1)
770 | .parallelism(1)
771 | .saltLength(8)
772 | .hashLength(16);
773 |
774 | String encodedHash = hasher.password(secret).encodedHash();
775 |
776 | // encoded hash produced by this hasher must match
777 | assertTrue(hasher.propertiesMatch(encodedHash));
778 |
779 | // encoded hash produced by this hasher must match again
780 | assertTrue(hasher.propertiesMatch(encodedHash));
781 |
782 | encodedHash = "$argon2id$v=19$m=8,t=1,p=1$AAAAAAAAAAA$BBBBBBBBBBBBBBBBBBBBBB";
783 |
784 | // build a new hasher by changing a single property
785 | // encoded hash must not match with the new hasher
786 | {
787 | Hasher otherHasher = hasher.type(Type.ARGON2i);
788 | assertFalse(otherHasher.propertiesMatch(encodedHash));
789 | assertTrue(otherHasher.propertiesMatch(encodedHash.replace("argon2id", "argon2i")));
790 | }
791 |
792 | {
793 | Hasher otherHasher = hasher.version(Version.V10);
794 | assertFalse(otherHasher.propertiesMatch(encodedHash));
795 | assertTrue(otherHasher.propertiesMatch(encodedHash.replace("$v=19", "")));
796 | }
797 |
798 | {
799 | Hasher otherHasher = hasher.memoryCost(16);
800 | assertFalse(otherHasher.propertiesMatch(encodedHash));
801 | assertTrue(otherHasher.propertiesMatch(encodedHash.replace("m=8", "m=16")));
802 | }
803 |
804 | {
805 | Hasher otherHasher = hasher.timeCost(10);
806 | assertFalse(otherHasher.propertiesMatch(encodedHash));
807 | assertTrue(otherHasher.propertiesMatch(encodedHash.replace("t=1", "t=10")));
808 | }
809 |
810 | {
811 | Hasher otherHasher = hasher.parallelism(4);
812 | assertFalse(otherHasher.propertiesMatch(encodedHash));
813 | assertTrue(otherHasher.propertiesMatch(encodedHash.replace("p=1", "p=4")));
814 | }
815 |
816 | {
817 | Hasher otherHasher = hasher.parallelism(4, 1);
818 | assertFalse(otherHasher.propertiesMatch(encodedHash));
819 | assertTrue(otherHasher.propertiesMatch(encodedHash.replace("p=1", "p=4")));
820 | }
821 |
822 | {
823 | Hasher otherHasher = hasher.saltLength(16);
824 | assertFalse(otherHasher.propertiesMatch(encodedHash));
825 | assertTrue(otherHasher.propertiesMatch(encodedHash.replace("$AAAAAAAAAAA", "$AAAAAAAAAAAAAAAAAAAAAA")));
826 | }
827 |
828 | {
829 | Hasher otherHasher = hasher.hashLength(32);
830 | assertFalse(otherHasher.propertiesMatch(encodedHash));
831 | assertTrue(otherHasher.propertiesMatch(encodedHash.replace("$BBBBBBBBBBBBBBBBBBBBBB", "$BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB")));
832 | }
833 |
834 | // encoded hash produced by the original hasher must still match
835 | assertTrue(hasher.propertiesMatch(encodedHash));
836 |
837 | // original hasher does not match with changed encoded hashes
838 | assertFalse(hasher.propertiesMatch(encodedHash.replace("argon2id", "argon2i")));
839 | assertFalse(hasher.propertiesMatch(encodedHash.replace("$v=19", "")));
840 | assertFalse(hasher.propertiesMatch(encodedHash.replace("m=8", "m=16")));
841 | assertFalse(hasher.propertiesMatch(encodedHash.replace("t=1", "t=10")));
842 | assertFalse(hasher.propertiesMatch(encodedHash.replace("p=1", "p=4")));
843 | assertFalse(hasher.propertiesMatch(encodedHash.replace("p=1", "p=4")));
844 | assertFalse(hasher.propertiesMatch(encodedHash.replace("$AAAAAAAAAAA", "$AAAAAAAAAAAAAAAAAAAAAA")));
845 | assertFalse(hasher.propertiesMatch(encodedHash.replace("$BBBBBBBBBBBBBBBBBBBBBB", "$BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB")));
846 | }
847 | }
--------------------------------------------------------------------------------
/src/test/java/com/kosprov/jargon2/api/NonConstructableJargon2Backend.java:
--------------------------------------------------------------------------------
1 | package com.kosprov.jargon2.api;
2 |
3 | import com.kosprov.jargon2.spi.Jargon2Backend;
4 |
5 | import java.util.Map;
6 |
7 | public class NonConstructableJargon2Backend implements Jargon2Backend {
8 |
9 | public NonConstructableJargon2Backend(String dummy) {
10 | }
11 |
12 | @Override
13 | public byte[] rawHash(Jargon2.Type type, Jargon2.Version version, int memoryCost, int timeCost, int lanes, int threads, int hashLength, byte[] secret, byte[] ad, byte[] salt, byte[] password, Map options) {
14 | return null;
15 | }
16 |
17 | @Override
18 | public String encodedHash(Jargon2.Type type, Jargon2.Version version, int memoryCost, int timeCost, int lanes, int threads, int hashLength, byte[] secret, byte[] ad, byte[] salt, byte[] password, Map options) {
19 | return null;
20 | }
21 |
22 | @Override
23 | public boolean verifyRaw(Jargon2.Type type, Jargon2.Version version, int memoryCost, int timeCost, int lanes, int threads, byte[] rawHash, byte[] secret, byte[] ad, byte[] salt, byte[] password, Map options) {
24 | return false;
25 | }
26 |
27 | @Override
28 | public boolean verifyEncoded(String encodedHash, int threads, byte[] secret, byte[] ad, byte[] password, Map options) {
29 | return false;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/test/java/com/kosprov/jargon2/internal/ByteArrayImplTest.java:
--------------------------------------------------------------------------------
1 | package com.kosprov.jargon2.internal;
2 |
3 | import com.kosprov.jargon2.api.Jargon2;
4 | import org.junit.Test;
5 |
6 | import java.io.ByteArrayInputStream;
7 | import java.io.CharArrayReader;
8 | import java.nio.charset.StandardCharsets;
9 | import java.util.Arrays;
10 |
11 | import static com.kosprov.jargon2.api.Jargon2.ByteArray;
12 | import static org.junit.Assert.*;
13 |
14 | public class ByteArrayImplTest {
15 |
16 | @Test
17 | public void toByteArrayFromCharArrayTest() throws Exception {
18 |
19 | char[] chars = "0123456789".toCharArray();
20 |
21 | ByteArray byteArray = new ByteArrayImpl.CharSeqByteArrayImpl(chars, StandardCharsets.UTF_8);
22 | byte[] bytes = byteArray.getBytes();
23 |
24 | assertTrue(Arrays.equals(new String(chars).getBytes(StandardCharsets.UTF_8), bytes));
25 | }
26 |
27 | @Test
28 | public void toByteArrayFromCharArrayNonAsciiTest() throws Exception {
29 |
30 | char[] chars = "Φούμπαρ".toCharArray();
31 |
32 | ByteArray byteArray = new ByteArrayImpl.CharSeqByteArrayImpl(chars, StandardCharsets.UTF_8);
33 | byte[] bytes = byteArray.getBytes();
34 |
35 | assertTrue(Arrays.equals(new String(chars).getBytes(StandardCharsets.UTF_8), bytes));
36 | }
37 |
38 | @Test
39 | public void toByteArrayFromCharArrayNonAsciiNonDefaultEncodingTest() throws Exception {
40 |
41 | char[] chars = "Φούμπαρ".toCharArray();
42 |
43 | ByteArray byteArray1 = new ByteArrayImpl.CharSeqByteArrayImpl(chars, StandardCharsets.UTF_8);
44 | byte[] bytes1 = byteArray1.getBytes();
45 |
46 | ByteArray byteArray2 = new ByteArrayImpl.CharSeqByteArrayImpl(chars, StandardCharsets.UTF_8).encoding("ISO8859_7");
47 | byte[] bytes2 = byteArray2.getBytes();
48 |
49 | assertFalse(Arrays.equals(bytes1, bytes2));
50 | assertTrue(Arrays.equals(new String(chars).getBytes("ISO8859_7"), bytes2));
51 | }
52 |
53 | @Test
54 | public void toByteArrayFromStringTest() throws Exception {
55 |
56 | String str = "0123456789";
57 |
58 | ByteArray byteArray = new ByteArrayImpl.CharSeqByteArrayImpl(str, StandardCharsets.UTF_8);
59 | byte[] bytes = byteArray.getBytes();
60 |
61 | assertTrue(Arrays.equals(str.getBytes(StandardCharsets.UTF_8), bytes));
62 | }
63 |
64 | @Test
65 | public void toByteArrayFromStringNonAsciiTest() throws Exception {
66 |
67 | String str = "Φούμπαρ";
68 |
69 | ByteArray byteArray = new ByteArrayImpl.CharSeqByteArrayImpl(str, StandardCharsets.UTF_8);
70 | byte[] bytes = byteArray.getBytes();
71 |
72 | assertTrue(Arrays.equals(str.getBytes(StandardCharsets.UTF_8), bytes));
73 | }
74 |
75 | @Test
76 | public void toByteArrayFromStringNonAsciiNonDefaultEncodingTest() throws Exception {
77 |
78 | String str = "Φούμπαρ";
79 |
80 | ByteArray byteArray1 = new ByteArrayImpl.CharSeqByteArrayImpl(str, StandardCharsets.UTF_8);
81 | byte[] bytes1 = byteArray1.getBytes();
82 |
83 | ByteArray byteArray2 = new ByteArrayImpl.CharSeqByteArrayImpl(str, StandardCharsets.UTF_8).encoding("ISO8859_7");
84 | byte[] bytes2 = byteArray2.getBytes();
85 |
86 | assertFalse(Arrays.equals(bytes1, bytes2));
87 | assertTrue(Arrays.equals(str.getBytes("ISO8859_7"), bytes2));
88 | }
89 |
90 | @Test
91 | public void toByteArrayFromInputStreamTest() throws Exception {
92 | int bufferSize = 64;
93 |
94 | {
95 | // zero bytes
96 | byte[] bytes = new byte[0];
97 |
98 | assertTrue(bytes.length == 0);
99 |
100 | ByteArray byteArray = new ByteArrayImpl(new ByteArrayInputStream(bytes), bufferSize);
101 | byte[] bytes2 = byteArray.getBytes();
102 |
103 | assertTrue(bytes2.length == 0);
104 | }
105 |
106 | {
107 | // less than 64 bytes
108 | byte[] bytes = "0123456789".getBytes();
109 |
110 | assertTrue(bytes.length < 64);
111 |
112 | ByteArray byteArray = new ByteArrayImpl(new ByteArrayInputStream(bytes), bufferSize);
113 | byte[] bytes2 = byteArray.getBytes();
114 |
115 | assertTrue(Arrays.equals(bytes, bytes2));
116 | }
117 |
118 | {
119 | // exactly 64 bytes
120 | byte[] bytes = "0123456789 0123456789 0123456789 0123456789 0123456789 012345678".getBytes();
121 |
122 | assertTrue(bytes.length == 64);
123 |
124 | ByteArray byteArray = new ByteArrayImpl(new ByteArrayInputStream(bytes), bufferSize);
125 | byte[] bytes2 = byteArray.getBytes();
126 |
127 | assertTrue(Arrays.equals(bytes, bytes2));
128 | }
129 |
130 | {
131 | // less than 128 bytes
132 | byte[] bytes = "0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 01234".getBytes();
133 |
134 | assertTrue(bytes.length > 64 && bytes.length < 128);
135 |
136 | ByteArray byteArray = new ByteArrayImpl(new ByteArrayInputStream(bytes), bufferSize);
137 | byte[] bytes2 = byteArray.getBytes();
138 |
139 | assertTrue(Arrays.equals(bytes, bytes2));
140 | }
141 |
142 | {
143 | // exactly 128 bytes
144 | byte[] bytes = "0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456".getBytes();
145 |
146 | assertTrue(bytes.length == 128);
147 |
148 | ByteArray byteArray = new ByteArrayImpl(new ByteArrayInputStream(bytes), bufferSize);
149 | byte[] bytes2 = byteArray.getBytes();
150 |
151 | assertTrue(Arrays.equals(bytes, bytes2));
152 | }
153 |
154 | {
155 | // more than 128 bytes
156 | byte[] bytes = "0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789 0123456789".getBytes();
157 |
158 | assertTrue(bytes.length > 128);
159 |
160 | ByteArray byteArray = new ByteArrayImpl(new ByteArrayInputStream(bytes), bufferSize);
161 | byte[] bytes2 = byteArray.getBytes();
162 |
163 | assertTrue(Arrays.equals(bytes, bytes2));
164 | }
165 | }
166 |
167 | @Test
168 | public void toByteArrayFromReaderTest() throws Exception {
169 | char[] chars = "value".toCharArray();
170 |
171 | ByteArray byteArray = new ByteArrayImpl.CharSeqByteArrayImpl(new CharArrayReader(chars), 64, StandardCharsets.UTF_8);
172 | byte[] bytes = byteArray.getBytes();
173 |
174 | assertTrue(Arrays.equals(new String(chars).getBytes(StandardCharsets.UTF_8), bytes));
175 | }
176 |
177 | @Test
178 | public void toByteArrayFromReaderNonAsciiTest() throws Exception {
179 | char[] chars = "Φούμπαρ".toCharArray();
180 |
181 | ByteArray byteArray = new ByteArrayImpl.CharSeqByteArrayImpl(new CharArrayReader(chars), 64, StandardCharsets.UTF_8);
182 | byte[] bytes = byteArray.getBytes();
183 |
184 | assertTrue(Arrays.equals(new String(chars).getBytes(StandardCharsets.UTF_8), bytes));
185 | }
186 |
187 | @Test
188 | public void toByteArrayFromReaderNonAsciiNonDefaultEncodingTest() throws Exception {
189 | char[] chars = "Φούμπαρ".toCharArray();
190 |
191 | ByteArray byteArray1 = new ByteArrayImpl.CharSeqByteArrayImpl(new CharArrayReader(chars), 64, StandardCharsets.UTF_8);
192 | byte[] bytes1 = byteArray1.getBytes();
193 |
194 | ByteArray byteArray2 = new ByteArrayImpl.CharSeqByteArrayImpl(new CharArrayReader(chars), 64, StandardCharsets.UTF_8).encoding("ISO8859_7");
195 | byte[] bytes2 = byteArray2.getBytes();
196 |
197 | assertFalse(Arrays.equals(bytes1, bytes2));
198 | assertTrue(Arrays.equals(new String(chars).getBytes("ISO8859_7"), bytes2));
199 | }
200 |
201 | @Test
202 | public void toByteArrayFromCharArrayNormalizedTest() throws Exception {
203 | char[] chars1 = "\u00C1".toCharArray();
204 | char[] chars2 = "\u0041\u0301".toCharArray();
205 |
206 | {
207 | ByteArray byteArray1 = new ByteArrayImpl.CharSeqByteArrayImpl(chars1, StandardCharsets.UTF_8);
208 | byte[] bytes1 = byteArray1.getBytes();
209 |
210 | ByteArray byteArray2 = new ByteArrayImpl.CharSeqByteArrayImpl(chars2, StandardCharsets.UTF_8);
211 | byte[] bytes2 = byteArray2.getBytes();
212 |
213 | assertFalse(Arrays.equals(bytes1, bytes2));
214 | }
215 |
216 | {
217 | ByteArray byteArray1 = new ByteArrayImpl.CharSeqByteArrayImpl(chars1, StandardCharsets.UTF_8).normalize();
218 | byte[] bytes1 = byteArray1.getBytes();
219 |
220 | ByteArray byteArray2 = new ByteArrayImpl.CharSeqByteArrayImpl(chars2, StandardCharsets.UTF_8).normalize();
221 | byte[] bytes2 = byteArray2.getBytes();
222 |
223 | assertTrue(Arrays.equals(bytes1, bytes2));
224 | }
225 | }
226 |
227 | @Test
228 | public void toByteArrayFromStringNormalizedTest() throws Exception {
229 | String str1 = "\u00C1";
230 | String str2 = "\u0041\u0301";
231 |
232 | {
233 | ByteArray byteArray1 = new ByteArrayImpl.CharSeqByteArrayImpl(str1, StandardCharsets.UTF_8);
234 | byte[] bytes1 = byteArray1.getBytes();
235 |
236 | ByteArray byteArray2 = new ByteArrayImpl.CharSeqByteArrayImpl(str2, StandardCharsets.UTF_8);
237 | byte[] bytes2 = byteArray2.getBytes();
238 |
239 | assertFalse(Arrays.equals(bytes1, bytes2));
240 | }
241 |
242 | {
243 | ByteArray byteArray1 = new ByteArrayImpl.CharSeqByteArrayImpl(str1, StandardCharsets.UTF_8).normalize();
244 | byte[] bytes1 = byteArray1.getBytes();
245 |
246 | ByteArray byteArray2 = new ByteArrayImpl.CharSeqByteArrayImpl(str2, StandardCharsets.UTF_8).normalize();
247 | byte[] bytes2 = byteArray2.getBytes();
248 |
249 | assertTrue(Arrays.equals(bytes1, bytes2));
250 | }
251 | }
252 |
253 | @Test
254 | public void toByteArrayFromReaderNormalizedTest() throws Exception {
255 | char[] chars1 = "\u00C1".toCharArray();
256 | char[] chars2 = "\u0041\u0301".toCharArray();
257 |
258 | {
259 | ByteArray byteArray1 = new ByteArrayImpl.CharSeqByteArrayImpl(new CharArrayReader(chars1), 64, StandardCharsets.UTF_8);
260 | byte[] bytes1 = byteArray1.getBytes();
261 |
262 | ByteArray byteArray2 = new ByteArrayImpl.CharSeqByteArrayImpl(new CharArrayReader(chars2), 64, StandardCharsets.UTF_8);
263 | byte[] bytes2 = byteArray2.getBytes();
264 |
265 | assertFalse(Arrays.equals(bytes1, bytes2));
266 | }
267 |
268 | {
269 | ByteArray byteArray1 = new ByteArrayImpl.CharSeqByteArrayImpl(new CharArrayReader(chars1), 64, StandardCharsets.UTF_8).normalize();
270 | byte[] bytes1 = byteArray1.getBytes();
271 |
272 | ByteArray byteArray2 = new ByteArrayImpl.CharSeqByteArrayImpl(new CharArrayReader(chars2), 64, StandardCharsets.UTF_8).normalize();
273 | byte[] bytes2 = byteArray2.getBytes();
274 |
275 | assertTrue(Arrays.equals(bytes1, bytes2));
276 | }
277 | }
278 |
279 | @Test
280 | public void byteArrayClearSourceTest() throws Exception {
281 | byte[] referenceBytes = "12345".getBytes();
282 | {
283 | byte[] source = Arrays.copyOf(referenceBytes, referenceBytes.length);
284 | byte[] copy;
285 |
286 | try (ByteArray byteArray = new ByteArrayImpl.ClearableSourceByteArrayImpl(source)) {
287 | copy = byteArray.getBytes();
288 |
289 | assertTrue(Arrays.equals(source, copy));
290 | }
291 |
292 | byte[] zeros = new byte[copy.length];
293 | assertTrue(Arrays.equals(zeros, copy));
294 | assertTrue(Arrays.equals(referenceBytes, source));
295 | }
296 |
297 | {
298 | byte[] source = Arrays.copyOf(referenceBytes, referenceBytes.length);
299 | byte[] copy;
300 |
301 | try (ByteArray byteArray = new ByteArrayImpl.ClearableSourceByteArrayImpl(source).clearSource()) {
302 | copy = byteArray.getBytes();
303 |
304 | assertTrue(Arrays.equals(source, copy));
305 | }
306 |
307 | byte[] zeros = new byte[copy.length];
308 | assertTrue(Arrays.equals(zeros, copy));
309 | assertTrue(Arrays.equals(zeros, source));
310 | }
311 |
312 | String referenceString = "Φούμπαρ";
313 |
314 | {
315 | char[] source = referenceString.toCharArray();
316 | byte[] copy;
317 |
318 | try(ByteArray byteArray = new ByteArrayImpl.ClearableSourceCharSeqByteArrayImpl(source, Jargon2.DEFAULT_ENCODING).encoding("ISO8859_7")) {
319 | copy = byteArray.getBytes();
320 |
321 | assertTrue(Arrays.equals(new String(source).getBytes("ISO8859_7"), copy));
322 | }
323 |
324 | byte[] zeros = new byte[copy.length];
325 | assertTrue(Arrays.equals(zeros, copy));
326 | assertTrue(Arrays.equals(referenceString.toCharArray(), source));
327 | }
328 |
329 | {
330 | char[] source = referenceString.toCharArray();
331 | byte[] copy;
332 |
333 | try(ByteArray byteArray = new ByteArrayImpl.ClearableSourceCharSeqByteArrayImpl(source, Jargon2.DEFAULT_ENCODING).encoding("ISO8859_7").clearSource()) {
334 | copy = byteArray.getBytes();
335 |
336 | assertTrue(Arrays.equals(new String(source).getBytes("ISO8859_7"), copy));
337 | }
338 |
339 | byte[] zeroBytes = new byte[copy.length];
340 | assertTrue(Arrays.equals(zeroBytes, copy));
341 | char[] zeroChars = new char[copy.length];
342 | assertTrue(Arrays.equals(zeroChars, source));
343 | }
344 | }
345 |
346 | @Test
347 | public void finalizableByteArrayTest() throws Exception {
348 |
349 | {
350 | byte[] bytes = new byte[] { 0x01 };
351 | // non-finalizable: copyBytes stays as-is after GC
352 | byte[] copyBytes = new ByteArrayImpl(new ByteArrayInputStream(bytes), bytes.length).getBytes();
353 | assertTrue(Arrays.equals(bytes, copyBytes));
354 | System.gc();
355 | System.runFinalization();
356 | assertTrue(Arrays.equals(bytes, copyBytes));
357 | }
358 |
359 | {
360 | byte[] bytes = new byte[] { 0x01 };
361 | // finalizable: copyBytes are wiped out after GC
362 | byte[] copyBytes = new ByteArrayImpl(new ByteArrayInputStream(bytes), bytes.length).finalizable().getBytes();
363 | assertTrue(Arrays.equals(bytes, copyBytes));
364 | System.gc();
365 | System.runFinalization();
366 | assertFalse(Arrays.equals(bytes, copyBytes));
367 | assertEquals(0x00, copyBytes[0]);
368 | }
369 |
370 | {
371 | byte[] bytes = new byte[] { 0x01 };
372 | // non-finalizable: copyBytes and bytes stays as-is after GC
373 | byte[] copyBytes = new ByteArrayImpl.ClearableSourceByteArrayImpl(bytes).clearSource().getBytes();
374 | assertTrue(Arrays.equals(bytes, copyBytes));
375 | System.gc();
376 | System.runFinalization();
377 | assertTrue(Arrays.equals(bytes, copyBytes));
378 | }
379 |
380 | {
381 | byte[] bytes = new byte[] { 0x01 };
382 | // finalizable: copyBytes and bytes are wiped out after GC
383 | byte[] copyBytes = new ByteArrayImpl.ClearableSourceByteArrayImpl(bytes).clearSource().finalizable().getBytes();
384 | assertTrue(Arrays.equals(bytes, copyBytes));
385 | System.gc();
386 | System.runFinalization();
387 | assertEquals(0x00, bytes[0]);
388 | assertEquals(0x00, copyBytes[0]);
389 | }
390 |
391 | {
392 | char c = 'a';
393 | byte b = (byte) c;
394 | char[] chars = new char[] { c };
395 | // non-finalizable: copyBytes and chars stays as-is after GC
396 | byte[] copyBytes = new ByteArrayImpl.ClearableSourceCharSeqByteArrayImpl(chars, StandardCharsets.UTF_8).clearSource().getBytes();
397 | assertEquals(c, chars[0]);
398 | assertEquals(b, copyBytes[0]);
399 | System.gc();
400 | System.runFinalization();
401 | assertEquals(c, chars[0]);
402 | assertEquals(b, copyBytes[0]);
403 | }
404 |
405 | {
406 | char c = 'a';
407 | byte b = (byte) c;
408 | char[] chars = new char[] { c };
409 | // finalizable: copyBytes and chars are wiped out after GC
410 | byte[] copyBytes = new ByteArrayImpl.ClearableSourceCharSeqByteArrayImpl(chars, StandardCharsets.UTF_8).clearSource().finalizable().getBytes();
411 | assertEquals(c, chars[0]);
412 | assertEquals(b, copyBytes[0]);
413 | System.gc();
414 | System.runFinalization();
415 | assertEquals(0, chars[0]);
416 | assertEquals(0x00, copyBytes[0]);
417 | }
418 | }
419 | }
--------------------------------------------------------------------------------
/src/test/java/com/kosprov/jargon2/internal/discovery/Jargon2BackendDiscoveryErrorTest.java:
--------------------------------------------------------------------------------
1 | package com.kosprov.jargon2.internal.discovery;
2 |
3 | import com.kosprov.jargon2.api.Jargon2;
4 | import com.kosprov.jargon2.api.Jargon2Exception;
5 | import com.kosprov.jargon2.spi.Jargon2Backend;
6 | import org.junit.Test;
7 |
8 | import java.util.Map;
9 |
10 | public class Jargon2BackendDiscoveryErrorTest {
11 |
12 | @Test(expected = Jargon2BackendDiscoveryException.class)
13 | public void errorOnMoreThanOneBackendTest() {
14 | System.setProperty("com.kosprov.jargon2.spi.backend", AnotherJargon2Backend.class.getName());
15 | try {
16 | Jargon2BackendDiscovery.INSTANCE.getJargon2Backend();
17 | } finally {
18 | System.setProperty("com.kosprov.jargon2.spi.backend", "");
19 | }
20 | }
21 |
22 | @Test(expected = Jargon2BackendDiscoveryException.class)
23 | public void errorOnInvalidBackendTest() {
24 | System.setProperty("com.kosprov.jargon2.spi.backend", "invalid.backend.class.Name");
25 | try {
26 | Jargon2BackendDiscovery.INSTANCE.getJargon2Backend();
27 | } finally {
28 | System.setProperty("com.kosprov.jargon2.spi.backend", "");
29 | }
30 | }
31 |
32 | public static class AnotherJargon2Backend implements Jargon2Backend {
33 | @Override
34 | public byte[] rawHash(Jargon2.Type type, Jargon2.Version version, int memoryCost, int timeCost, int lanes, int threads, int hashLength, byte[] secret, byte[] ad, byte[] salt, byte[] password, Map options) throws Jargon2Exception {
35 | return new byte[0];
36 | }
37 |
38 | @Override
39 | public String encodedHash(Jargon2.Type type, Jargon2.Version version, int memoryCost, int timeCost, int lanes, int threads, int hashLength, byte[] secret, byte[] ad, byte[] salt, byte[] password, Map options) throws Jargon2Exception {
40 | return null;
41 | }
42 |
43 | @Override
44 | public boolean verifyRaw(Jargon2.Type type, Jargon2.Version version, int memoryCost, int timeCost, int lanes, int threads, byte[] rawHash, byte[] secret, byte[] ad, byte[] salt, byte[] password, Map options) throws Jargon2Exception {
45 | return false;
46 | }
47 |
48 | @Override
49 | public boolean verifyEncoded(String encodedHash, int threads, byte[] secret, byte[] ad, byte[] password, Map options) throws Jargon2Exception {
50 | return false;
51 | }
52 | }
53 |
54 | }
--------------------------------------------------------------------------------
/src/test/java/com/kosprov/jargon2/internal/discovery/Jargon2BackendDiscoveryTest.java:
--------------------------------------------------------------------------------
1 | package com.kosprov.jargon2.internal.discovery;
2 |
3 | import com.kosprov.jargon2.spi.Jargon2Backend;
4 | import org.junit.Test;
5 |
6 | import static org.junit.Assert.assertNotNull;
7 |
8 | public class Jargon2BackendDiscoveryTest {
9 |
10 | @Test
11 | public void defaultTest() {
12 | Jargon2Backend backend = Jargon2BackendDiscovery.INSTANCE.getJargon2Backend();
13 | assertNotNull(backend);
14 | }
15 |
16 | }
--------------------------------------------------------------------------------
/src/test/resources/META-INF/services/com.kosprov.jargon2.spi.Jargon2Backend:
--------------------------------------------------------------------------------
1 | com.kosprov.jargon2.api.DummyJargon2Backend
--------------------------------------------------------------------------------