From 2b718d4747d42f0b84c16a26df68d430a5101875 Mon Sep 17 00:00:00 2001 From: Samuel Bushi Date: Wed, 2 Feb 2022 18:00:46 -0500 Subject: [PATCH 1/9] Add Argon2 --- .../com/google/firebase/auth/hash/Argon2.java | 154 ++++++++++++++++++ .../firebase/auth/UserImportHashTest.java | 32 ++++ .../firebase/auth/hash/InvalidHashTest.java | 68 ++++++++ 3 files changed, 254 insertions(+) create mode 100644 src/main/java/com/google/firebase/auth/hash/Argon2.java diff --git a/src/main/java/com/google/firebase/auth/hash/Argon2.java b/src/main/java/com/google/firebase/auth/hash/Argon2.java new file mode 100644 index 000000000..9d137fea9 --- /dev/null +++ b/src/main/java/com/google/firebase/auth/hash/Argon2.java @@ -0,0 +1,154 @@ +/* + * Copyright 2022 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.firebase.auth.hash; + +import static com.google.common.base.Preconditions.checkArgument; + +import com.google.common.collect.ImmutableMap; +import com.google.firebase.auth.UserImportHash; +import java.util.Map; + +/** + * Represents the Argon2 password hashing algorithm. Can be used as an instance of {@link + * com.google.firebase.auth.UserImportHash} when importing users. + */ +public final class Argon2 extends UserImportHash { + + private final int hashLengthBytes; + private final Argon2HashType hashType; + private final int parallelism; + private final int iterations; + private final int memoryCostKib; + private final Argon2Version version; + private final byte[] associatedData; + + private Argon2(Builder builder) { + super("ARGON2"); + checkArgument(builder.hashLengthBytes >= 4 && builder.hashLengthBytes <= 1024, + "hashLengthBytes is required for Argon2 and must be between 4 and 1024"); + checkArgument(builder.hashType != null, + "A hashType is required for Argon2"); + checkArgument(builder.parallelism >= 1 && builder.parallelism <= 16, + "parallelism is required for Argon2 and must be between 1 and 16"); + checkArgument(builder.iterations >= 1 && builder.iterations <= 16, + "iterations is required for Argon2 and must be between 1 and 16"); + checkArgument(builder.memoryCostKib > 0 && builder.memoryCostKib <= 32768, + "memoryCostKib is required for Argon2 and must be less than or equal to 32768"); + this.hashLengthBytes = builder.hashLengthBytes; + this.hashType = builder.hashType; + this.parallelism = builder.parallelism; + this.iterations = builder.iterations; + this.memoryCostKib = builder.memoryCostKib; + if (builder.version != null) { + this.version = builder.version; + } else { + this.version = Argon2Version.VERSION_13; + } + this.associatedData = builder.associatedData; + } + + @Override + protected Map getOptions() { + return ImmutableMap.builder() + .put("hashLengthBytes", hashLengthBytes) + .put("hashType", hashType) + .put("parallelism", parallelism) + .put("iterations", iterations) + .put("memoryCostKib", memoryCostKib) + .put("version", version.getValue()) + .put("associatedData", associatedData) + .build(); + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + + private int hashLengthBytes; + private Argon2HashType hashType; + private int parallelism; + private int iterations; + private int memoryCostKib; + private Argon2Version version; + private byte[] associatedData; + + private Builder() { + } + + public Builder setHashLengthBytes(int hashLengthBytes) { + this.hashLengthBytes = hashLengthBytes; + return this; + } + + public Builder setHashType(Argon2HashType hashType) { + this.hashType = hashType; + return this; + } + + public Builder setParallelism(int parallelism) { + this.parallelism = parallelism; + return this; + } + + public Builder setIterations(int iterations) { + this.iterations = iterations; + return this; + } + + public Builder setMemoryCostKib(int memoryCostKib) { + this.memoryCostKib = memoryCostKib; + return this; + } + + public Builder setVersion(Argon2Version version) { + this.version = version; + return this; + } + + public Builder setAssociatedData(byte[] associatedData) { + this.associatedData = associatedData; + return this; + } + + public Argon2 build() { + return new Argon2(this); + } + } + + public enum Argon2HashType { + ARGON2_D, + ARGON2_ID, + ARGON2_I + } + + public enum Argon2Version { + VERSION_10(0x10), + VERSION_13(0x13); + + private final int value; + + Argon2Version(final int newValue) { + value = newValue; + } + + public int getValue() { + return value; + } + } +} \ No newline at end of file diff --git a/src/test/java/com/google/firebase/auth/UserImportHashTest.java b/src/test/java/com/google/firebase/auth/UserImportHashTest.java index e4a3af14a..52c428c4a 100644 --- a/src/test/java/com/google/firebase/auth/UserImportHashTest.java +++ b/src/test/java/com/google/firebase/auth/UserImportHashTest.java @@ -20,6 +20,9 @@ import com.google.common.collect.ImmutableMap; import com.google.common.io.BaseEncoding; +import com.google.firebase.auth.hash.Argon2; +import com.google.firebase.auth.hash.Argon2.Argon2HashType; +import com.google.firebase.auth.hash.Argon2.Argon2Version; import com.google.firebase.auth.hash.Bcrypt; import com.google.firebase.auth.hash.HmacMd5; import com.google.firebase.auth.hash.HmacSha1; @@ -42,6 +45,7 @@ public class UserImportHashTest { private static final byte[] SALT_SEPARATOR = "separator".getBytes(); private static class MockHash extends UserImportHash { + MockHash() { super("MOCK_HASH"); } @@ -109,6 +113,34 @@ public void testStandardScryptHash() { assertEquals(properties, scrypt.getProperties()); } + @Test + public void testArgon2Hash() { + /* base64 encoded {"hello": "world"} */ + byte[] associatedData = "eyJoZWxsbyI6ICJ3b3JsZCJ9".getBytes(); + + UserImportHash argon2 = Argon2.builder() + .setHashLengthBytes(512) + .setHashType(Argon2HashType.ARGON2_ID) + .setParallelism(8) + .setIterations(16) + .setMemoryCostKib(512) + .setVersion(Argon2Version.VERSION_10) + .setAssociatedData(associatedData) + .build(); + + Map properties = ImmutableMap.builder() + .put("hashAlgorithm", "ARGON2") + .put("hashLengthBytes", 512) + .put("hashType", "ARGON2_ID") + .put("parallelism", 8) + .put("iterations", 16) + .put("memoryCostKib", 512) + .put("version", 0x10) + .put("associatedData", associatedData) + .build(); + assertEquals(properties, argon2.getProperties()); + } + @Test public void testHmacHash() { Map hashes = ImmutableMap.of( diff --git a/src/test/java/com/google/firebase/auth/hash/InvalidHashTest.java b/src/test/java/com/google/firebase/auth/hash/InvalidHashTest.java index 5186a443a..653bb8147 100644 --- a/src/test/java/com/google/firebase/auth/hash/InvalidHashTest.java +++ b/src/test/java/com/google/firebase/auth/hash/InvalidHashTest.java @@ -20,6 +20,7 @@ import static org.junit.Assert.fail; import com.google.common.collect.ImmutableList; +import com.google.firebase.auth.hash.Argon2.Argon2HashType; import java.util.List; import org.junit.Test; @@ -124,4 +125,71 @@ public void testInvalidScrypt() { } } } + + @Test + public void testInvalidArgon2() { + List builders = ImmutableList.of( + Argon2.builder() // hashLengthBytes < 4 + .setHashLengthBytes(2) + .setHashType(Argon2HashType.ARGON2_ID) + .setParallelism(8) + .setIterations(16) + .setMemoryCostKib(512), + Argon2.builder() // hashLengthBytes > 1024 + .setHashLengthBytes(2048) + .setHashType(Argon2HashType.ARGON2_ID) + .setParallelism(8) + .setIterations(16) + .setMemoryCostKib(512), + Argon2.builder() // missing hashType + .setHashLengthBytes(32) + .setParallelism(8) + .setIterations(16) + .setMemoryCostKib(512), + Argon2.builder() // parallelism < 1 + .setHashType(Argon2HashType.ARGON2_ID) + .setParallelism(0) + .setHashLengthBytes(32) + .setIterations(16) + .setMemoryCostKib(512), + Argon2.builder() // parallelism > 16 + .setHashType(Argon2HashType.ARGON2_ID) + .setParallelism(32) + .setHashLengthBytes(32) + .setIterations(16) + .setMemoryCostKib(512), + Argon2.builder() // iterations < 1 + .setHashType(Argon2HashType.ARGON2_ID) + .setParallelism(16) + .setHashLengthBytes(32) + .setIterations(0) + .setMemoryCostKib(512), + Argon2.builder() // iterations > 16 + .setHashType(Argon2HashType.ARGON2_ID) + .setParallelism(16) + .setHashLengthBytes(32) + .setIterations(32) + .setMemoryCostKib(512), + Argon2.builder() // memoryCostKib < 0 + .setHashType(Argon2HashType.ARGON2_ID) + .setParallelism(16) + .setHashLengthBytes(32) + .setIterations(8) + .setMemoryCostKib(-1), + Argon2.builder() // memoryCostKib > 32768 + .setHashType(Argon2HashType.ARGON2_ID) + .setParallelism(16) + .setHashLengthBytes(32) + .setIterations(8) + .setMemoryCostKib(99999) + ); + for (Argon2.Builder builder : builders) { + try { + builder.build(); + fail("No error thrown for invalid configuration"); + } catch (IllegalArgumentException expected) { + // expected + } + } + } } From 58982f777f984b5537c866844434b53d44492f39 Mon Sep 17 00:00:00 2001 From: Samuel Bushi Date: Thu, 3 Feb 2022 10:52:02 -0500 Subject: [PATCH 2/9] More checks for associatedData --- .../com/google/firebase/auth/hash/Argon2.java | 18 +++++++++++++----- .../firebase/auth/UserImportHashTest.java | 4 ++-- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/google/firebase/auth/hash/Argon2.java b/src/main/java/com/google/firebase/auth/hash/Argon2.java index 9d137fea9..7b7076aad 100644 --- a/src/main/java/com/google/firebase/auth/hash/Argon2.java +++ b/src/main/java/com/google/firebase/auth/hash/Argon2.java @@ -19,8 +19,10 @@ import static com.google.common.base.Preconditions.checkArgument; import com.google.common.collect.ImmutableMap; +import com.google.common.io.BaseEncoding; import com.google.firebase.auth.UserImportHash; import java.util.Map; +import javax.annotation.Nullable; /** * Represents the Argon2 password hashing algorithm. Can be used as an instance of {@link @@ -58,20 +60,26 @@ private Argon2(Builder builder) { } else { this.version = Argon2Version.VERSION_13; } - this.associatedData = builder.associatedData; + if (builder.associatedData != null) { + this.associatedData = builder.associatedData; + } else { + this.associatedData = null; + } } @Override protected Map getOptions() { - return ImmutableMap.builder() + ImmutableMap.Builder builder = ImmutableMap.builder() .put("hashLengthBytes", hashLengthBytes) .put("hashType", hashType) .put("parallelism", parallelism) .put("iterations", iterations) .put("memoryCostKib", memoryCostKib) - .put("version", version.getValue()) - .put("associatedData", associatedData) - .build(); + .put("version", version.getValue()); + if (this.associatedData != null) { + builder.put("associatedData", associatedData); + } + return builder.build(); } public static Builder builder() { diff --git a/src/test/java/com/google/firebase/auth/UserImportHashTest.java b/src/test/java/com/google/firebase/auth/UserImportHashTest.java index 52c428c4a..798f7f70d 100644 --- a/src/test/java/com/google/firebase/auth/UserImportHashTest.java +++ b/src/test/java/com/google/firebase/auth/UserImportHashTest.java @@ -125,7 +125,7 @@ public void testArgon2Hash() { .setIterations(16) .setMemoryCostKib(512) .setVersion(Argon2Version.VERSION_10) - .setAssociatedData(associatedData) + // .setAssociatedData(associatedData) .build(); Map properties = ImmutableMap.builder() @@ -136,7 +136,7 @@ public void testArgon2Hash() { .put("iterations", 16) .put("memoryCostKib", 512) .put("version", 0x10) - .put("associatedData", associatedData) + // .put("associatedData", associatedData) .build(); assertEquals(properties, argon2.getProperties()); } From e75839eb1d3481babd5764fcd9ccb289ab090035 Mon Sep 17 00:00:00 2001 From: Samuel Bushi Date: Mon, 7 Feb 2022 12:18:37 -0500 Subject: [PATCH 3/9] Use String for associated data, add another test --- .../com/google/firebase/auth/hash/Argon2.java | 24 ++++-------- .../firebase/auth/UserImportHashTest.java | 37 +++++++++++++++---- 2 files changed, 37 insertions(+), 24 deletions(-) diff --git a/src/main/java/com/google/firebase/auth/hash/Argon2.java b/src/main/java/com/google/firebase/auth/hash/Argon2.java index 7b7076aad..cc753fb2c 100644 --- a/src/main/java/com/google/firebase/auth/hash/Argon2.java +++ b/src/main/java/com/google/firebase/auth/hash/Argon2.java @@ -22,7 +22,6 @@ import com.google.common.io.BaseEncoding; import com.google.firebase.auth.UserImportHash; import java.util.Map; -import javax.annotation.Nullable; /** * Represents the Argon2 password hashing algorithm. Can be used as an instance of {@link @@ -36,7 +35,7 @@ public final class Argon2 extends UserImportHash { private final int iterations; private final int memoryCostKib; private final Argon2Version version; - private final byte[] associatedData; + private final String associatedData; private Argon2(Builder builder) { super("ARGON2"); @@ -58,10 +57,11 @@ private Argon2(Builder builder) { if (builder.version != null) { this.version = builder.version; } else { + /* Default to VERSION_13 */ this.version = Argon2Version.VERSION_13; } if (builder.associatedData != null) { - this.associatedData = builder.associatedData; + this.associatedData = BaseEncoding.base64Url().encode(builder.associatedData); } else { this.associatedData = null; } @@ -71,11 +71,11 @@ private Argon2(Builder builder) { protected Map getOptions() { ImmutableMap.Builder builder = ImmutableMap.builder() .put("hashLengthBytes", hashLengthBytes) - .put("hashType", hashType) + .put("hashType", hashType.toString()) .put("parallelism", parallelism) .put("iterations", iterations) .put("memoryCostKib", memoryCostKib) - .put("version", version.getValue()); + .put("version", version.toString()); if (this.associatedData != null) { builder.put("associatedData", associatedData); } @@ -146,17 +146,7 @@ public enum Argon2HashType { } public enum Argon2Version { - VERSION_10(0x10), - VERSION_13(0x13); - - private final int value; - - Argon2Version(final int newValue) { - value = newValue; - } - - public int getValue() { - return value; - } + VERSION_10, + VERSION_13 } } \ No newline at end of file diff --git a/src/test/java/com/google/firebase/auth/UserImportHashTest.java b/src/test/java/com/google/firebase/auth/UserImportHashTest.java index 798f7f70d..65297e058 100644 --- a/src/test/java/com/google/firebase/auth/UserImportHashTest.java +++ b/src/test/java/com/google/firebase/auth/UserImportHashTest.java @@ -17,8 +17,11 @@ package com.google.firebase.auth; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.MapDifference; +import com.google.common.collect.Maps; import com.google.common.io.BaseEncoding; import com.google.firebase.auth.hash.Argon2; import com.google.firebase.auth.hash.Argon2.Argon2HashType; @@ -114,10 +117,7 @@ public void testStandardScryptHash() { } @Test - public void testArgon2Hash() { - /* base64 encoded {"hello": "world"} */ - byte[] associatedData = "eyJoZWxsbyI6ICJ3b3JsZCJ9".getBytes(); - + public void testArgon2Hash_withoutAssociatedData() { UserImportHash argon2 = Argon2.builder() .setHashLengthBytes(512) .setHashType(Argon2HashType.ARGON2_ID) @@ -125,7 +125,6 @@ public void testArgon2Hash() { .setIterations(16) .setMemoryCostKib(512) .setVersion(Argon2Version.VERSION_10) - // .setAssociatedData(associatedData) .build(); Map properties = ImmutableMap.builder() @@ -135,8 +134,32 @@ public void testArgon2Hash() { .put("parallelism", 8) .put("iterations", 16) .put("memoryCostKib", 512) - .put("version", 0x10) - // .put("associatedData", associatedData) + .put("version", "VERSION_10") + .build(); + assertEquals(properties, argon2.getProperties()); + } + + @Test + public void testArgon2Hash_withAssociatedData() { + byte[] associatedData = "associatedData".getBytes(); + UserImportHash argon2 = Argon2.builder() + .setHashLengthBytes(512) + .setHashType(Argon2HashType.ARGON2_ID) + .setParallelism(8) + .setIterations(16) + .setMemoryCostKib(512) + .setAssociatedData(associatedData) + .build(); + + Map properties = ImmutableMap.builder() + .put("hashAlgorithm", "ARGON2") + .put("hashLengthBytes", 512) + .put("hashType", "ARGON2_ID") + .put("parallelism", 8) + .put("iterations", 16) + .put("memoryCostKib", 512) + .put("version", "VERSION_13") + .put("associatedData", BaseEncoding.base64Url().encode(associatedData)) .build(); assertEquals(properties, argon2.getProperties()); } From 79dd9c626620bd149a39b823b58f19a9db7ba300 Mon Sep 17 00:00:00 2001 From: Samuel Bushi Date: Mon, 7 Feb 2022 14:36:03 -0500 Subject: [PATCH 4/9] Removed default handling for Version in Argon2 --- src/main/java/com/google/firebase/auth/hash/Argon2.java | 9 +++++---- .../com/google/firebase/auth/UserImportHashTest.java | 5 ++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/google/firebase/auth/hash/Argon2.java b/src/main/java/com/google/firebase/auth/hash/Argon2.java index cc753fb2c..b98ab2159 100644 --- a/src/main/java/com/google/firebase/auth/hash/Argon2.java +++ b/src/main/java/com/google/firebase/auth/hash/Argon2.java @@ -57,8 +57,7 @@ private Argon2(Builder builder) { if (builder.version != null) { this.version = builder.version; } else { - /* Default to VERSION_13 */ - this.version = Argon2Version.VERSION_13; + this.version = null; } if (builder.associatedData != null) { this.associatedData = BaseEncoding.base64Url().encode(builder.associatedData); @@ -74,11 +73,13 @@ protected Map getOptions() { .put("hashType", hashType.toString()) .put("parallelism", parallelism) .put("iterations", iterations) - .put("memoryCostKib", memoryCostKib) - .put("version", version.toString()); + .put("memoryCostKib", memoryCostKib); if (this.associatedData != null) { builder.put("associatedData", associatedData); } + if (this.version != null) { + builder.put("version", version.toString()); + } return builder.build(); } diff --git a/src/test/java/com/google/firebase/auth/UserImportHashTest.java b/src/test/java/com/google/firebase/auth/UserImportHashTest.java index 65297e058..30f5e60d8 100644 --- a/src/test/java/com/google/firebase/auth/UserImportHashTest.java +++ b/src/test/java/com/google/firebase/auth/UserImportHashTest.java @@ -117,7 +117,7 @@ public void testStandardScryptHash() { } @Test - public void testArgon2Hash_withoutAssociatedData() { + public void testArgon2Hash_withoutAssociatedDataWithVersion() { UserImportHash argon2 = Argon2.builder() .setHashLengthBytes(512) .setHashType(Argon2HashType.ARGON2_ID) @@ -140,7 +140,7 @@ public void testArgon2Hash_withoutAssociatedData() { } @Test - public void testArgon2Hash_withAssociatedData() { + public void testArgon2Hash_withAssociatedDataWithoutVersion() { byte[] associatedData = "associatedData".getBytes(); UserImportHash argon2 = Argon2.builder() .setHashLengthBytes(512) @@ -158,7 +158,6 @@ public void testArgon2Hash_withAssociatedData() { .put("parallelism", 8) .put("iterations", 16) .put("memoryCostKib", 512) - .put("version", "VERSION_13") .put("associatedData", BaseEncoding.base64Url().encode(associatedData)) .build(); assertEquals(properties, argon2.getProperties()); From dd6cddc347072e0c31db07b682d31164a621cda2 Mon Sep 17 00:00:00 2001 From: Samuel Bushi Date: Mon, 7 Feb 2022 14:37:43 -0500 Subject: [PATCH 5/9] lint fix --- src/main/java/com/google/firebase/auth/hash/Argon2.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/google/firebase/auth/hash/Argon2.java b/src/main/java/com/google/firebase/auth/hash/Argon2.java index b98ab2159..cd4d7d0e0 100644 --- a/src/main/java/com/google/firebase/auth/hash/Argon2.java +++ b/src/main/java/com/google/firebase/auth/hash/Argon2.java @@ -150,4 +150,4 @@ public enum Argon2Version { VERSION_10, VERSION_13 } -} \ No newline at end of file +} From cd35c547bcc8f3d749af4bdf8be4c78cb7505935 Mon Sep 17 00:00:00 2001 From: Samuel Bushi Date: Mon, 7 Feb 2022 14:43:37 -0500 Subject: [PATCH 6/9] more tests --- .../firebase/auth/UserImportHashTest.java | 56 ++++++++++++++++--- 1 file changed, 49 insertions(+), 7 deletions(-) diff --git a/src/test/java/com/google/firebase/auth/UserImportHashTest.java b/src/test/java/com/google/firebase/auth/UserImportHashTest.java index 30f5e60d8..931889c64 100644 --- a/src/test/java/com/google/firebase/auth/UserImportHashTest.java +++ b/src/test/java/com/google/firebase/auth/UserImportHashTest.java @@ -17,11 +17,8 @@ package com.google.firebase.auth; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; import com.google.common.collect.ImmutableMap; -import com.google.common.collect.MapDifference; -import com.google.common.collect.Maps; import com.google.common.io.BaseEncoding; import com.google.firebase.auth.hash.Argon2; import com.google.firebase.auth.hash.Argon2.Argon2HashType; @@ -46,9 +43,9 @@ public class UserImportHashTest { private static final byte[] SIGNER_KEY = "key".getBytes(); private static final byte[] SALT_SEPARATOR = "separator".getBytes(); + private static final byte[] ARGON2_ASSOCIATED_DATA = "associatedData".getBytes(); private static class MockHash extends UserImportHash { - MockHash() { super("MOCK_HASH"); } @@ -141,14 +138,13 @@ public void testArgon2Hash_withoutAssociatedDataWithVersion() { @Test public void testArgon2Hash_withAssociatedDataWithoutVersion() { - byte[] associatedData = "associatedData".getBytes(); UserImportHash argon2 = Argon2.builder() .setHashLengthBytes(512) .setHashType(Argon2HashType.ARGON2_ID) .setParallelism(8) .setIterations(16) .setMemoryCostKib(512) - .setAssociatedData(associatedData) + .setAssociatedData(ARGON2_ASSOCIATED_DATA) .build(); Map properties = ImmutableMap.builder() @@ -158,7 +154,53 @@ public void testArgon2Hash_withAssociatedDataWithoutVersion() { .put("parallelism", 8) .put("iterations", 16) .put("memoryCostKib", 512) - .put("associatedData", BaseEncoding.base64Url().encode(associatedData)) + .put("associatedData", BaseEncoding.base64Url().encode(ARGON2_ASSOCIATED_DATA)) + .build(); + assertEquals(properties, argon2.getProperties()); + } + + @Test + public void testArgon2Hash_withAssociatedDataAndVersion() { + UserImportHash argon2 = Argon2.builder() + .setHashLengthBytes(512) + .setHashType(Argon2HashType.ARGON2_ID) + .setParallelism(8) + .setIterations(16) + .setMemoryCostKib(512) + .setAssociatedData(ARGON2_ASSOCIATED_DATA) + .setVersion(Argon2Version.VERSION_10) + .build(); + + Map properties = ImmutableMap.builder() + .put("hashAlgorithm", "ARGON2") + .put("hashLengthBytes", 512) + .put("hashType", "ARGON2_ID") + .put("parallelism", 8) + .put("iterations", 16) + .put("memoryCostKib", 512) + .put("associatedData", BaseEncoding.base64Url().encode(ARGON2_ASSOCIATED_DATA)) + .put("version", "VERSION_10") + .build(); + assertEquals(properties, argon2.getProperties()); + } + + @Test + public void testArgon2Hash_withoutAssociatedDataAndVersion() { + UserImportHash argon2 = Argon2.builder() + .setHashLengthBytes(512) + .setHashType(Argon2HashType.ARGON2_ID) + .setParallelism(8) + .setIterations(16) + .setMemoryCostKib(2048) + .build(); + + Map properties = ImmutableMap.builder() + .put("hashAlgorithm", "ARGON2") + .put("hashLengthBytes", 512) + .put("hashType", "ARGON2_ID") + .put("parallelism", 8) + .put("iterations", 16) + .put("memoryCostKib", 2048) .build(); assertEquals(properties, argon2.getProperties()); } From 81d62552d1b02fd20fab3e3f9cf5cb5e8bee111c Mon Sep 17 00:00:00 2001 From: Samuel Bushi Date: Mon, 7 Feb 2022 18:16:10 -0500 Subject: [PATCH 7/9] Address comments. Use consts for param limits --- .../com/google/firebase/auth/hash/Argon2.java | 38 +++++++++++++++---- .../firebase/auth/hash/InvalidHashTest.java | 6 +-- 2 files changed, 33 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/google/firebase/auth/hash/Argon2.java b/src/main/java/com/google/firebase/auth/hash/Argon2.java index cd4d7d0e0..31a334452 100644 --- a/src/main/java/com/google/firebase/auth/hash/Argon2.java +++ b/src/main/java/com/google/firebase/auth/hash/Argon2.java @@ -29,6 +29,15 @@ */ public final class Argon2 extends UserImportHash { + private static final int MIN_HASH_LENGTH_BYTES = 4; + private static final int MAX_HASH_LENGTH_BYTES = 1024; + private static final int MIN_PARALLELISM = 1; + private static final int MAX_PARALLELISM = 16; + private static final int MIN_ITERATIONS = 1; + private static final int MAX_ITERATIONS = 16; + private static final int MIN_MEMORY_COST_KIB = 1; + private static final int MAX_MEMORY_COST_KIB = 32768; + private final int hashLengthBytes; private final Argon2HashType hashType; private final int parallelism; @@ -39,16 +48,24 @@ public final class Argon2 extends UserImportHash { private Argon2(Builder builder) { super("ARGON2"); - checkArgument(builder.hashLengthBytes >= 4 && builder.hashLengthBytes <= 1024, - "hashLengthBytes is required for Argon2 and must be between 4 and 1024"); + checkArgument(intShouldBeBetweenLimitsInclusive(builder.hashLengthBytes, MIN_HASH_LENGTH_BYTES, + MAX_HASH_LENGTH_BYTES), + "hashLengthBytes is required for Argon2 and must be between %s and %s", + MIN_HASH_LENGTH_BYTES, MAX_HASH_LENGTH_BYTES); checkArgument(builder.hashType != null, "A hashType is required for Argon2"); - checkArgument(builder.parallelism >= 1 && builder.parallelism <= 16, - "parallelism is required for Argon2 and must be between 1 and 16"); - checkArgument(builder.iterations >= 1 && builder.iterations <= 16, - "iterations is required for Argon2 and must be between 1 and 16"); - checkArgument(builder.memoryCostKib > 0 && builder.memoryCostKib <= 32768, - "memoryCostKib is required for Argon2 and must be less than or equal to 32768"); + checkArgument( + intShouldBeBetweenLimitsInclusive(builder.parallelism, MIN_PARALLELISM, MAX_PARALLELISM), + "parallelism is required for Argon2 and must be between %s and %s", MIN_PARALLELISM, + MAX_PARALLELISM); + checkArgument( + intShouldBeBetweenLimitsInclusive(builder.iterations, MIN_ITERATIONS, MAX_ITERATIONS), + "iterations is required for Argon2 and must be between %s and %s", MIN_ITERATIONS, + MAX_ITERATIONS); + checkArgument(intShouldBeBetweenLimitsInclusive(builder.memoryCostKib, MIN_MEMORY_COST_KIB, + MAX_MEMORY_COST_KIB), + "memoryCostKib is required for Argon2 and must be less than or equal to %s", + MAX_MEMORY_COST_KIB); this.hashLengthBytes = builder.hashLengthBytes; this.hashType = builder.hashType; this.parallelism = builder.parallelism; @@ -66,6 +83,11 @@ private Argon2(Builder builder) { } } + private static boolean intShouldBeBetweenLimitsInclusive(int property, int fromInclusive, + int toInclusive) { + return property >= fromInclusive && property <= toInclusive; + } + @Override protected Map getOptions() { ImmutableMap.Builder builder = ImmutableMap.builder() diff --git a/src/test/java/com/google/firebase/auth/hash/InvalidHashTest.java b/src/test/java/com/google/firebase/auth/hash/InvalidHashTest.java index 653bb8147..8087b8583 100644 --- a/src/test/java/com/google/firebase/auth/hash/InvalidHashTest.java +++ b/src/test/java/com/google/firebase/auth/hash/InvalidHashTest.java @@ -170,18 +170,18 @@ public void testInvalidArgon2() { .setHashLengthBytes(32) .setIterations(32) .setMemoryCostKib(512), - Argon2.builder() // memoryCostKib < 0 + Argon2.builder() // memoryCostKib < 1 .setHashType(Argon2HashType.ARGON2_ID) .setParallelism(16) .setHashLengthBytes(32) .setIterations(8) - .setMemoryCostKib(-1), + .setMemoryCostKib(0), Argon2.builder() // memoryCostKib > 32768 .setHashType(Argon2HashType.ARGON2_ID) .setParallelism(16) .setHashLengthBytes(32) .setIterations(8) - .setMemoryCostKib(99999) + .setMemoryCostKib(32769) ); for (Argon2.Builder builder : builders) { try { From e2e6c267246abf5fbc0461da4c5ea8df45dc3fd3 Mon Sep 17 00:00:00 2001 From: Samuel Bushi Date: Tue, 8 Feb 2022 13:19:34 -0500 Subject: [PATCH 8/9] Add javadoc for the Builder methods --- .../com/google/firebase/auth/hash/Argon2.java | 48 +++++++++++++++++-- 1 file changed, 45 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/google/firebase/auth/hash/Argon2.java b/src/main/java/com/google/firebase/auth/hash/Argon2.java index 31a334452..ee753fd85 100644 --- a/src/main/java/com/google/firebase/auth/hash/Argon2.java +++ b/src/main/java/com/google/firebase/auth/hash/Argon2.java @@ -119,39 +119,81 @@ public static class Builder { private Argon2Version version; private byte[] associatedData; - private Builder() { - } - + private Builder() {} + + /** + * Sets the hash length in bytes. Required field. + * + * @param hashLengthBytes an integer between 4 and 1024 (inclusive). + * @return This builder. + */ public Builder setHashLengthBytes(int hashLengthBytes) { this.hashLengthBytes = hashLengthBytes; return this; } + /** + * Sets the Argon2 hash type. Required field. + * + * @param hashType a value from the {@link Argon2HashType} enum. + * @return This builder. + */ public Builder setHashType(Argon2HashType hashType) { this.hashType = hashType; return this; } + /** + * Sets the degree of parallelism, also called threads or lanes. Required field. + * + * @param parallelism an integer between 1 and 16 (inclusive). + * @return This builder. + */ public Builder setParallelism(int parallelism) { this.parallelism = parallelism; return this; } + /** + * Sets the number of iterations to perform. Required field. + * + * @param iterations an integer between 1 and 16 (inclusive). + * @return This builder. + */ public Builder setIterations(int iterations) { this.iterations = iterations; return this; } + /** + * Sets the memory cost in kibibytes. Required field. + * + * @param memoryCostKib an integer between 1 and 32768 (inclusive). + * @return This builder. + */ public Builder setMemoryCostKib(int memoryCostKib) { this.memoryCostKib = memoryCostKib; return this; } + /** + * Sets the version of the Argon2 algorithm. + * + * @param version a value from the {@link Argon2Version} enum. + * @return This builder. + */ public Builder setVersion(Argon2Version version) { this.version = version; return this; } + /** + * Sets additional associated data, if provided, to append to the hash value for additional + * security. This data is base64 encoded before it is sent to the API. + * + * @param associatedData Associated data as a byte array. + * @return This builder. + */ public Builder setAssociatedData(byte[] associatedData) { this.associatedData = associatedData; return this; From fc8d973368e691d78a7a2389a514fe9b6cb9852e Mon Sep 17 00:00:00 2001 From: Samuel Bushi Date: Fri, 11 Feb 2022 13:00:11 -0500 Subject: [PATCH 9/9] Fix the mapping for Argon2. All parameters must be inside the `argon2Parameters` field --- .../com/google/firebase/auth/hash/Argon2.java | 8 +++--- .../firebase/auth/UserImportHashTest.java | 27 ++++++++++--------- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/src/main/java/com/google/firebase/auth/hash/Argon2.java b/src/main/java/com/google/firebase/auth/hash/Argon2.java index ee753fd85..8edcf11b7 100644 --- a/src/main/java/com/google/firebase/auth/hash/Argon2.java +++ b/src/main/java/com/google/firebase/auth/hash/Argon2.java @@ -90,19 +90,19 @@ private static boolean intShouldBeBetweenLimitsInclusive(int property, int fromI @Override protected Map getOptions() { - ImmutableMap.Builder builder = ImmutableMap.builder() + ImmutableMap.Builder argon2Parameters = ImmutableMap.builder() .put("hashLengthBytes", hashLengthBytes) .put("hashType", hashType.toString()) .put("parallelism", parallelism) .put("iterations", iterations) .put("memoryCostKib", memoryCostKib); if (this.associatedData != null) { - builder.put("associatedData", associatedData); + argon2Parameters.put("associatedData", associatedData); } if (this.version != null) { - builder.put("version", version.toString()); + argon2Parameters.put("version", version.toString()); } - return builder.build(); + return ImmutableMap.of("argon2Parameters", argon2Parameters.build()); } public static Builder builder() { diff --git a/src/test/java/com/google/firebase/auth/UserImportHashTest.java b/src/test/java/com/google/firebase/auth/UserImportHashTest.java index 931889c64..95d85088a 100644 --- a/src/test/java/com/google/firebase/auth/UserImportHashTest.java +++ b/src/test/java/com/google/firebase/auth/UserImportHashTest.java @@ -124,8 +124,7 @@ public void testArgon2Hash_withoutAssociatedDataWithVersion() { .setVersion(Argon2Version.VERSION_10) .build(); - Map properties = ImmutableMap.builder() - .put("hashAlgorithm", "ARGON2") + Map argon2Parameters = ImmutableMap.builder() .put("hashLengthBytes", 512) .put("hashType", "ARGON2_ID") .put("parallelism", 8) @@ -133,7 +132,7 @@ public void testArgon2Hash_withoutAssociatedDataWithVersion() { .put("memoryCostKib", 512) .put("version", "VERSION_10") .build(); - assertEquals(properties, argon2.getProperties()); + assertEquals(getArgon2ParametersMap(argon2Parameters), argon2.getProperties()); } @Test @@ -147,8 +146,7 @@ public void testArgon2Hash_withAssociatedDataWithoutVersion() { .setAssociatedData(ARGON2_ASSOCIATED_DATA) .build(); - Map properties = ImmutableMap.builder() - .put("hashAlgorithm", "ARGON2") + Map argon2Parameters = ImmutableMap.builder() .put("hashLengthBytes", 512) .put("hashType", "ARGON2_ID") .put("parallelism", 8) @@ -156,7 +154,7 @@ public void testArgon2Hash_withAssociatedDataWithoutVersion() { .put("memoryCostKib", 512) .put("associatedData", BaseEncoding.base64Url().encode(ARGON2_ASSOCIATED_DATA)) .build(); - assertEquals(properties, argon2.getProperties()); + assertEquals(getArgon2ParametersMap(argon2Parameters), argon2.getProperties()); } @Test @@ -171,8 +169,7 @@ public void testArgon2Hash_withAssociatedDataAndVersion() { .setVersion(Argon2Version.VERSION_10) .build(); - Map properties = ImmutableMap.builder() - .put("hashAlgorithm", "ARGON2") + Map argon2Parameters = ImmutableMap.builder() .put("hashLengthBytes", 512) .put("hashType", "ARGON2_ID") .put("parallelism", 8) @@ -181,7 +178,7 @@ public void testArgon2Hash_withAssociatedDataAndVersion() { .put("associatedData", BaseEncoding.base64Url().encode(ARGON2_ASSOCIATED_DATA)) .put("version", "VERSION_10") .build(); - assertEquals(properties, argon2.getProperties()); + assertEquals(getArgon2ParametersMap(argon2Parameters), argon2.getProperties()); } @Test @@ -194,15 +191,14 @@ public void testArgon2Hash_withoutAssociatedDataAndVersion() { .setMemoryCostKib(2048) .build(); - Map properties = ImmutableMap.builder() - .put("hashAlgorithm", "ARGON2") + Map argon2Parameters = ImmutableMap.builder() .put("hashLengthBytes", 512) .put("hashType", "ARGON2_ID") .put("parallelism", 8) .put("iterations", 16) .put("memoryCostKib", 2048) .build(); - assertEquals(properties, argon2.getProperties()); + assertEquals(getArgon2ParametersMap(argon2Parameters), argon2.getProperties()); } @Test @@ -247,4 +243,11 @@ public void testBcryptHash() { Map properties = ImmutableMap.of("hashAlgorithm", "BCRYPT"); assertEquals(properties, bcrypt.getProperties()); } + + private static ImmutableMap getArgon2ParametersMap( + Map argon2Parameters) { + return ImmutableMap.of( + "hashAlgorithm", "ARGON2", + "argon2Parameters", argon2Parameters); + } }