├── src
├── test
│ ├── my
│ │ └── custom
│ │ │ └── path
│ │ │ └── to
│ │ │ └── some.properties
│ ├── resources
│ │ └── psw4j.properties
│ ├── com
│ │ └── password4j
│ │ │ ├── PepperGeneratorTest.java
│ │ │ ├── HashBuilderTest.java
│ │ │ ├── HashTest.java
│ │ │ ├── SaltGeneratorTest.java
│ │ │ ├── Blake2bTest.java
│ │ │ ├── PropertyReaderTest.java
│ │ │ ├── SystemCheckTest.java
│ │ │ ├── BalloonHashingFunctionTest.java
│ │ │ ├── StringTest.java
│ │ │ ├── ScryptFunctionTest.java
│ │ │ └── IssuesTest.java
│ └── org
│ │ └── example
│ │ └── project
│ │ └── PublicPasswordTest.java
└── main
│ └── java
│ └── com
│ └── password4j
│ ├── SaltOption.java
│ ├── types
│ ├── Bcrypt.java
│ ├── Argon2.java
│ └── Hmac.java
│ ├── BenchmarkResult.java
│ ├── BadParametersException.java
│ ├── HashUpdate.java
│ ├── PepperGenerator.java
│ ├── SaltGenerator.java
│ ├── SecureString.java
│ ├── AbstractHashingFunction.java
│ ├── PropertyReader.java
│ ├── Password.java
│ ├── MessageDigestFunction.java
│ ├── CompressedPBKDF2Function.java
│ ├── PBKDF2Function.java
│ ├── Blake2b.java
│ ├── HashBuilder.java
│ ├── HashChecker.java
│ └── BalloonHashingFunction.java
├── .github
├── ISSUE_TEMPLATE
│ ├── custom.md
│ ├── bug_report.md
│ └── feature_request.md
├── dependabot.yml
└── workflows
│ ├── build.yml
│ └── codeql-analysis.yml
├── CITATION.cff
├── .gitignore
├── examples
├── Service.java
└── MigrateFromMD5.java
├── SECURITY.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── pom.xml
└── CHANGELOG.md
/src/test/my/custom/path/to/some.properties:
--------------------------------------------------------------------------------
1 | check.this.out=hello!!
2 |
--------------------------------------------------------------------------------
/src/main/java/com/password4j/SaltOption.java:
--------------------------------------------------------------------------------
1 | package com.password4j;
2 |
3 | public enum SaltOption
4 | {
5 | PREPEND, APPEND;
6 | }
7 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/custom.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Custom issue template
3 | about: Describe this issue template's purpose here.
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 |
11 |
--------------------------------------------------------------------------------
/CITATION.cff:
--------------------------------------------------------------------------------
1 | cff-version: 1.2.0
2 | message: "If you use this software, please cite it as below."
3 | authors:
4 | - family-names: "Bertoldi"
5 | given-names: "David"
6 | orcid: "https://orcid.org/0009-0006-8810-6062"
7 | title: "password4j"
8 | version: 1.8.4
9 | doi: 10.5281/zenodo.15778340
10 | date-released: 2025-06-30
11 | url: "https://github.com/Password4j/password4j"
12 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | target/
2 | pom.xml.tag
3 | pom.xml.releaseBackup
4 | pom.xml.versionsBackup
5 | pom.xml.next
6 | release.properties
7 | dependency-reduced-pom.xml
8 | buildNumber.properties
9 | .mvn/timing.properties
10 | # https://github.com/takari/maven-wrapper#usage-without-binary-jar
11 | .mvn/wrapper/maven-wrapper.jar
12 |
13 | .DS_Store
14 | .idea
15 | *.iml
16 | *.orig
17 | [Dd]esktop.ini
18 | Thumbs.db
19 | Thumbs.db:encryptable
20 | ehthumbs.db
21 | ehthumbs_vista.db
22 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: bug
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Describe here the steps to reproduce the behavior
15 |
16 | **Expected behavior**
17 | A clear and concise description of what you expected to happen.
18 |
19 | **Environment:**
20 | - OS: [e.g. iOS]
21 | - JDK [e.g. OracleJDK 12]
22 | - Version [e.g. 1.2.0]
23 |
24 | **Additional context**
25 | Add any other context about the problem here.
26 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # To get started with Dependabot version updates, you'll need to specify which
2 | # package ecosystems to update and where the package manifests are located.
3 | # Please see the documentation for all configuration options:
4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
5 |
6 | version: 2
7 | updates:
8 | - package-ecosystem: "maven" # See documentation for possible values
9 | directory: "/" # Location of package manifests
10 | schedule:
11 | interval: "daily"
12 | labels:
13 | - "priority: low"
14 | - "type: enhancement"
15 | - "status: confirmed"
16 |
17 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: enhancement
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/src/test/resources/psw4j.properties:
--------------------------------------------------------------------------------
1 | global.random.strong=false
2 | global.banner=true
3 | global.pepper=AlicePepper
4 | global.salt.length=16
5 |
6 | hash.md.algorithm=SHA-512
7 | hash.md.salt.option=append
8 |
9 | hash.pbkdf2.algorithm=SHA256
10 | hash.pbkdf2.iterations=64000
11 | hash.pbkdf2.length=256
12 | hash.pbkdf2.delimiter=$
13 |
14 | hash.bcrypt.minor=b
15 | hash.bcrypt.rounds=12
16 |
17 | hash.scrypt.workfactor=16384
18 | hash.scrypt.resources=16
19 | hash.scrypt.parallelization=1
20 |
21 | hash.argon2.memory=1024
22 | hash.argon2.iterations=5
23 | hash.argon2.length=64
24 | hash.argon2.parallelism=3
25 | hash.argon2.type=id
26 | hash.argon2.version=19
27 |
28 | test.int=10
29 | test.string=This is a string
30 | test.bool=true
31 | test.char=\\
--------------------------------------------------------------------------------
/examples/Service.java:
--------------------------------------------------------------------------------
1 | /*
2 | * (C) Copyright 2022 Password4j (http://password4j.com/).
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 |
19 | public class Service
20 | {
21 |
22 | static String getPasswordFromDatabaseForCurrentUser()
23 | {
24 | return "8a0bee289129817329727eb05f9a81a5";
25 | }
26 |
27 | static void storePasswordInDatabase(String hash)
28 | {
29 | return;
30 | }
31 |
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/src/main/java/com/password4j/types/Bcrypt.java:
--------------------------------------------------------------------------------
1 | /*
2 | * (C) Copyright 2021 Password4j (http://password4j.com/).
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package com.password4j.types;
19 |
20 | public enum Bcrypt
21 | {
22 | A, B, X, Y;
23 |
24 | public static Bcrypt valueOf(char minor)
25 | {
26 | for (Bcrypt type : Bcrypt.values())
27 | {
28 | if (type.minor() == minor)
29 | {
30 | return type;
31 | }
32 | }
33 | return null;
34 | }
35 |
36 | public char minor()
37 | {
38 | return name().toLowerCase().charAt(0);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/main/java/com/password4j/BenchmarkResult.java:
--------------------------------------------------------------------------------
1 | /*
2 | * (C) Copyright 2020 Password4j (http://password4j.com/).
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 | package com.password4j;
18 |
19 | public class BenchmarkResult
20 | {
21 |
22 | private final P prototype;
23 |
24 | private final long elapsed;
25 |
26 | BenchmarkResult(P prototype, long elapsed)
27 | {
28 | this.prototype = prototype;
29 | this.elapsed = elapsed;
30 | }
31 |
32 | public P getPrototype()
33 | {
34 | return prototype;
35 | }
36 |
37 | public long getElapsed()
38 | {
39 | return elapsed;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security Policy
2 |
3 | ## Supported Versions
4 |
5 | We release patches for security vulnerabilities. Which versions are eligible receiving such patches depend on the [CVSS 3 ](https://www.first.org/cvss/calculator/3.0) Rating:
6 | | Version | Supported |
7 | |---------|----------------|
8 | | 0.x | :red_circle: |
9 | | 1.0.x - 1.4.x | :red_circle: |
10 | | 1.5.x + | :green_circle: |
11 |
12 | ## Reporting a Vulnerability
13 |
14 | Please report (suspected) security vulnerabilities with the [Private Vulnerability Reporting](https://github.com/Password4j/password4j/security/advisories/new) functionality. If the issue is confirmed, we will release a patch as soon as possible depending on complexity but historically within a few days.
15 |
16 | We generally **aren’t** interested in the following problems:
17 | * Any vulnerability with a [CVSS 3 ](https://www.first.org/cvss/calculator/3.0) score lower than `4.0`, unless it can be combined with other vulnerabilities to achieve a higher score.
18 | * DoS, phishing, text injection, or social engineering attacks. Wikis, Tracs, forums, etc are intended to allow users to edit them.
19 | * Output from automated scans - please manually verify issues and include a valid proof of concept.
20 | * Theoretical vulnerabilities where you can't demonstrate a significant security impact with a PoC.
21 |
--------------------------------------------------------------------------------
/src/test/com/password4j/PepperGeneratorTest.java:
--------------------------------------------------------------------------------
1 | package com.password4j;
2 |
3 | import org.junit.Assert;
4 | import org.junit.Test;
5 |
6 |
7 | public class PepperGeneratorTest
8 | {
9 |
10 | @Test
11 | public void testSaltLength()
12 | {
13 | // GIVEN
14 | int length = 23;
15 |
16 | // WHEN
17 | String pepper = PepperGenerator.generate(length);
18 |
19 | // THEN
20 | Assert.assertNotNull(pepper);
21 | Assert.assertEquals(length, pepper.length());
22 | }
23 |
24 |
25 | @Test
26 | public void testSaltNoLength()
27 | {
28 | // GIVEN
29 |
30 | // WHEN
31 | String pepper = PepperGenerator.generate();
32 |
33 | // THEN
34 | Assert.assertNotNull(pepper);
35 | Assert.assertEquals(24, pepper.length());
36 | }
37 |
38 | @Test(expected = BadParametersException.class)
39 | public void testSaltNegativeLength()
40 | {
41 | // GIVEN
42 |
43 | // WHEN
44 | PepperGenerator.generate(-3);
45 |
46 | // THEN
47 |
48 | }
49 |
50 | @Test
51 | public void testSaltZeroLength()
52 | {
53 | // GIVEN
54 | int length = 0;
55 |
56 | // WHEN
57 | String pepper = PepperGenerator.generate(length);
58 |
59 | // THEN
60 | Assert.assertNotNull(pepper);
61 | Assert.assertEquals(length, pepper.length());
62 | }
63 |
64 | @Test
65 | public void testAlice()
66 | {
67 | // GIVEN
68 |
69 | // WHEN
70 | String pepper = PepperGenerator.get();
71 |
72 | // THEN
73 | Assert.assertEquals("AlicePepper", pepper);
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/test/com/password4j/HashBuilderTest.java:
--------------------------------------------------------------------------------
1 | package com.password4j;
2 |
3 | import org.junit.Assert;
4 | import org.junit.Test;
5 |
6 | import java.awt.event.KeyEvent;
7 |
8 |
9 | public class HashBuilderTest
10 | {
11 | @Test
12 | public void testPrintable()
13 | {
14 | // GIVEN
15 |
16 | // WHEN
17 | Hash hash = Password.hash("a password").addRandomSalt().withCompressedPBKDF2();
18 |
19 | // THEN
20 | Assert.assertNotNull(hash.getSalt());
21 | Assert.assertTrue(isPrintableChar(hash.getResult()));
22 | }
23 |
24 | @Test
25 | public void testPrintable2()
26 | {
27 | // GIVEN
28 |
29 | // WHEN
30 | Hash hash = Password.hash("a password").withBcrypt();
31 |
32 | // THEN
33 | Assert.assertNotNull(hash.getSalt());
34 | Assert.assertTrue(isPrintableChar(hash.getResult()));
35 | }
36 |
37 | @Test
38 | public void testPrintable3()
39 | {
40 | // GIVEN
41 |
42 | // WHEN
43 | Hash hash = Password.hash("a password").addRandomSalt().withScrypt();
44 |
45 | // THEN
46 | Assert.assertNotNull(hash.getSalt());
47 | Assert.assertTrue(isPrintableChar(hash.getResult()));
48 | }
49 |
50 | private static boolean isPrintableChar(String str)
51 | {
52 | boolean res = true;
53 | for (char c : str.toCharArray())
54 | {
55 | Character.UnicodeBlock block = Character.UnicodeBlock.of(c);
56 | res &= (!Character.isISOControl(
57 | c)) && c != KeyEvent.CHAR_UNDEFINED && block != null && block != Character.UnicodeBlock.SPECIALS;
58 | }
59 |
60 | return res;
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: Build
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | pull_request:
8 | types: [opened, synchronize, reopened]
9 |
10 | jobs:
11 | build:
12 | runs-on: ubuntu-latest
13 |
14 | strategy:
15 | matrix:
16 | java-version: [ 8, 11, 17 ]
17 |
18 | steps:
19 | - uses: actions/checkout@v2
20 | - uses: actions/setup-java@v1
21 | with:
22 | java-version: ${{ matrix.java-version }}
23 | - run: mvn clean animal-sniffer:check install test
24 |
25 | analyze:
26 | runs-on: ubuntu-latest
27 | env:
28 | JAVA_TOOL_OPTIONS: "--add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED"
29 | MAVEN_OPTS: "--add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED"
30 | steps:
31 | - uses: actions/checkout@v2
32 | with:
33 | fetch-depth: 0
34 | - name: Set up JDK 17
35 | uses: actions/setup-java@v1
36 | with:
37 | java-version: 17
38 | - name: Cache SonarCloud packages
39 | uses: actions/cache@v4
40 | with:
41 | path: ~/.sonar/cache
42 | key: ${{ runner.os }}-sonar
43 | restore-keys: ${{ runner.os }}-sonar
44 | - name: Cache Maven packages
45 | uses: actions/cache@v4
46 | with:
47 | path: ~/.m2
48 | key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
49 | restore-keys: ${{ runner.os }}-m2
50 | - name: Build and analyze
51 | env:
52 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
53 | SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
54 | run: mvn -B verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.projectKey=Password4j_password4j
55 |
--------------------------------------------------------------------------------
/src/main/java/com/password4j/types/Argon2.java:
--------------------------------------------------------------------------------
1 | /*
2 | * (C) Copyright 2021 Password4j (http://password4j.com/).
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 | package com.password4j.types;
18 |
19 | /**
20 | * Enum containing the different variations of Argon2.
21 | *
22 | * @author David Bertoldi
23 | * @see Argon2
24 | * @since 1.5.0
25 | */
26 | public enum Argon2
27 | {
28 | /**
29 | * It maximizes resistance to GPU cracking attacks.
30 | * It accesses the memory array in a password dependent order, which reduces the possibility of time–memory trade-off (TMTO) attacks,
31 | * but introduces possible side-channel attacks
32 | */
33 | D,
34 |
35 | /**
36 | * It is optimized to resist side-channel attacks. It accesses the memory array in a password independent order.
37 | */
38 | I,
39 |
40 | /**
41 | * It is a hybrid version. It follows the Argon2i approach for the first half pass over memory and the Argon2d approach for subsequent passes.
42 | * It is recommended to use Argon2id except when there are reasons to prefer one of the other two modes.
43 | */
44 | ID;
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/src/main/java/com/password4j/BadParametersException.java:
--------------------------------------------------------------------------------
1 | /*
2 | * (C) Copyright 2020 Password4j (http://password4j.com/).
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 | package com.password4j;
18 |
19 | /**
20 | * This exception is normally thrown when a not well formed parameter
21 | * is passed as argument to a function.
22 | *
23 | * This exception covers all the exceptions raised by underlying logic,
24 | * grouping them as one exception.
25 | *
26 | * @author David Bertoldi
27 | * @since 0.1.0
28 | */
29 | public class BadParametersException extends IllegalArgumentException
30 | {
31 |
32 | private static final long serialVersionUID = 9204720180786210237L;
33 |
34 | /**
35 | * Constructs the exception.
36 | *
37 | * @param message the message describing the cause of the exception
38 | * @since 0.1.0
39 | */
40 | public BadParametersException(String message)
41 | {
42 | super(message);
43 | }
44 |
45 | /**
46 | * Constructs the exception.
47 | *
48 | * @param message the message describing the cause of the exception
49 | * @param exception the exception masked by this object
50 | * @since 0.1.0
51 | */
52 | public BadParametersException(String message, Throwable exception)
53 | {
54 | super(message, exception);
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/.github/workflows/codeql-analysis.yml:
--------------------------------------------------------------------------------
1 | name: "CodeQL"
2 |
3 | on:
4 | push:
5 | branches: [master]
6 | pull_request:
7 | # The branches below must be a subset of the branches above
8 | branches: [master]
9 | schedule:
10 | - cron: '0 2 * * 4'
11 |
12 | jobs:
13 | analyze:
14 | name: Analyze
15 | runs-on: ubuntu-latest
16 |
17 | strategy:
18 | fail-fast: false
19 | matrix:
20 | # Override automatic language detection by changing the below list
21 | # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python']
22 | language: ['java']
23 | # Learn more...
24 | # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection
25 |
26 | steps:
27 | - name: Checkout repository
28 | uses: actions/checkout@v2
29 | with:
30 | # We must fetch at least the immediate parents so that if this is
31 | # a pull request then we can checkout the head.
32 | fetch-depth: 2
33 |
34 | # If this run was triggered by a pull request event, then checkout
35 | # the head of the pull request instead of the merge commit.
36 | - run: git checkout HEAD^2
37 | if: ${{ github.event_name == 'pull_request' }}
38 |
39 | # Initializes the CodeQL tools for scanning.
40 | - name: Initialize CodeQL
41 | uses: github/codeql-action/init@v1
42 | with:
43 | languages: ${{ matrix.language }}
44 |
45 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
46 | # If this step fails, then you should remove it and run the build manually (see below)
47 | - name: Autobuild
48 | uses: github/codeql-action/autobuild@v1
49 |
50 | # ℹ️ Command-line programs to run using the OS shell.
51 | # 📚 https://git.io/JvXDl
52 |
53 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
54 | # and modify them (or add more) to build your code if your project
55 | # uses a compiled language
56 |
57 | #- run: |
58 | # make bootstrap
59 | # make release
60 |
61 | - name: Perform CodeQL Analysis
62 | uses: github/codeql-action/analyze@v1
63 |
--------------------------------------------------------------------------------
/src/main/java/com/password4j/types/Hmac.java:
--------------------------------------------------------------------------------
1 | /*
2 | * (C) Copyright 2021 Password4j (http://password4j.com/).
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package com.password4j.types;
19 |
20 | import com.password4j.CompressedPBKDF2Function;
21 |
22 |
23 | /**
24 | * Static representation of the commonly supported
25 | * Hmac variants.
26 | */
27 | public enum Hmac
28 | {
29 |
30 | SHA1(160, 1), //
31 | SHA224(224, 2), //
32 | SHA256(256, 3), //
33 | SHA384(384, 4), //
34 | SHA512(512, 5);
35 |
36 | private final int bits;
37 |
38 | private final int code;
39 |
40 | /**
41 | * @param bits length of the produced hash
42 | * @param code uid used by {@link CompressedPBKDF2Function}
43 | */
44 | Hmac(int bits, int code)
45 | {
46 | this.bits = bits;
47 | this.code = code;
48 | }
49 |
50 | /**
51 | * Finds the enum associated with the given code
52 | *
53 | * @param code a numeric uid that identifies the algorithm
54 | * @return a {@link Hmac} enum. Null if the code is not present in this enum
55 | */
56 | public static Hmac fromCode(int code)
57 | {
58 | for (Hmac alg : values())
59 | {
60 | if (alg.code() == code)
61 | {
62 | return alg;
63 | }
64 | }
65 | return null;
66 | }
67 |
68 | /**
69 | * @return length of the algorithm in bits
70 | */
71 | public int bits()
72 | {
73 | return bits;
74 | }
75 |
76 | /**
77 | * @return the numeric uid used in {@link CompressedPBKDF2Function}
78 | */
79 | public int code()
80 | {
81 | return code;
82 | }
83 |
84 | @Override
85 | public String toString()
86 | {
87 | return "PBKDF2WithHmac" + this.name();
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/src/main/java/com/password4j/HashUpdate.java:
--------------------------------------------------------------------------------
1 | /*
2 | * (C) Copyright 2020 Password4j (http://password4j.com/).
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package com.password4j;
19 |
20 | /**
21 | * This class represents the result of the password verification process
22 | * along with the generated {@link Hash}.
23 | *
24 | * If the verification passed a new hash is always provided.
25 | *
26 | * @author David Bertoldi
27 | * @since 1.3.0
28 | */
29 | public class HashUpdate
30 | {
31 | public static final HashUpdate UNVERIFIED = new HashUpdate();
32 |
33 | private Hash hash;
34 |
35 | private boolean updated;
36 |
37 | private HashUpdate()
38 | {
39 | //
40 | }
41 |
42 | /**
43 | * @param hash the new hash
44 | * @throws BadParametersException if hash is null but verified is true
45 | * @since 1.3.0
46 | */
47 | public HashUpdate(Hash hash)
48 | {
49 | this.hash = hash;
50 | }
51 |
52 | /**
53 | * @param hash the new hash
54 | * @param updated flag for updated hash
55 | * @throws BadParametersException if hash is null but verified is true
56 | * @since 1.7.0
57 | */
58 | public HashUpdate(Hash hash, boolean updated)
59 | {
60 | this(hash);
61 | this.updated = updated;
62 | }
63 |
64 | /**
65 | * Returns the hash generated after a verification + update
66 | * process.
67 | *
68 | * It is never null if the hash is {@link #isVerified()}
69 | * return true.
70 | *
71 | * @return the regenerated hash
72 | * @since 1.3.0
73 | */
74 | public Hash getHash()
75 | {
76 | return this.hash;
77 | }
78 |
79 | /**
80 | * Returns the result of the verification process.
81 | *
82 | * @return true if the verification process was successful
83 | * @since 1.3.0
84 | */
85 | public boolean isVerified()
86 | {
87 | return hash != null;
88 | }
89 |
90 | /**
91 | * True if the update process changed the original hash due to changes on parameters.
92 | * Changing the algorithms always set this flag to true.
93 | *
94 | * @return true if the hash was updated
95 | * @since 1.7.0
96 | */
97 | public boolean isUpdated()
98 | {
99 | return updated;
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/src/main/java/com/password4j/PepperGenerator.java:
--------------------------------------------------------------------------------
1 | /*
2 | * (C) Copyright 2020 Password4j (http://password4j.com/).
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package com.password4j;
19 |
20 |
21 | /**
22 | * This class contains static functions that
23 | * help to create a secure pepper.
24 | *
25 | * In cryptography, a pepper is a secret added to a password
26 | * prior to being hashed with a CHF.
27 | *
28 | * @author David Bertoldi
29 | * @since 0.1.1
30 | */
31 | public class PepperGenerator
32 | {
33 |
34 | private PepperGenerator()
35 | {
36 | //
37 | }
38 |
39 | /**
40 | * Generates a {@link String} that can be used as pepper
41 | * by a CHF.
42 | * The generated pepper is created by a cryptographically
43 | * strong random number generator (RNG).
44 | *
45 | * The parameter length must be a non-negative number,
46 | * otherwise a {@link BadParametersException} is thrown.
47 | *
48 | * @param length of the returned string
49 | * @return a pepper of the given length
50 | * @throws BadParametersException if the length is negative
51 | * @since 0.1.1
52 | */
53 | public static String generate(int length)
54 | {
55 | if (length < 0)
56 | {
57 | throw new BadParametersException("Pepper length cannot be negative");
58 | }
59 | return Utils.randomPrintable(length);
60 | }
61 |
62 | /**
63 | * Generates a {@link String} that can be used as pepper
64 | * by a CHF.
65 | * The generated pepper is created by a cryptographically
66 | * strong random number generator (RNG).
67 | *
68 | * The pepper generated is 24 characters long.
69 | *
70 | * @return a pepper as {@link String}
71 | * @since 0.1.1
72 | */
73 | public static String generate()
74 | {
75 | return generate(24);
76 | }
77 |
78 | /**
79 | * Peppers by definition are shared between all passwords and
80 | * must be stored in a location different from the one used
81 | * for the passwords.
82 | *
83 | * It can be set in the psw4j.properties file with
84 | * the property {@code global.pepper}.
85 | *
86 | * If the psw4j.properties or the property {@code global.pepper}
87 | * are not found, {@code null} is returned.
88 | *
89 | * @return a shared pepper set in the psw4j.properties file.
90 | * @since 0.1.1
91 | */
92 | public static String get()
93 | {
94 | return PropertyReader.readString("global.pepper", null, "Global pepper is not defined");
95 | }
96 |
97 | }
98 |
--------------------------------------------------------------------------------
/src/main/java/com/password4j/SaltGenerator.java:
--------------------------------------------------------------------------------
1 | /*
2 | * (C) Copyright 2020 Password4j (http://password4j.com/).
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package com.password4j;
19 |
20 | import java.security.SecureRandom;
21 |
22 |
23 | /**
24 | * This class contains static functions that
25 | * help to create a secure salt.
26 | *
27 | * In cryptography, a salt is random data that is used as
28 | * an additional input to a CHF.
29 | *
30 | * @author David Bertoldi
31 | * @since 0.1.0
32 | */
33 | public class SaltGenerator
34 | {
35 |
36 | private SaltGenerator()
37 | {
38 | //
39 | }
40 |
41 | /**
42 | * Generates an array of {@code byte}s that can be used
43 | * as salt by the CHFs.
44 | * The generated salt is created by a cryptographically
45 | * strong random number generator (RNG).
46 | *
47 | * The parameter length must be a non-negative number,
48 | * otherwise a {@link BadParametersException} is thrown.
49 | *
50 | * @param length of the returned byte array
51 | * @return a salt as array of {@code byte}s
52 | * @throws BadParametersException if the length is negative
53 | * @since 0.1.0
54 | */
55 | public static byte[] generate(int length)
56 | {
57 | if (length < 0)
58 | {
59 | throw new BadParametersException("Salt length cannot be negative");
60 | }
61 | byte[] salt = new byte[length];
62 | SecureRandom sr = AlgorithmFinder.getSecureRandom();
63 | sr.nextBytes(salt);
64 | return salt;
65 | }
66 |
67 | /**
68 | * Generates an array of {@code byte}s that can be used
69 | * as salt by the CHFs.
70 | * The generated salt is created by a cryptographically
71 | * strong random number generator (RNG).
72 | *
73 | * The length of the array is 64.
74 | *
75 | * @return a salt as array of {@code byte}s
76 | * @since 0.1.0
77 | */
78 | public static byte[] generate()
79 | {
80 | return generate(get());
81 | }
82 |
83 | /**
84 | * Get the length of salt from configurations and
85 | * must be stored in a location different from the one used
86 | * for the passwords.
87 | *
88 | * It can be set in the psw4j.properties file with
89 | * the property {@code global.salt.length}.
90 | *
91 | * If the psw4j.properties or the property {@code global.salt.length}
92 | * are not found, {@code null} is returned.
93 | *
94 | * @return a shared pepper set in the psw4j.properties file.
95 | * @since 1.7.0
96 | */
97 | public static int get()
98 | {
99 | return PropertyReader.readInt("global.salt.length", 64, "Global salt length is not defined in properties file");
100 | }
101 |
102 | }
103 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, sex characteristics, gender identity and expression,
9 | level of experience, education, socio-economic status, nationality, personal
10 | appearance, race, religion, or sexual identity and orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at dav.bertoldi at gmail.com . All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
72 |
73 | [homepage]: https://www.contributor-covenant.org
74 |
75 | For answers to common questions about this code of conduct, see
76 | https://www.contributor-covenant.org/faq
77 |
--------------------------------------------------------------------------------
/src/test/com/password4j/HashTest.java:
--------------------------------------------------------------------------------
1 | package com.password4j;
2 |
3 | import org.junit.Assert;
4 | import org.junit.Test;
5 |
6 | import java.util.Arrays;
7 |
8 |
9 | public class HashTest
10 | {
11 |
12 |
13 |
14 | @Test
15 | public void testHashContent()
16 | {
17 | // GIVEN
18 | String hashed = "myHash";
19 | String salt = "mySalt";
20 | String pepper = "myPepper";
21 | HashingFunction function = new CompressedPBKDF2Function();
22 |
23 | // WHEN
24 | Hash hash = new Hash(function, hashed, hashed.getBytes(), salt);
25 | hash.setPepper(pepper);
26 | Hash hash2 = new Hash(function, hashed, hashed.getBytes(), salt);
27 |
28 | // THEN
29 | Assert.assertEquals(hashed, hash.getResult());
30 | Assert.assertEquals(salt, hash.getSalt());
31 | Assert.assertEquals(pepper, hash.getPepper());
32 | Assert.assertEquals(Arrays.toString(hashed.getBytes()), Arrays.toString(hash.getBytes()));
33 | Assert.assertNull(hash2.getPepper());
34 | }
35 |
36 | @Test
37 | public void testHashCheckNull()
38 | {
39 | // GIVEN
40 | Hash hash = Password.hash("myPassword").withCompressedPBKDF2();
41 |
42 | // WHEN
43 | boolean result = Password.check((byte[]) null, hash);
44 |
45 | // THEN
46 | Assert.assertFalse(result);
47 | }
48 |
49 | @Test
50 | public void testHashEquality()
51 | {
52 | // GIVEN
53 | Hash hash = Password.hash("myPassword").withCompressedPBKDF2();
54 |
55 | // WHEN
56 | boolean eq1 = hash.equals(null);
57 | boolean eq2 = hash.equals(new Object());
58 | boolean eq3 = hash.equals(new Hash(AlgorithmFinder.getCompressedPBKDF2Instance(), hash.getResult(), hash.getBytes(), hash.getSaltBytes()));
59 | boolean eq4 = hash.equals(new Hash(AlgorithmFinder.getPBKDF2Instance(), hash.getResult(), hash.getBytes(), hash.getSalt()));
60 | boolean eq5 = hash.equals(new Hash(AlgorithmFinder.getCompressedPBKDF2Instance(), "hash", hash.getBytes(), hash.getSalt()));
61 | boolean eq6 = hash.equals(new Hash(AlgorithmFinder.getCompressedPBKDF2Instance(), hash.getResult(), hash.getBytes(), "salt"));
62 | boolean eq7 = hash.equals(new Hash(AlgorithmFinder.getCompressedPBKDF2Instance(), hash.getResult(), new byte[]{1,2,3,4}, hash.getSalt()));
63 |
64 | hash.setPepper("pepper");
65 | Hash testingHash = new Hash(AlgorithmFinder.getCompressedPBKDF2Instance(), hash.getResult(), hash.getBytes(), hash.getSaltBytes());
66 |
67 | testingHash.setPepper(hash.getPepper());
68 | boolean eq8 = hash.equals(testingHash);
69 | hash.setPepper("reppep");
70 | boolean eq9 = hash.equals(testingHash);
71 |
72 | // THEN
73 | Assert.assertFalse(eq1);
74 | Assert.assertFalse(eq2);
75 | Assert.assertTrue(eq3);
76 | Assert.assertFalse(eq4);
77 | Assert.assertFalse(eq5);
78 | Assert.assertFalse(eq6);
79 | Assert.assertFalse(eq7);
80 | Assert.assertTrue(eq8);
81 | Assert.assertFalse(eq9);
82 | }
83 |
84 | @Test
85 | public void testSecFunc()
86 | {
87 | // GIVEN
88 | Hash hash1 = Password.hash("myPassword").withCompressedPBKDF2();
89 | Hash hash2 = Password.hash("myPassword").withPBKDF2();
90 | Hash hash3 = Password.hash("myPassword").addPepper().withPBKDF2();
91 |
92 | // WHEN
93 | String toString1 = hash1.toString();
94 | int hc1 = hash1.hashCode();
95 | String toString2 = hash2.toString();
96 | int hc2 = hash2.hashCode();
97 | String toString3 = hash3.toString();
98 | Hash hash4 = new Hash(hash3.getHashingFunction(), hash3.getResult(), hash3.getBytes(), hash3.getSalt());
99 |
100 |
101 | // THEN
102 | Assert.assertNotNull(toString1);
103 | Assert.assertNotNull(toString2);
104 | Assert.assertNotEquals(toString1, toString2);
105 | Assert.assertNotEquals(toString3, toString2);
106 | Assert.assertNotEquals(hc1, hc2);
107 | Assert.assertNotEquals(hash4, hash3);
108 | }
109 |
110 | }
111 |
--------------------------------------------------------------------------------
/examples/MigrateFromMD5.java:
--------------------------------------------------------------------------------
1 | /*
2 | * (C) Copyright 2022 Password4j (http://password4j.com/).
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | import com.password4j.BadParametersException;
19 | import com.password4j.Hash;
20 | import com.password4j.HashUpdate;
21 | import com.password4j.MessageDigestFunction;
22 | import com.password4j.Password;
23 | import com.password4j.ScryptFunction;
24 |
25 |
26 | /**
27 | * In this case the application used MD5 to hash passwords.
28 | * Here you can see how this dummy DAO verifies the credentials and updates the hash with a stronger algorithm, like scrypt.
29 | * Note that it already hashes the passwords with scrypt, so it takes in account not only old MD5 hashes but also the new ones with scrypt
30 | */
31 | public class MigrateFromMD5
32 | {
33 |
34 | public class UserDAO
35 | {
36 | /**
37 | * Hash the password.
38 | * @param providedPassword password received from the user
39 | */
40 | public void storePassword(String providedPassword)
41 | {
42 | Hash hash = Password.hash(providedPassword).withScrypt();
43 | Service.storePasswordInDatabase(hash.getResult());
44 | }
45 |
46 | /**
47 | * Verify if the password matches the one saved in the database
48 | * @param providedPassword password received from the user
49 | * @return true if mathces, false otherwise
50 | */
51 | public boolean verifyCredentials(String providedPassword)
52 | {
53 | // Retrieve the password from the Database
54 | String passwordFromDB = Service.getPasswordFromDatabaseForCurrentUser();
55 |
56 | try
57 | {
58 | // Parse the hash and tries to generate a scrypt prototype to use during the verification.
59 | // If the hash retrieved from the database is not a valid scrypt, a BadParametersException is thrown
60 | // and that means we found an old MD5 hash.
61 | ScryptFunction scrypt = ScryptFunction.getInstanceFromHash(passwordFromDB);
62 |
63 | if(Password.check(providedPassword, passwordFromDB).with(scrypt))
64 | {
65 | // The check passed and the user is authenticated
66 | return true;
67 | }
68 | return false;
69 | }
70 | catch (BadParametersException bpe)
71 | {
72 | // If scrypt is not recognized it means we are working with a legacy MD5 hash!
73 | return handleOldPasswords(providedPassword, passwordFromDB);
74 | }
75 | }
76 |
77 | /**
78 | * If mathces, updates the hash with scrypt
79 | * @param providedPassword password received from the user
80 | * @param passwordFromDB password saved in the database
81 | * @return @return true if mathces, false otherwise
82 | */
83 | private boolean handleOldPasswords(String providedPassword, String passwordFromDB)
84 | {
85 | // Create a MD5 prototype
86 | MessageDigestFunction md5 = MessageDigestFunction.getInstance("MD5");
87 |
88 | // Check with MD5 and if the check passed it generates a stronger hash with scrypt
89 | HashUpdate update = Password.check(providedPassword, passwordFromDB).andUpdate().withScrypt(md5);
90 |
91 | // The check passed and the user can be authenticated
92 | if(update.isVerified())
93 | {
94 | // Remember to store the new scrypt hash in the database
95 | Service.storePasswordInDatabase(update.getHash().getResult());
96 | return true;
97 | }
98 | return false;
99 | }
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/src/test/com/password4j/SaltGeneratorTest.java:
--------------------------------------------------------------------------------
1 | package com.password4j;
2 |
3 | import org.junit.Assert;
4 | import org.junit.Before;
5 | import org.junit.Test;
6 |
7 | import java.security.*;
8 |
9 | import static org.junit.Assert.assertEquals;
10 | import static org.junit.Assert.assertNotEquals;
11 |
12 |
13 | public class SaltGeneratorTest
14 | {
15 |
16 | @Before
17 | public void init()
18 | {
19 | PropertyReader.properties.setProperty("global.random.strong", "false");
20 | AlgorithmFinder.initialize();
21 | }
22 |
23 | @Test
24 | public void testSaltLength()
25 | {
26 | // GIVEN
27 | int length = 23;
28 |
29 | // WHEN
30 | byte[] salt = SaltGenerator.generate(length);
31 |
32 | // THEN
33 | Assert.assertNotNull(salt);
34 | Assert.assertEquals(length, salt.length);
35 | }
36 |
37 |
38 | @Test
39 | public void testSaltNoLength()
40 | {
41 | // GIVEN
42 |
43 | // WHEN
44 | byte[] salt = SaltGenerator.generate();
45 |
46 | // THEN
47 | Assert.assertNotNull(salt);
48 | Assert.assertEquals(16, salt.length);
49 | }
50 |
51 | @Test
52 | public void testSaltNoProp()
53 | {
54 | // GIVEN
55 | PropertyReader.properties.remove("global.salt.length");
56 |
57 | // WHEN
58 | byte[] salt = SaltGenerator.generate();
59 |
60 | // THEN
61 | Assert.assertNotNull(salt);
62 | Assert.assertEquals(64, salt.length);
63 |
64 | PropertyReader.properties.put("global.salt.length", "16");
65 | }
66 |
67 | @Test(expected = BadParametersException.class)
68 | public void testSaltNegativeLength()
69 | {
70 | // GIVEN
71 |
72 | // WHEN
73 | SaltGenerator.generate(-3);
74 |
75 | // THEN
76 |
77 | }
78 |
79 | @Test
80 | public void testSaltZeroLength()
81 | {
82 | // GIVEN
83 | int length = 0;
84 |
85 | // WHEN
86 | byte[] salt = SaltGenerator.generate(length);
87 |
88 | // THEN
89 | Assert.assertNotNull(salt);
90 | Assert.assertEquals(length, salt.length);
91 | }
92 |
93 | @Test
94 | public void testStrongRandom()
95 | {
96 | // GIVEN
97 |
98 | PropertyReader.properties.setProperty("global.random.strong", "true");
99 |
100 | // WHEN
101 | AlgorithmFinder.initialize();
102 |
103 | // THEN
104 |
105 | try
106 | {
107 | assertEquals(SecureRandom.getInstanceStrong().getAlgorithm(), AlgorithmFinder.getSecureRandom().getAlgorithm());
108 |
109 | }
110 | catch (NoSuchAlgorithmException e)
111 | {
112 | //
113 | }
114 |
115 | }
116 |
117 | @Test
118 | public void testStrongRandom2()
119 | {
120 | // GIVEN
121 | PropertyReader.properties.setProperty("global.random.strong", "true");
122 |
123 | // WHEN
124 |
125 | String old = getSecurityProperty();
126 | String strongAlg;
127 | try
128 | {
129 | strongAlg = SecureRandom.getInstanceStrong().getAlgorithm();
130 | }
131 | catch (Exception e)
132 | {
133 | return;
134 | }
135 |
136 | setSecurityProperty("not and algorithm");
137 | AlgorithmFinder.initialize();
138 |
139 | // THEN
140 |
141 |
142 | assertNotEquals(strongAlg, AlgorithmFinder.getSecureRandom().getAlgorithm());
143 | setSecurityProperty(old);
144 |
145 | }
146 | @SuppressWarnings("removal")
147 | private String getSecurityProperty()
148 | {
149 | return AccessController.doPrivileged((PrivilegedAction) () -> Security.getProperty("securerandom.strongAlgorithms"));
150 | }
151 |
152 | @SuppressWarnings("removal")
153 | private void setSecurityProperty(String value)
154 | {
155 | AccessController.doPrivileged((PrivilegedAction) () -> {
156 | Security.setProperty("securerandom.strongAlgorithms", value);
157 | return null;
158 | });
159 | }
160 |
161 | @Test
162 | public void testSaltLength2()
163 | {
164 | //GIVEN
165 |
166 | //WHEN
167 | int saltLength = SaltGenerator.get();
168 |
169 | //THEN
170 | Assert.assertEquals(16, saltLength);
171 | }
172 |
173 | }
174 |
--------------------------------------------------------------------------------
/src/test/com/password4j/Blake2bTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * (C) Copyright 2020 Password4j (http://password4j.com/).
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 | package com.password4j;
18 |
19 | import org.junit.Test;
20 |
21 | import java.util.ArrayList;
22 | import java.util.Arrays;
23 | import java.util.List;
24 | import java.util.concurrent.*;
25 |
26 | import static org.junit.Assert.assertEquals;
27 | import static org.junit.Assert.assertTrue;
28 |
29 |
30 | public class Blake2bTest
31 | {
32 |
33 | static class TestCase
34 | {
35 | String message;
36 | int length;
37 | String expected;
38 |
39 | public TestCase(String message, int length, String expected)
40 | {
41 | this.message = message;
42 | this.length = length;
43 | this.expected = expected;
44 | }
45 | }
46 |
47 | private static final List CASES = Arrays.asList(
48 | new TestCase("IamUsingBlake2b###", 512/8, "5fc5a199294099e98280dac6047523aa123ba29e6995618339c9590e4dca983dea2529ad85afbac5613c495b3fb50bf2d5919cb3f51f6a9dba78a33f9d278f6f"),
49 | new TestCase(null, 512/8, "786a02f742015903c6c6fd852552d272912f4740e15847618a86e217f71f5419d25e1031afee585313896444934eb04b903a685b1448b755d56f701afe9be2ce"),
50 | new TestCase(null, 384/8, "b32811423377f52d7862286ee1a72ee540524380fda1724a6f25d7978c6fd3244a6caf0498812673c5e05ef583825100"),
51 | new TestCase(null, 256/8, "0e5751c026e543b2e8ab2eb06099daa1d1e5df47778f7787faab45cdf12fe3a8"),
52 | new TestCase(null, 224/8, "836cc68931c2e4e3e838602eca1902591d216837bafddfe6f0c8cb07"),
53 |
54 | new TestCase("0", 384/8, "c62e79958b2e7796d4b6afaba57b3a929a5c38125f56703cae90a952a96a6ef2a2d42376fe7183222779e3790fc95a22"),
55 |
56 | new TestCase("!$%^&*()_+@~{}", 512/8, "a7128f0b9a745d7073be967e2dc4ceb5e326a998ca45c451835c1c4eecd499dea1c1e04e15e890b2ac32675baea270785dd12d591646bc4df7c545b31041ed22")
57 | );
58 |
59 |
60 | @Test
61 | public void test()
62 | {
63 | for (TestCase test : CASES)
64 | {
65 | Blake2b instance = new Blake2b(test.length);
66 | instance.update(test.message == null ? null : test.message.getBytes(Utils.DEFAULT_CHARSET));
67 | byte[] out = new byte[test.length];
68 | instance.doFinal(out, 0);
69 | assertEquals(test.expected, Utils.toHex(out));
70 | }
71 | }
72 |
73 |
74 | @Test
75 | public void parallelTest() throws InterruptedException, ExecutionException
76 | {
77 |
78 | ExecutorService executors = Executors.newCachedThreadPool();
79 | List> tasks = new ArrayList<>();
80 | for (final TestCase test : CASES)
81 | {
82 | Callable c = () -> {
83 | Blake2b instance = new Blake2b(test.length);
84 | instance.update(test.message == null ? null : test.message.getBytes(Utils.DEFAULT_CHARSET));
85 | byte[] out = new byte[test.length];
86 | instance.doFinal(out, 0);
87 | return test.expected.equals(Utils.toHex(out));
88 | };
89 | tasks.add(c);
90 | }
91 | List> results = executors.invokeAll(tasks);
92 |
93 | for (Future future : results)
94 | {
95 | assertTrue(future.get());
96 | }
97 |
98 | }
99 |
100 | @Test(expected = BadParametersException.class)
101 | public void badTest1()
102 | {
103 | // GIVEN
104 |
105 | // WHEN
106 | new Blake2b(0);
107 | }
108 |
109 |
110 | @Test(expected = BadParametersException.class)
111 | public void badTest2()
112 | {
113 | // GIVEN
114 |
115 | // WHEN
116 | new Blake2b(65);
117 | }
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 | }
126 |
127 |
--------------------------------------------------------------------------------
/src/test/com/password4j/PropertyReaderTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * (C) Copyright 2020 Password4j (http://password4j.com/).
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 | package com.password4j;
18 |
19 | import org.junit.After;
20 | import org.junit.Assert;
21 | import org.junit.Before;
22 | import org.junit.Test;
23 |
24 | import java.io.File;
25 | import java.io.InputStream;
26 |
27 |
28 | public class PropertyReaderTest
29 | {
30 |
31 | @After
32 | @Before
33 | public void setup()
34 | {
35 | Thread.currentThread().setContextClassLoader(ClassLoader.getSystemClassLoader());
36 | System.clearProperty("psw4j.configuration");
37 | PropertyReader.init();
38 | }
39 |
40 | @Test
41 | public void testInt()
42 | {
43 | // GIVEN
44 | String key = "test.int";
45 |
46 | // WHEN
47 | int ten = PropertyReader.readInt(key, -4, null);
48 | int minusNine = PropertyReader.readInt(key + "abc", -9, null);
49 |
50 | // THEN
51 | Assert.assertEquals(10, ten);
52 | Assert.assertEquals(-9, minusNine);
53 | }
54 |
55 | @Test
56 | public void testBool()
57 | {
58 | // GIVEN
59 | String key = "test.bool";
60 |
61 | // WHEN
62 | boolean bool1 = PropertyReader.readBoolean(key, false);
63 | boolean bool2 = PropertyReader.readBoolean(key + "abc", true);
64 |
65 | // THEN
66 | Assert.assertTrue(bool1);
67 | Assert.assertTrue(bool2);
68 | }
69 |
70 | @Test
71 | public void testString()
72 | {
73 | // GIVEN
74 | String key = "test.string";
75 |
76 | // WHEN
77 | String testString = PropertyReader.readString(key, "default string", null);
78 | String defaultValue = PropertyReader.readString(key + "abc", "default string", null);
79 |
80 | // THEN
81 | Assert.assertEquals("This is a string", testString);
82 | Assert.assertEquals("default string", defaultValue);
83 | }
84 |
85 | @Test
86 | public void testChar()
87 | {
88 | // GIVEN
89 | String key = "test.char";
90 |
91 | // WHEN
92 | int backslash = PropertyReader.readChar(key, '/');
93 | int slash = PropertyReader.readChar(key + "abc", '/');
94 | int backslash2 = PropertyReader.readChar(key, '/', null);
95 | int slash2 = PropertyReader.readChar(key + "abc", '/', null);
96 |
97 | // THEN
98 | Assert.assertEquals('\\', backslash);
99 | Assert.assertEquals('/', slash);
100 | Assert.assertEquals('\\', backslash2);
101 | Assert.assertEquals('/', slash2);
102 | }
103 |
104 | @Test(expected = BadParametersException.class)
105 | public void testNull()
106 | {
107 | PropertyReader.readString(null, "null", null);
108 | }
109 |
110 | @Test
111 | public void testInitInvalidPath()
112 | {
113 | // GIVEN
114 | System.setProperty("psw4j.configuration", "/my/improbable/path/xyz.properties");
115 |
116 | // WHEN
117 | PropertyReader.init();
118 |
119 | // THEN
120 | Assert.assertTrue(PropertyReader.properties.isEmpty());
121 | }
122 |
123 | @Test
124 | public void testInitCustomPath() throws Exception
125 | {
126 | // GIVEN
127 | String path = new File(".").getCanonicalPath() + "/src/test/my/custom/path/to/some.properties";
128 | System.setProperty("psw4j.configuration", path);
129 |
130 | // WHEN
131 | PropertyReader.init();
132 |
133 | // THEN
134 | Assert.assertFalse(PropertyReader.properties.isEmpty());
135 | Assert.assertEquals("hello!!", PropertyReader.readString("check.this.out", "kappa", null));
136 | }
137 |
138 | @Test
139 | public void testNoThreadClassLoader() throws Exception
140 | {
141 | // GIVEN
142 | String path = new File(".").getCanonicalPath() + "/src/test/my/custom/path/to/some.properties";
143 | System.setProperty("psw4j.configuration", path);
144 | Thread.currentThread().setContextClassLoader(null);
145 |
146 | // WHEN
147 | PropertyReader.init();
148 |
149 | // THEN
150 | Assert.assertFalse(PropertyReader.properties.isEmpty());
151 | Assert.assertEquals("hello!!", PropertyReader.readString("check.this.out", "kappa", null));
152 | }
153 |
154 | @Test
155 | public void testResource1()
156 | {
157 | // GIVEN
158 |
159 | // WHEN
160 | InputStream in = PropertyReader.getResource("PropertyReader.class");
161 | // THEN
162 | Assert.assertNotNull(in);
163 | }
164 |
165 | }
166 |
--------------------------------------------------------------------------------
/src/main/java/com/password4j/SecureString.java:
--------------------------------------------------------------------------------
1 | /*
2 | * (C) Copyright 2020 Password4j (http://password4j.com/).
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package com.password4j;
19 |
20 | import java.util.Arrays;
21 |
22 |
23 | /**
24 | * More secure implementation of {@link CharSequence} than {@link String}, preventing heap memory attacks.
25 | *
26 | * Like {@link String}, this {@link CharSequence} implementation has an underlying array of {@code char}s.
27 | * The sequence however is not stored in the String pool and the array of {@code char}s lives
28 | * temporary in the heap memory.
29 | *
30 | * @author David Bertoldi
31 | * @since 1.2.0
32 | */
33 | public class SecureString implements CharSequence
34 | {
35 | private final char[] chars;
36 |
37 | /**
38 | * Creates a {@link SecureString} from an array of {@code char}s.
39 | * The sequence is never put in the String pool.
40 | *
41 | * @param chars sequence of characters
42 | * @throws NullPointerException if null is passed
43 | * @since 1.2.0
44 | */
45 | public SecureString(char[] chars)
46 | {
47 | this.chars = new char[chars.length];
48 | System.arraycopy(chars, 0, this.chars, 0, chars.length);
49 | }
50 |
51 | /**
52 | * Creates a {@link SecureString} from an array of {@code char}s.
53 | * Important: if the second argument is true, the original array is zeroed after the object creation! Each {@code char} is replaced
54 | * with {@link Character#MIN_VALUE}
55 | *
56 | * The sequence is never put in the String pool.
57 | *
58 | * @param chars sequence of characters
59 | * @param eraseSource if true, the original array is zeroed
60 | * @throws NullPointerException if null is passed
61 | * @since 1.2.0
62 | */
63 | public SecureString(char[] chars, boolean eraseSource)
64 | {
65 | this(chars);
66 | if (eraseSource)
67 | {
68 | clear(chars);
69 | }
70 | }
71 |
72 | /**
73 | * Creates a {@link SecureString} from a subsequence of an array of {@code char}s.
74 | * The sequence is never put in the String pool.
75 | *
76 | * @param chars sequence of characters
77 | * @param start index of the beginning of the subsequence
78 | * @param end index of the end of the subsequence
79 | * @throws NullPointerException if null is passed as array of {@code char}s
80 | * @since 1.2.0
81 | */
82 | public SecureString(char[] chars, int start, int end)
83 | {
84 | this.chars = new char[end - start];
85 | System.arraycopy(chars, start, this.chars, 0, this.chars.length);
86 | clear(chars);
87 | }
88 |
89 | private static synchronized void clear(char[] chars)
90 | {
91 | Arrays.fill(chars, Character.MIN_VALUE);
92 | }
93 |
94 | /**
95 | * @return length of the underlying array of {@code char}s.
96 | * @since 1.2.0
97 | */
98 | @Override
99 | public synchronized int length()
100 | {
101 | return chars.length;
102 | }
103 |
104 | /**
105 | * @param index position in the underlying array of {@code char}s.
106 | * @return the {@code char} in the given position
107 | * @since 1.2.0
108 | */
109 | @Override
110 | public synchronized char charAt(int index)
111 | {
112 | return chars[index];
113 | }
114 |
115 | /**
116 | * Creates a {@link SecureString} from a subsequence of this object.
117 | *
118 | * @param start index of the beginning of the subsequence
119 | * @param end index of the end of the subsequence
120 | * @see SecureString#SecureString(char[], int, int)
121 | * @since 1.2.0
122 | */
123 | @Override
124 | public synchronized CharSequence subSequence(int start, int end)
125 | {
126 | return new SecureString(this.chars, start, end);
127 | }
128 |
129 | /**
130 | * Manually clear the underlying array holding the characters
131 | *
132 | * @since 1.2.0
133 | */
134 | public void clear()
135 | {
136 | synchronized (chars)
137 | {
138 | clear(chars);
139 | }
140 | }
141 |
142 | /**
143 | * Returns a constant {@link String} in order to prevent data leaks due
144 | * to accidental usage of a {@link SecureString} objects in methods like
145 | * {@link java.io.PrintStream#print(Object)}, loggers, etc.
146 | *
147 | * @return a masked version of this object.
148 | * @since 1.2.0
149 | */
150 | @Override
151 | public String toString()
152 | {
153 | return "SecureString[****]";
154 | }
155 |
156 | /**
157 | * Constant time equality to avoid potential timing attacks.
158 | *
159 | * @param other object
160 | * @since 1.2.1
161 | */
162 | @Override
163 | public synchronized boolean equals(Object other)
164 | {
165 | if (this == other)
166 | {
167 | return true;
168 | }
169 | if (!(other instanceof CharSequence))
170 | {
171 | return false;
172 | }
173 | CharSequence that = (CharSequence) other;
174 |
175 | if (chars.length != that.length())
176 | {
177 | return false;
178 | }
179 |
180 | int equals = 0;
181 | for (int i = 0; i < chars.length; i++)
182 | {
183 | equals |= chars[i] ^ that.charAt(i);
184 | }
185 |
186 | return equals == 0;
187 | }
188 |
189 | @Override
190 | public synchronized int hashCode()
191 | {
192 | return Arrays.hashCode(chars);
193 | }
194 | }
195 |
--------------------------------------------------------------------------------
/src/main/java/com/password4j/AbstractHashingFunction.java:
--------------------------------------------------------------------------------
1 | /*
2 | * (C) Copyright 2020 Password4j (http://password4j.com/).
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package com.password4j;
19 |
20 | /**
21 | * Class in the hierarchy to avoid code duplication.
22 | *
23 | * @author David Bertoldi
24 | * @since 0.1.0
25 | */
26 | public abstract class AbstractHashingFunction implements HashingFunction
27 | {
28 |
29 | /**
30 | * Compares two {@link CharSequence}s as byte arrays in length-constant time. This comparison method
31 | * is used so that password hashes cannot be extracted from an on-line
32 | * system using a timing attack and then attacked off-line.
33 | *
34 | * @param a the first CharSequence
35 | * @param b the second CharSequence
36 | * @return true if both {@link CharSequence}s are the same, false if not
37 | */
38 | protected static boolean slowEquals(CharSequence a, CharSequence b)
39 | {
40 | return slowEquals(Utils.fromCharSequenceToBytes(a), Utils.fromCharSequenceToBytes(b));
41 | }
42 |
43 | /**
44 | * Compares two byte arrays in length-constant time. This comparison method
45 | * is used so that password hashes cannot be extracted from an on-line
46 | * system using a timing attack and then attacked off-line.
47 | *
48 | * @param a the first byte array
49 | * @param b the second byte array
50 | * @return true if both byte arrays are the same, false if not
51 | */
52 | protected static boolean slowEquals(byte[] a, byte[] b)
53 | {
54 | int diff = a.length ^ b.length;
55 | for (int i = 0; i < a.length && i < b.length; i++)
56 | {
57 | diff |= a[i] ^ b[i];
58 | }
59 | return diff == 0;
60 | }
61 |
62 | @Override
63 | public Hash hash(CharSequence plainTextPassword, String salt, CharSequence pepper)
64 | {
65 | CharSequence peppered = Utils.append(pepper, plainTextPassword);
66 | Hash result;
67 | if (salt == null)
68 | {
69 | result = hash(peppered);
70 | }
71 | else
72 | {
73 | result = hash(peppered, salt);
74 | }
75 |
76 | result.setPepper(pepper);
77 | return result;
78 | }
79 |
80 | @Override
81 | public Hash hash(byte[] plainTextPassword, byte[] salt, CharSequence pepper)
82 | {
83 | byte[] pepperAsBytes = Utils.fromCharSequenceToBytes(pepper);
84 | byte[] peppered = Utils.append(pepperAsBytes, plainTextPassword);
85 | Hash result;
86 | if (salt == null)
87 | {
88 | result = hash(peppered);
89 | }
90 | else
91 | {
92 | result = hash(peppered, salt);
93 | }
94 |
95 | result.setPepper(pepper);
96 | return result;
97 | }
98 |
99 | /**
100 | * Just calls {@link #check(CharSequence, String)} without salt
101 | * parameter.
102 | *
103 | * Do not override this if the algorithm doesn't need a manually
104 | * provided salt.
105 | *
106 | * @param plainTextPassword the plaintext password
107 | * @param hashed the hash
108 | * @param salt the salt used to produce the hash
109 | * @return true if the hash is generated from the plaintext; false otherwise
110 | * @since 0.1.0
111 | */
112 | @Override
113 | public boolean check(CharSequence plainTextPassword, String hashed, String salt)
114 | {
115 | return check(plainTextPassword, hashed);
116 | }
117 |
118 | /**
119 | * Just calls {@link #check(CharSequence, String)} without salt
120 | * parameter.
121 | *
122 | * Do not override this if the algorithm doesn't need a manually
123 | * provided salt.
124 | *
125 | * @param plainTextPassword the plaintext password as bytes array
126 | * @param hashed the hash as bytes array
127 | * @param salt the salt as bytes array used to produce the hash
128 | * @return true if the hash is generated from the plaintext; false otherwise
129 | * @since 1.7.0
130 | */
131 | @Override
132 | public boolean check(byte[] plainTextPassword, byte[] hashed, byte[] salt)
133 | {
134 | return check(plainTextPassword, hashed);
135 | }
136 |
137 | /**
138 | * Just calls {@link #check(CharSequence, String, String)}, with a prepended pepper.
139 | *
140 | * @param plainTextPassword the plaintext password
141 | * @param hashed the hash
142 | * @param salt the salt used to produce the hash
143 | * @return true if the hash is generated from the plaintext; false otherwise
144 | * @since 1.5.0
145 | */
146 | @Override
147 | public boolean check(CharSequence plainTextPassword, String hashed, String salt, CharSequence pepper)
148 | {
149 | return check(Utils.append(pepper, plainTextPassword), hashed, salt);
150 | }
151 |
152 | /**
153 | * Just calls {@link #check(CharSequence, String, String)}, with a prepended pepper.
154 | *
155 | * @param plainTextPassword the plaintext password
156 | * @param hashed the hash
157 | * @param salt the salt used to produce the hash
158 | * @return true if the hash is generated from the plaintext; false otherwise
159 | * @since 1.7.0
160 | */
161 | @Override
162 | public boolean check(byte[] plainTextPassword, byte[] hashed, byte[] salt, CharSequence pepper)
163 | {
164 | byte[] pepperAsBytes = Utils.fromCharSequenceToBytes(pepper);
165 | return check(Utils.append(pepperAsBytes, plainTextPassword), hashed, salt);
166 | }
167 |
168 | }
169 |
--------------------------------------------------------------------------------
/src/test/com/password4j/SystemCheckTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * (C) Copyright 2020 Password4j (http://password4j.com/).
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package com.password4j;
19 |
20 | import com.password4j.types.Argon2;
21 | import com.password4j.types.Hmac;
22 | import org.junit.Assert;
23 | import org.junit.Test;
24 |
25 | import java.util.Set;
26 |
27 |
28 | public class SystemCheckTest
29 | {
30 |
31 | @Test
32 | public void testPBKDF2Iterations()
33 | {
34 | // GIVEN
35 | long maxMilliseconds = 10;
36 |
37 | // WHEN
38 | BenchmarkResult result = SystemChecker.benchmarkPBKDF2(maxMilliseconds, Hmac.SHA256, 256);
39 |
40 | // THEN
41 | Assert.assertTrue(result.getPrototype().getIterations() > 150);
42 | Assert.assertTrue(result.getElapsed() <= maxMilliseconds);
43 | }
44 |
45 | @Test
46 | public void testPBKDF2Iterations2()
47 | {
48 | // GIVEN
49 | long maxMilliseconds = -1;
50 |
51 | // WHEN
52 | BenchmarkResult result = SystemChecker.benchmarkPBKDF2(maxMilliseconds, Hmac.SHA512, 4096);
53 |
54 | // THEN
55 | Assert.assertNull(result.getPrototype());
56 | Assert.assertEquals(-1, result.getElapsed());
57 | }
58 |
59 |
60 | @Test
61 | public void testArgon2Iterations()
62 | {
63 | // GIVEN
64 | long maxMilliseconds = 50;
65 | int memoryForEachHash = 512;
66 | int threadsPerHash = 2;
67 | int outputLength = 128;
68 | Argon2 type = Argon2.ID;
69 |
70 | // WHEN
71 | BenchmarkResult result = SystemChecker.benchmarkForArgon2(maxMilliseconds, memoryForEachHash, threadsPerHash, outputLength, type);
72 |
73 | // THEN
74 | Assert.assertTrue(result.getPrototype().getIterations() > 1);
75 | Assert.assertTrue(result.getElapsed() <= maxMilliseconds);
76 | }
77 |
78 | @Test
79 | public void testArgon2Iterations2()
80 | {
81 | // GIVEN
82 | long maxMilliseconds = 2;
83 | int memoryForEachHash = 4096;
84 | int threadsPerHash = 21;
85 | int outputLength = 128;
86 | Argon2 type = Argon2.ID;
87 |
88 | // WHEN
89 | BenchmarkResult result = SystemChecker.benchmarkForArgon2(maxMilliseconds, memoryForEachHash, threadsPerHash, outputLength, type);
90 |
91 | // THEN
92 | Assert.assertNull(result.getPrototype());
93 | Assert.assertEquals(-1, result.getElapsed());
94 | }
95 |
96 |
97 | @Test
98 | public void testBcryptRounds()
99 | {
100 | // GIVEN
101 | long maxMilliseconds = 50;
102 |
103 | // WHEN
104 | BenchmarkResult result = SystemChecker.benchmarkBcrypt(maxMilliseconds);
105 |
106 | // THEN
107 | Assert.assertTrue(result.getPrototype().getLogarithmicRounds() >= 4);
108 | Assert.assertTrue(result.getElapsed() <= maxMilliseconds);
109 | }
110 |
111 | @Test
112 | public void testBcryptRounds2()
113 | {
114 | // GIVEN
115 | long maxMilliseconds = -1;
116 |
117 | // WHEN
118 | BenchmarkResult result = SystemChecker.benchmarkBcrypt(maxMilliseconds);
119 |
120 | // THEN
121 | Assert.assertNull(result.getPrototype());
122 | Assert.assertEquals(-1, result.getElapsed());
123 | }
124 |
125 | @Test
126 | public void testScryptRounds()
127 | {
128 | // GIVEN
129 | long maxMilliseconds = -1;
130 |
131 | // WHEN
132 | BenchmarkResult result1 = SystemChecker.findWorkFactorForScrypt(maxMilliseconds, 16, 1);
133 | BenchmarkResult result2 = SystemChecker.findResourcesForScrypt(maxMilliseconds, 1024, 1);
134 |
135 | // THEN
136 | Assert.assertNull(result1.getPrototype());
137 | Assert.assertEquals(-1, result1.getElapsed());
138 | Assert.assertNull(result2.getPrototype());
139 | Assert.assertEquals(-1, result2.getElapsed());
140 | }
141 |
142 | @Test
143 | public void testScryptRounds2()
144 | {
145 | // GIVEN
146 | long maxMilliseconds = 50;
147 |
148 | // WHEN
149 | BenchmarkResult result1 = SystemChecker.findWorkFactorForScrypt(maxMilliseconds, 16, 1);
150 | BenchmarkResult result2 = SystemChecker.findResourcesForScrypt(maxMilliseconds, result1.getPrototype().getWorkFactor(), 1);
151 |
152 | // THEN
153 | Assert.assertTrue(result1.getElapsed() > 0);
154 | Assert.assertTrue(result2.getElapsed() > 0);
155 | }
156 |
157 |
158 |
159 | @Test(expected = BadParametersException.class)
160 | public void testWrongVariants()
161 | {
162 | //GIVEN
163 |
164 | // WHEN
165 | SystemChecker.isPBKDF2Supported(null);
166 | }
167 |
168 | @Test(expected = Test.None.class)
169 | public void testVariants()
170 | {
171 | //GIVEN
172 |
173 | // WHEN
174 | SystemChecker.isPBKDF2Supported(Hmac.SHA256.name());
175 | }
176 |
177 |
178 | @Test(expected = BadParametersException.class)
179 | public void testWrongAlgs()
180 | {
181 | //GIVEN
182 |
183 | // WHEN
184 | SystemChecker.isMessageDigestSupported(null);
185 | }
186 |
187 | @Test
188 | public void testAlgs()
189 | {
190 | //GIVEN
191 |
192 | // WHEN
193 | Set mds = AlgorithmFinder.getAllMessageDigests();
194 | for(String md : mds)
195 | {
196 | Assert.assertTrue(SystemChecker.isMessageDigestSupported(md));
197 | }
198 |
199 | }
200 |
201 |
202 |
203 |
204 | }
205 |
--------------------------------------------------------------------------------
/src/main/java/com/password4j/PropertyReader.java:
--------------------------------------------------------------------------------
1 | /*
2 | * (C) Copyright 2020 Password4j (http://password4j.com/).
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 | package com.password4j;
18 |
19 | import org.slf4j.Logger;
20 | import org.slf4j.LoggerFactory;
21 |
22 | import java.io.FileInputStream;
23 | import java.io.FileNotFoundException;
24 | import java.io.IOException;
25 | import java.io.InputStream;
26 | import java.security.AccessControlException;
27 | import java.util.Properties;
28 |
29 |
30 | class PropertyReader
31 | {
32 |
33 | private static final Logger LOG = LoggerFactory.getLogger(PropertyReader.class);
34 |
35 | private static final String FILE_NAME = "psw4j.properties";
36 |
37 | private static final String CONFIGURATION_KEY = "psw4j.configuration";
38 |
39 | private static final String MESSAGE = "{}. Default value is used ({}). Please set property {} in your " + FILE_NAME + " file.";
40 |
41 | protected static Properties properties;
42 |
43 | static
44 | {
45 | init();
46 | }
47 |
48 | private PropertyReader()
49 | {
50 | //
51 | }
52 |
53 | static int readInt(String key, int defaultValue, String message)
54 | {
55 | String str = readString(key);
56 | if (str == null)
57 | {
58 | LOG.warn(MESSAGE, message, defaultValue, key);
59 | return defaultValue;
60 | }
61 | return Integer.parseInt(str);
62 | }
63 |
64 | static boolean readBoolean(String key, boolean defaultValue)
65 | {
66 | String str = readString(key);
67 | if (str == null)
68 | {
69 | return defaultValue;
70 | }
71 | return Boolean.parseBoolean(str);
72 | }
73 |
74 | static String readString(String key, String defaultValue, String message)
75 | {
76 | String value = readString(key);
77 | if (value == null)
78 | {
79 | LOG.warn(MESSAGE, message, defaultValue, key);
80 | return defaultValue;
81 | }
82 | return value;
83 | }
84 |
85 | static char readChar(String key, char defaultValue, String message)
86 | {
87 | String str = readString(key);
88 | if (str == null)
89 | {
90 | LOG.warn(MESSAGE, message, defaultValue, key);
91 | return defaultValue;
92 | }
93 | return str.charAt(0);
94 | }
95 |
96 | static char readChar(String key, char defaultValue)
97 | {
98 | String str = readString(key);
99 | if (str == null)
100 | {
101 | return defaultValue;
102 | }
103 | return str.charAt(0);
104 | }
105 |
106 | private static String readString(String key)
107 | {
108 | if (key == null)
109 | {
110 | throw new BadParametersException("Key cannot be null");
111 | }
112 |
113 | if (properties != null)
114 | {
115 | return properties.getProperty(key);
116 | }
117 | return null;
118 | }
119 |
120 | static void init()
121 | {
122 | String customPath = null;
123 |
124 | try
125 | {
126 | customPath = System.getProperty(CONFIGURATION_KEY, null);
127 | }
128 | catch (AccessControlException ex)
129 | {
130 | LOG.debug("Cannot access configuration key property", ex);
131 | }
132 |
133 | InputStream in = null;
134 | Properties props = new Properties();
135 | try
136 | {
137 | if (customPath == null || customPath.isEmpty())
138 | {
139 | in = getResource('/' + FILE_NAME);
140 | }
141 | else
142 | {
143 | in = getResource(customPath);
144 | }
145 | }
146 | catch (AccessControlException ex)
147 | {
148 | LOG.debug("Cannot access properties file", ex);
149 | props.setProperty("global.banner", "false");
150 | }
151 |
152 | if (in != null)
153 | {
154 | try
155 | {
156 | props.load(in);
157 | }
158 | catch (IOException e)
159 | {
160 | //
161 | }
162 | }
163 | else
164 | {
165 | LOG.debug("Cannot find any properties file.");
166 | }
167 |
168 | properties = props;
169 | }
170 |
171 | static InputStream getResource(String resource)
172 | {
173 | ClassLoader classLoader;
174 | InputStream in;
175 |
176 | try
177 | {
178 | classLoader = Thread.currentThread().getContextClassLoader();
179 | if (classLoader != null)
180 | {
181 | in = classLoader.getResourceAsStream(resource);
182 | if (in != null)
183 | {
184 | return in;
185 | }
186 | }
187 |
188 | // Try with the class loader that loaded this class
189 | classLoader = PropertyReader.class.getClassLoader();
190 | if (classLoader != null)
191 | {
192 | in = classLoader.getResourceAsStream(resource);
193 | if (in != null)
194 | {
195 | return in;
196 | }
197 | }
198 | }
199 | catch (Exception e)
200 | {
201 | LOG.warn("", e);
202 | }
203 |
204 | // Get the resource from the class path in case that the class is loaded
205 | // by the Extension class loader which the parent of the system class loader.
206 | in = ClassLoader.getSystemResourceAsStream(resource);
207 | if (in != null)
208 | {
209 | return in;
210 | }
211 |
212 | try
213 | {
214 | return new FileInputStream(resource);
215 | }
216 | catch (FileNotFoundException e)
217 | {
218 | return PropertyReader.class.getResourceAsStream(resource);
219 | }
220 |
221 | }
222 |
223 | }
224 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | When contributing to this repository, please first discuss the change you wish to make via issue,
4 | email, or any other method with the owners of this repository before making a change.
5 |
6 | Please note we have a code of conduct, please follow it in all your interactions with the project.
7 |
8 | ## Pull Request Process
9 |
10 | 1. Ensure any install or build dependencies are removed before the end of the layer when doing a
11 | build.
12 | 2. Update the README.md with details of changes to the interface, this includes new environment
13 | variables, exposed ports, useful file locations and container parameters.
14 | 3. Increase the version numbers in any examples files and the README.md to the new version that this
15 | Pull Request would represent. The versioning scheme we use is [SemVer](http://semver.org/).
16 | 4. You may merge the Pull Request in once you have the sign-off of two other developers, or if you
17 | do not have permission to do that, you may request the second reviewer to merge it for you.
18 |
19 | ## Code of Conduct
20 |
21 | ### Our Pledge
22 |
23 | We as members, contributors, and leaders pledge to make participation in our
24 | community a harassment-free experience for everyone, regardless of age, body
25 | size, visible or invisible disability, ethnicity, sex characteristics, gender
26 | identity and expression, level of experience, education, socio-economic status,
27 | nationality, personal appearance, race, religion, or sexual identity
28 | and orientation.
29 |
30 | We pledge to act and interact in ways that contribute to an open, welcoming,
31 | diverse, inclusive, and healthy community.
32 |
33 | ### Our Standards
34 |
35 | Examples of behavior that contributes to a positive environment for our
36 | community include:
37 |
38 | * Demonstrating empathy and kindness toward other people
39 | * Being respectful of differing opinions, viewpoints, and experiences
40 | * Giving and gracefully accepting constructive feedback
41 | * Accepting responsibility and apologizing to those affected by our mistakes,
42 | and learning from the experience
43 | * Focusing on what is best not just for us as individuals, but for the
44 | overall community
45 |
46 | Examples of unacceptable behavior include:
47 |
48 | * The use of sexualized language or imagery, and sexual attention or
49 | advances of any kind
50 | * Trolling, insulting or derogatory comments, and personal or political attacks
51 | * Public or private harassment
52 | * Publishing others' private information, such as a physical or email
53 | address, without their explicit permission
54 | * Other conduct which could reasonably be considered inappropriate in a
55 | professional setting
56 |
57 | ### Enforcement Responsibilities
58 |
59 | Community leaders are responsible for clarifying and enforcing our standards of
60 | acceptable behavior and will take appropriate and fair corrective action in
61 | response to any behavior that they deem inappropriate, threatening, offensive,
62 | or harmful.
63 |
64 | Community leaders have the right and responsibility to remove, edit, or reject
65 | comments, commits, code, wiki edits, issues, and other contributions that are
66 | not aligned to this Code of Conduct, and will communicate reasons for moderation
67 | decisions when appropriate.
68 |
69 | ### Scope
70 |
71 | This Code of Conduct applies within all community spaces, and also applies when
72 | an individual is officially representing the community in public spaces.
73 | Examples of representing our community include using an official e-mail address,
74 | posting via an official social media account, or acting as an appointed
75 | representative at an online or offline event.
76 |
77 | ### Enforcement
78 |
79 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
80 | reported to the community leaders.
81 | All complaints will be reviewed and investigated promptly and fairly.
82 |
83 | All community leaders are obligated to respect the privacy and security of the
84 | reporter of any incident.
85 |
86 | ### Enforcement Guidelines
87 |
88 | Community leaders will follow these Community Impact Guidelines in determining
89 | the consequences for any action they deem in violation of this Code of Conduct:
90 |
91 | #### 1. Correction
92 |
93 | **Community Impact**: Use of inappropriate language or other behavior deemed
94 | unprofessional or unwelcome in the community.
95 |
96 | **Consequence**: A private, written warning from community leaders, providing
97 | clarity around the nature of the violation and an explanation of why the
98 | behavior was inappropriate. A public apology may be requested.
99 |
100 | #### 2. Warning
101 |
102 | **Community Impact**: A violation through a single incident or series
103 | of actions.
104 |
105 | **Consequence**: A warning with consequences for continued behavior. No
106 | interaction with the people involved, including unsolicited interaction with
107 | those enforcing the Code of Conduct, for a specified period of time. This
108 | includes avoiding interactions in community spaces as well as external channels
109 | like social media. Violating these terms may lead to a temporary or
110 | permanent ban.
111 |
112 | #### 3. Temporary Ban
113 |
114 | **Community Impact**: A serious violation of community standards, including
115 | sustained inappropriate behavior.
116 |
117 | **Consequence**: A temporary ban from any sort of interaction or public
118 | communication with the community for a specified period of time. No public or
119 | private interaction with the people involved, including unsolicited interaction
120 | with those enforcing the Code of Conduct, is allowed during this period.
121 | Violating these terms may lead to a permanent ban.
122 |
123 | #### 4. Permanent Ban
124 |
125 | **Community Impact**: Demonstrating a pattern of violation of community
126 | standards, including sustained inappropriate behavior, harassment of an
127 | individual, or aggression toward or disparagement of classes of individuals.
128 |
129 | **Consequence**: A permanent ban from any sort of public interaction within
130 | the community.
131 |
132 | ### Attribution
133 |
134 | This Code of Conduct is adapted from the [Contributor Covenant][homepage],
135 | version 2.0, available at
136 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
137 |
138 | Community Impact Guidelines were inspired by [Mozilla's code of conduct
139 | enforcement ladder](https://github.com/mozilla/diversity).
140 |
141 | [homepage]: https://www.contributor-covenant.org
142 |
143 | For answers to common questions about this code of conduct, see the FAQ at
144 | https://www.contributor-covenant.org/faq. Translations are available at
145 | https://www.contributor-covenant.org/translations.
--------------------------------------------------------------------------------
/src/test/com/password4j/BalloonHashingFunctionTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * (C) Copyright 2023 Password4j (http://password4j.com/).
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package com.password4j;
19 |
20 | import com.password4j.types.Argon2;
21 | import com.password4j.types.Bcrypt;
22 | import org.junit.Assert;
23 | import org.junit.Test;
24 |
25 | import java.nio.charset.StandardCharsets;
26 | import java.util.Arrays;
27 |
28 | public class BalloonHashingFunctionTest
29 | {
30 |
31 | private static final Object[][] TEST_VECTORS = new Object[][]{
32 | // Single thread
33 | new Object[]{"hunter42", "examplesalt", "SHA-256", 1024, 3, 0, 3, "716043dff777b44aa7b88dcbab12c078abecfac9d289c5b5195967aa63440dfb"},
34 | new Object[]{"", "salt", "SHA-256", 3, 3, 0, 3, "5f02f8206f9cd212485c6bdf85527b698956701ad0852106f94b94ee94577378"},
35 | new Object[]{"password", "", "SHA-256", 3, 3, 0, 3, "20aa99d7fe3f4df4bd98c655c5480ec98b143107a331fd491deda885c4d6a6cc"},
36 | new Object[]{"\0", "\0", "SHA-256", 3, 3, 0, 3, "4fc7e302ffa29ae0eac31166cee7a552d1d71135f4e0da66486fb68a749b73a4"},
37 | new Object[]{"password", "salt", "SHA-256", 1, 1, 0, 3, "eefda4a8a75b461fa389c1dcfaf3e9dfacbc26f81f22e6f280d15cc18c417545"},
38 |
39 | // Multiple threads
40 | new Object[]{"hunter42", "examplesalt", "SHA-256", 1024, 3, 4, 3, "1832bd8e5cbeba1cb174a13838095e7e66508e9bf04c40178990adbc8ba9eb6f"},
41 | new Object[]{"", "salt", "SHA-256", 3, 3, 2, 3, "f8767fe04059cef67b4427cda99bf8bcdd983959dbd399a5e63ea04523716c23"},
42 | new Object[]{"password", "", "SHA-256", 3, 3, 3, 3, "bcad257eff3d1090b50276514857e60db5d0ec484129013ef3c88f7d36e438d6"},
43 | new Object[]{"password", "", "SHA-256", 3, 3, 1, 3, "498344ee9d31baf82cc93ebb3874fe0b76e164302c1cefa1b63a90a69afb9b4d"},
44 | new Object[]{"\000", "\000", "SHA-256", 3, 3, 4, 3, "8a665611e40710ba1fd78c181549c750f17c12e423c11930ce997f04c7153e0c"},
45 | new Object[]{"\000", "\000", "SHA-256", 3, 3, 1, 3, "d9e33c683451b21fb3720afbd78bf12518c1d4401fa39f054b052a145c968bb1"},
46 | new Object[]{"password", "salt", "SHA-256", 1, 1, 16, 3, "a67b383bb88a282aef595d98697f90820adf64582a4b3627c76b7da3d8bae915"},
47 | new Object[]{"password", "salt", "SHA-256", 1, 1, 1, 3, "97a11df9382a788c781929831d409d3599e0b67ab452ef834718114efdcd1c6d"},
48 |
49 | };
50 |
51 |
52 | @Test
53 | public void test()
54 | {
55 |
56 | BalloonHashingFunction balloonHashingFunction;
57 | for (Object[] testVector : TEST_VECTORS)
58 | {
59 | balloonHashingFunction = new BalloonHashingFunction((String) testVector[2], (Integer) testVector[3], (Integer) testVector[4], (Integer) testVector[5], (Integer) testVector[6]);
60 | Assert.assertEquals(testVector[7], balloonHashingFunction.hash((String) testVector[0], (String) testVector[1]).getResult());
61 |
62 | Assert.assertTrue(balloonHashingFunction.check((String) testVector[0], (String) testVector[7], (String) testVector[1]));
63 | }
64 |
65 | }
66 |
67 | @Test
68 | public void testInstance()
69 | {
70 |
71 | BalloonHashingFunction balloonHashingFunction;
72 | for (Object[] testVector : TEST_VECTORS)
73 | {
74 | balloonHashingFunction = BalloonHashingFunction.getInstance((String) testVector[2], (Integer) testVector[3], (Integer) testVector[4], (Integer) testVector[5], (Integer) testVector[6]);
75 | Assert.assertEquals(testVector[7], balloonHashingFunction.hash((String) testVector[0], (String) testVector[1]).getResult());
76 | Assert.assertEquals(testVector[7], balloonHashingFunction.hash(((String) testVector[0]).getBytes(), ((String) testVector[1]).getBytes()).getResult());
77 |
78 | Assert.assertTrue(balloonHashingFunction.check((String) testVector[0], (String) testVector[7], (String) testVector[1]));
79 | Assert.assertTrue(balloonHashingFunction.check(((String) testVector[0]).getBytes(), ((String) testVector[7]).getBytes(), ((String) testVector[1]).getBytes()));
80 | }
81 |
82 | }
83 |
84 | @Test
85 | public void testEquality()
86 | {
87 | // GIVEN
88 | String m = "SHA-256";
89 | int i = 2;
90 | int p = 3;
91 | int l = 4;
92 | int v = 5;
93 | BalloonHashingFunction balloonHashingFunction = BalloonHashingFunction.getInstance(m, i, p, l, v);
94 |
95 | // THEN
96 | boolean eqNull = balloonHashingFunction.equals(null);
97 | boolean eqClass = balloonHashingFunction.equals(new BcryptFunction(Bcrypt.A, 10));
98 | boolean sameInst = balloonHashingFunction.equals(BalloonHashingFunction.getInstance(m, i, p, l, v));
99 | boolean sameInst2 = balloonHashingFunction.equals(new BalloonHashingFunction(m, i, p, l, v));
100 | String toString = balloonHashingFunction.toString();
101 | int hashCode = balloonHashingFunction.hashCode();
102 | boolean notSameInst1 = balloonHashingFunction.equals(new BalloonHashingFunction("SHA-512", i, p, l, v));
103 | boolean notSameInst2 = balloonHashingFunction.equals(new BalloonHashingFunction(m, i+1, p, l, v));
104 | boolean notSameInst3 = balloonHashingFunction.equals(new BalloonHashingFunction(m, i, p+1, l, v));
105 | boolean notSameInst4 = balloonHashingFunction.equals(new BalloonHashingFunction(m, i, p, l+1, v));
106 | boolean notSameInst6 = balloonHashingFunction.equals(new BalloonHashingFunction(m, i, p, l, v+1));
107 |
108 | // END
109 | Assert.assertFalse(eqNull);
110 | Assert.assertFalse(eqClass);
111 | Assert.assertTrue(sameInst);
112 | Assert.assertTrue(sameInst2);
113 | Assert.assertNotEquals(toString, new BalloonHashingFunction(m, i+1, p, l, v).toString());
114 | Assert.assertNotEquals(hashCode, new BalloonHashingFunction(m, i, p, l, v+1).hashCode());
115 | Assert.assertFalse(notSameInst1);
116 | Assert.assertFalse(notSameInst2);
117 | Assert.assertFalse(notSameInst3);
118 | Assert.assertFalse(notSameInst4);
119 | Assert.assertFalse(notSameInst6);
120 | }
121 |
122 | }
123 |
--------------------------------------------------------------------------------
/src/test/org/example/project/PublicPasswordTest.java:
--------------------------------------------------------------------------------
1 | package org.example.project;
2 |
3 | import com.password4j.*;
4 | import com.password4j.types.Argon2;
5 | import com.password4j.types.Hmac;
6 | import org.junit.Assert;
7 | import org.junit.Test;
8 |
9 |
10 | public class PublicPasswordTest
11 | {
12 |
13 | private static final TestSuite[] PBKDF2_TEST = new TestSuite[]{
14 | new TestSuite("r+bFUweFtsxrHGRTOEcxvV7kMu5Un9QvtmlXea2KHFv1neacSPd078QAfVKY+QM8AkHVq2kwXntk7O642DTP7A==", "password",
15 | "salt", null, PBKDF2Function.getInstance(Hmac.SHA512, 1000, 512)),
16 |
17 | new TestSuite("x/daChKTPQGTKZrBsmPIqJ3KtqcaYni8FqdziEgPRw9gowIpZxfzW7UI8gqZj0pI5xChr5RDxjYjc8yMbucHHw==", "password",
18 | "salt", "pepper", PBKDF2Function.getInstance(Hmac.SHA512, 1000, 512)),
19 |
20 | new TestSuite("EgvuM3qhGradmNwl2b1Z5uPnasY=", "123", "456", "",
21 | PBKDF2Function.getInstance(Hmac.SHA1, 49999, 160)),
22 |
23 | new TestSuite("$2a$06$If6bvum7DFjUnE9p2uDeDu0YHzrHM6tf.iqN8.yx.jNN1ILEf7h0i", "bc", "$2a$06$If6bvum7DFjUnE9p2uDeDu", "a",
24 | BcryptFunction.getInstance(6)),
25 |
26 | new TestSuite("$2a$09$PVRpK74XnUl/dsFfw6YSsOgwnJ1N3b5jKgbR/qdqerkuIYMa2u6eG", "Password", "$2a$09$PVRpK74XnUl/dsFfw6YSsO", "my",
27 | BcryptFunction.getInstance(9)),
28 |
29 | new TestSuite("$2a$07$W3mOfB5auMDG3EitumH0S.ffmkA.NZIOZFaXb15tPWQyqq0hDXiEC", "Alice", "$2a$07$W3mOfB5auMDG3EitumH0S.", null,
30 | BcryptFunction.getInstance(7)),
31 |
32 | new TestSuite("$2a$14$7rdjAp2vQxO0hCK9GvniqeKURflehmGaW5C2CLOONKZauODS7xOGW", "password4j", "$2a$14$7rdjAp2vQxO0hCK9Gvniqe", null,
33 | BcryptFunction.getInstance(14)),
34 |
35 | new TestSuite("$e0801$c2FsdA==$dFcxr0SE8yOWiWntoomu7gBbWQOsVh5kpayhIXl793NO+f1YQi4uIhg7ysup7Ie6DIO3oueI8Dzg2gZGNDPNpg==", "word", "salt",
36 | "pass", ScryptFunction.getInstance(16384, 8, 1)),
37 |
38 | new TestSuite("$a0402$bm90UmFuZG9t$upriFfo7v+aAUqOKDpguh0duZlAHiKcQOLM0k/xFcBg7qfRcDfYLEZe/60+b+4NtA1M70LUI0IRY+3+ybuLMZg==", "known", "notRandom",
39 | "un", ScryptFunction.getInstance(1024, 4, 2)),
40 |
41 | new TestSuite("$argon2id$v=19$m=1024,t=3,p=12$MTExMTExMTE$0PUE8wVEaK0qdjms3b4pTZOs0+00S/+9j28WZ3gMUno", "first!", "11111111",
42 | null, Argon2Function.getInstance(1024, 3, 12, 32, Argon2.ID)),
43 |
44 | new TestSuite("$argon2id$v=19$m=4096,t=20,p=4$NUdyNEV4Yzc3RG9QOQ$aY701D5E9/hCqO4HMVChlGuR2PPYntYLcr8RsxFi/Xo", "password", "5Gr4Exc77DoP9",
45 | "my custom pepper", Argon2Function.getInstance(4096, 20, 4, 32, Argon2.ID))
46 | };
47 |
48 | @Test
49 | public void test()
50 | {
51 |
52 | for (TestSuite test : PBKDF2_TEST)
53 | {
54 | Assert.assertEquals(test.hashingFunction.toString(), test.hash,
55 | Password.hash(test.password).addSalt(test.salt).addPepper(test.pepper).with(test.hashingFunction)
56 | .getResult());
57 |
58 | SecureString securePassword = new SecureString(test.password.toCharArray());
59 | Assert.assertEquals(test.hashingFunction.toString(), test.hash,
60 | Password.hash(securePassword).addSalt(test.salt).addPepper(test.pepper).with(test.hashingFunction)
61 | .getResult());
62 |
63 | }
64 |
65 | }
66 |
67 | @Test
68 | public void testUpdate()
69 | {
70 |
71 | for (TestSuite test : PBKDF2_TEST)
72 | {
73 | Assert.assertEquals(test.hashingFunction.toString(), test.hash,
74 | Password.hash(test.password).addSalt(test.salt).addPepper(test.pepper).with(test.hashingFunction)
75 | .getResult());
76 |
77 | HashUpdate update = Password.check(test.password, test.hash).addSalt(test.salt).addPepper(test.pepper).andUpdate().with(test.hashingFunction, test.hashingFunction);
78 | Assert.assertTrue(test.hashingFunction.toString(), update.isVerified());
79 | Assert.assertEquals(test.hashingFunction.toString(), Password.hash(test.password).addSalt(test.salt).addPepper(test.pepper).with(test.hashingFunction).getResult(), update.getHash().getResult());
80 |
81 | }
82 |
83 | }
84 |
85 | /**
86 | * Must compile.
87 | */
88 | @Test
89 | public void testAccessibility()
90 | {
91 | try
92 | {
93 | String password = "";
94 | String salt = "";
95 | int saltLength = 1;
96 | String pepper = "";
97 |
98 | HashBuilder hb = Password.hash(password);
99 | hb.addPepper(pepper);
100 | hb.addPepper();
101 | hb.addRandomSalt();
102 | hb.addRandomSalt(saltLength);
103 | hb.addSalt(salt);
104 |
105 | hb.withCompressedPBKDF2();
106 | hb.withScrypt();
107 | hb.withBcrypt();
108 | hb.withPBKDF2();
109 | hb.withArgon2();
110 |
111 | HashChecker hc = Password.check(password, password);
112 | hc.addPepper(pepper);
113 | hc.addPepper();
114 | hc.addSalt(salt);
115 | hc.withCompressedPBKDF2();
116 | hc.withScrypt();
117 | hc.withBcrypt();
118 | hc.withPBKDF2();
119 | hc.withArgon2();
120 |
121 | Hmac.SHA256.code();
122 | Hmac.values();
123 | Hmac.SHA1.bits();
124 |
125 | PBKDF2Function.getInstance(Hmac.SHA512, 1, 1);
126 | PBKDF2Function.getInstance(password, 1, 1);
127 | CompressedPBKDF2Function.getInstance(Hmac.SHA512, 1, 1);
128 | CompressedPBKDF2Function.getInstance(password, 1, 1);
129 | CompressedPBKDF2Function.getInstanceFromHash(password);
130 |
131 | BcryptFunction.getInstance(1);
132 | ScryptFunction.getInstance(2, 1, 1);
133 | ScryptFunction.getInstanceFromHash(password);
134 |
135 | Argon2Function.getInstance(8, 1, 1, 32, Argon2.ID);
136 | Argon2Function.getInstanceFromHash(password);
137 |
138 | SecureString s = new SecureString(new char[]{'a'});
139 | s.clear();
140 | }
141 | catch (Exception e)
142 | {
143 | //
144 | }
145 | }
146 |
147 | private static class TestSuite
148 | {
149 | private String hash;
150 |
151 | private String password;
152 |
153 | private String salt;
154 |
155 | private String pepper;
156 |
157 | private HashingFunction hashingFunction;
158 |
159 | TestSuite(String hash, String password, String salt, String pepper, HashingFunction hashingFunction)
160 | {
161 | this.hash = hash;
162 | this.password = password;
163 | this.salt = salt;
164 | this.pepper = pepper;
165 | this.hashingFunction = hashingFunction;
166 | }
167 | }
168 |
169 | }
170 |
--------------------------------------------------------------------------------
/src/test/com/password4j/StringTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * (C) Copyright 2020 Password4j (http://password4j.com/).
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package com.password4j;
19 |
20 | import org.junit.Assert;
21 | import org.junit.Test;
22 |
23 | import java.nio.CharBuffer;
24 | import java.util.Arrays;
25 |
26 |
27 | public class StringTest
28 | {
29 | @Test
30 | public void testConstructors()
31 | {
32 | // GIVEN
33 | char[] password = new char[] { 'a', 'b', 'c', 'd', 'e', 'f' };
34 |
35 | // WHEN
36 | SecureString ss = new SecureString(password);
37 | CharSequence sub1 = ss.subSequence(1, 4);
38 | SecureString sub2 = new SecureString(password, 1, 4);
39 |
40 | // THEN
41 | Assert.assertEquals(sub1.length(), sub2.length());
42 | Assert.assertEquals(Arrays.toString(new char[] { 'b', 'c', 'd' }),
43 | Arrays.toString(Utils.fromCharSequenceToChars(sub1)));
44 | Assert.assertEquals(Arrays.toString(Utils.fromCharSequenceToChars(sub1)),
45 | Arrays.toString(Utils.fromCharSequenceToChars(sub2)));
46 |
47 | }
48 |
49 | @Test
50 | public void testClear()
51 | {
52 | // GIVEN
53 | char[] password = new char[] { 'a', 'b', 'c', 'd' };
54 |
55 | // WHEN
56 | SecureString ss = new SecureString(password);
57 | ss.clear();
58 |
59 | // THEN
60 | char z = Character.MIN_VALUE;
61 | Assert.assertEquals(Arrays.toString(new char[] { z, z, z, z }), Arrays.toString(Utils.fromCharSequenceToChars(ss)));
62 | }
63 |
64 | @Test(expected = NullPointerException.class)
65 | public void testNull()
66 | {
67 | new SecureString(null);
68 | }
69 |
70 | @Test(expected = NullPointerException.class)
71 | public void testNull2()
72 | {
73 | new SecureString(null, 0, 0);
74 | }
75 |
76 | @Test(expected = ArrayIndexOutOfBoundsException.class)
77 | public void testOut1()
78 | {
79 | new SecureString(new char[] { 'a', 'b', 'c', 'd' }, 0, 10);
80 | }
81 |
82 | @Test(expected = NegativeArraySizeException.class)
83 | public void testOut2()
84 | {
85 | new SecureString(new char[] { 'a', 'b', 'c', 'd' }, 0, -4);
86 | }
87 |
88 | @Test(expected = NegativeArraySizeException.class)
89 | public void testOut3()
90 | {
91 | new SecureString(new char[] { 'a', 'b', 'c', 'd' }, 3, 0);
92 | }
93 |
94 | @Test
95 | public void testEmpty()
96 | {
97 | SecureString ss = new SecureString(new char[0]);
98 |
99 | Assert.assertEquals("SecureString[****]", ss.toString());
100 | Assert.assertEquals(0, ss.length());
101 | try
102 | {
103 | ss.charAt(0);
104 | Assert.fail();
105 | }
106 | catch (ArrayIndexOutOfBoundsException e)
107 | {
108 | Assert.assertTrue(true);
109 | }
110 |
111 | }
112 |
113 | @Test
114 | public void testToString()
115 | {
116 | SecureString ss = new SecureString(new char[] { 'a', 'b', 'c', 'd' });
117 |
118 | Assert.assertEquals("SecureString[****]", ss.toString());
119 | }
120 |
121 | @Test
122 | public void erase()
123 | {
124 | // GIVEN
125 | char[] password1 = new char[] { 'a', 'b', 'c', 'd' };
126 | char[] password2 = new char[] { 'a', 'b', 'c', 'd' };
127 |
128 | // WHEN
129 | new SecureString(password1, true);
130 | new SecureString(password2, false);
131 |
132 | // THEN
133 | char z = Character.MIN_VALUE;
134 | Assert.assertEquals(Arrays.toString(new char[] { z, z, z, z }), Arrays.toString(password1));
135 | Assert.assertEquals(Arrays.toString(new char[] { 'a', 'b', 'c', 'd' }), Arrays.toString(password2));
136 | }
137 |
138 | @Test
139 | public void testEquality()
140 | {
141 | char[] password = new char[] { 'a', 'b', 'c', 'd' };
142 | String str = new String(password);
143 | SecureString ss = new SecureString(password);
144 | SecureString ss2 = (SecureString) Utils.append(ss, "123");
145 |
146 | Assert.assertTrue(((CharSequence) ss).equals(str) && ((CharSequence) ss).equals(CharBuffer.wrap(password)));
147 | Assert.assertNotEquals(null, ss);
148 | Assert.assertNotEquals("cbad", ss);
149 | Assert.assertEquals(new SecureString(password), ss);
150 | Assert.assertNotEquals(new SecureString(new char[] { 'b', 'b', 'b', 'b' }), ss);
151 | Assert.assertEquals(ss, ss);
152 | Assert.assertEquals(Arrays.hashCode(password), ss.hashCode());
153 |
154 | Assert.assertNotEquals(ss, 123);
155 | Assert.assertNotEquals(ss2, ss);
156 | }
157 |
158 | @Test
159 | public void testUtilities()
160 | {
161 | char[] c1 = Utils.fromCharSequenceToChars(null);
162 | char[] c2 = Utils.fromCharSequenceToChars(new String(new char[0]));
163 | byte[] b1 = Utils.fromCharSequenceToBytes(null);
164 | byte[] b2 = Utils.fromCharSequenceToBytes(new String(new char[0]));
165 | byte[] b3 = Utils.fromCharSequenceToBytes(new String(new char[]{(char)1}));
166 |
167 | CharSequence cs1 = Utils.append("a", null);
168 | CharSequence cs2 = Utils.append(null, "b");
169 |
170 | CharSequence a1 = Utils.append(new SecureString(new char[] { 'a', 'b', 'c' }), "def");
171 | CharSequence a2 = Utils.append(null, "def");
172 | CharSequence a3 = Utils.append(new SecureString(new char[0]), "def");
173 | CharSequence a4 = Utils.append("abc", null);
174 | CharSequence a5 = Utils.append("abc", new SecureString(new char[0]));
175 |
176 | Assert.assertEquals(Arrays.toString(c1), Arrays.toString(c2));
177 | Assert.assertEquals(Arrays.toString(b1), Arrays.toString(b2));
178 | Assert.assertEquals(1, b3.length);
179 | Assert.assertEquals(1, b3[0]);
180 | Assert.assertEquals("a", cs1);
181 | Assert.assertEquals("b", cs2);
182 | Assert.assertEquals(Arrays.toString(new char[] { 'a', 'b', 'c', 'd', 'e', 'f' }),
183 | Arrays.toString(Utils.fromCharSequenceToChars(a1)));
184 | Assert.assertEquals(Arrays.toString(new char[] { 'd', 'e', 'f' }),
185 | Arrays.toString(Utils.fromCharSequenceToChars(a2)));
186 | Assert.assertEquals(Arrays.toString(new char[] { 'd', 'e', 'f' }),
187 | Arrays.toString(Utils.fromCharSequenceToChars(a3)));
188 | Assert.assertEquals(Arrays.toString(new char[] { 'a', 'b', 'c' }),
189 | Arrays.toString(Utils.fromCharSequenceToChars(a4)));
190 | Assert.assertEquals(Arrays.toString(new char[] { 'a', 'b', 'c' }),
191 | Arrays.toString(Utils.fromCharSequenceToChars(a5)));
192 | }
193 | }
194 |
--------------------------------------------------------------------------------
/src/main/java/com/password4j/Password.java:
--------------------------------------------------------------------------------
1 | /*
2 | * (C) Copyright 2020 Password4j (http://password4j.com/).
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 | package com.password4j;
18 |
19 |
20 | /**
21 | * This class provides the two main operations on password: hash and verify.
22 | *
23 | * All the methods are static because there's no sense to
24 | * have an instance of {@link Password}.
25 | * It represents a facade layer that ease the usage of the
26 | * package itself and so this should be the only class
27 | * to be invoked from this package.
28 | *
29 | * @author David Bertoldi
30 | * @since 0.1.1
31 | */
32 | public class Password
33 | {
34 |
35 | static
36 | {
37 | Utils.printBanner(System.out); //NOSONAR
38 | }
39 |
40 | private Password()
41 | {
42 | //
43 | }
44 |
45 | /**
46 | * Starts to hash the given plain text password.
47 | *
48 | * This method is used to start the setup of a {@link HashBuilder}
49 | * instance that finally should execute the {@link HashBuilder#with(HashingFunction)}
50 | * method to hash the password.
51 | *
52 | * @param plainTextPassword the plain text password
53 | * @return a builder instance of {@link HashBuilder}
54 | * @throws BadParametersException if any of the arguments are null.
55 | * @since 0.1.1
56 | */
57 | public static HashBuilder hash(CharSequence plainTextPassword)
58 | {
59 | if (plainTextPassword == null)
60 | {
61 | throw new BadParametersException("Password cannot be null");
62 | }
63 | return new HashBuilder(plainTextPassword);
64 | }
65 |
66 | /**
67 | * Starts to hash the given plain text password.
68 | *
69 | * This method is used to start the setup of a {@link HashBuilder}
70 | * instance that finally should execute the {@link HashBuilder#with(HashingFunction)}
71 | * method to hash the password.
72 | *
73 | * @param plainTextPassword the plain text password as bytes array
74 | * @return a builder instance of {@link HashBuilder}
75 | * @throws BadParametersException if any of the arguments are null.
76 | * @since 1.7.0
77 | */
78 | public static HashBuilder hash(byte[] plainTextPassword)
79 | {
80 | if (plainTextPassword == null || plainTextPassword.length == 0)
81 | {
82 | throw new BadParametersException("Password cannot be null");
83 | }
84 | return new HashBuilder(plainTextPassword);
85 | }
86 |
87 |
88 | /**
89 | * Starts to verify if a hash string has been generated with
90 | * the given plain text password.
91 | *
92 | * This method is used to start the setup of an {@link HashChecker}
93 | * instance that finally should execute the {@link HashChecker#with(HashingFunction)}
94 | * method to verify the hash.
95 | *
96 | * @param plainTextPassword the plain text password
97 | * @param hash a hash string
98 | * @return a builder instance of {@link HashChecker}
99 | * @throws BadParametersException if any of the arguments are null.
100 | * @since 0.1.1
101 | */
102 | public static HashChecker check(CharSequence plainTextPassword, String hash)
103 | {
104 | if (hash == null || plainTextPassword == null)
105 | {
106 | throw new BadParametersException("Hash or plain cannot be null");
107 | }
108 | return new HashChecker(plainTextPassword, hash);
109 | }
110 |
111 |
112 | /**
113 | * Starts to verify if a hash string has been generated with
114 | * the given plain text password.
115 | *
116 | * This method is used to start the setup of an {@link HashChecker}
117 | * instance that finally should execute the {@link HashChecker#with(HashingFunction)}
118 | * method to verify the hash.
119 | *
120 | * @param plainTextPassword the plain text password as bytes array
121 | * @param hash a hash string as bytes array
122 | * @return a builder instance of {@link HashChecker}
123 | * @throws BadParametersException if any of the arguments are null.
124 | * @since 1.7.0
125 | */
126 | public static HashChecker check(byte[] plainTextPassword, byte[] hash)
127 | {
128 | if (hash == null || plainTextPassword == null || hash.length == 0 || plainTextPassword.length == 0)
129 | {
130 | throw new BadParametersException("Hash or plain cannot be null");
131 | }
132 | return new HashChecker(plainTextPassword, hash);
133 | }
134 |
135 | /**
136 | * Starts to verify if a hash object has been generated with
137 | * the given plain text password.
138 | *
139 | * This method uses the {@link HashingFunction} used to calculate the given {@link Hash}.
140 | * Il the password is null, this returns false;
141 | * otherwise {@link HashingFunction#check(CharSequence, String)} is invoked.
142 | *
143 | * @param plainTextPassword the original password.
144 | * @param hashObject an {@link Hash} object.
145 | * @return true if the check passes, false otherwise.
146 | * @throws BadParametersException if the Hash is null or if there's no hashing function defined in it.
147 | * @since 1.0.3
148 | */
149 | public static boolean check(CharSequence plainTextPassword, Hash hashObject)
150 | {
151 | return check(Utils.fromCharSequenceToBytes(plainTextPassword), hashObject);
152 | }
153 |
154 | /**
155 | * Starts to verify if a hash object has been generated with
156 | * the given plain text password.
157 | *
158 | * This method uses the {@link HashingFunction} used to calculate the given {@link Hash}.
159 | * Il the password is null, this returns false;
160 | * otherwise {@link HashingFunction#check(CharSequence, String)} is invoked.
161 | *
162 | * @param plainTextPassword the original password as bytes array.
163 | * @param hashObject an {@link Hash} object.
164 | * @return true if the check passes, false otherwise.
165 | * @throws BadParametersException if the Hash is null or if there's no hashing function defined in it.
166 | * @since 1.7.0
167 | */
168 | public static boolean check(byte[] plainTextPassword, Hash hashObject)
169 | {
170 | if (hashObject == null || hashObject.getHashingFunction() == null)
171 | {
172 | throw new BadParametersException("Invalid Hash object. " + (hashObject != null ? hashObject.toString() : null));
173 | }
174 | if (plainTextPassword == null)
175 | {
176 | return false;
177 | }
178 |
179 | return hashObject.getHashingFunction().check(plainTextPassword, hashObject.getResultAsBytes(), hashObject.getSaltBytes(), hashObject.getPepper());
180 | }
181 |
182 | }
183 |
--------------------------------------------------------------------------------
/src/main/java/com/password4j/MessageDigestFunction.java:
--------------------------------------------------------------------------------
1 | /*
2 | * (C) Copyright 2020 Password4j (http://password4j.com/).
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 | package com.password4j;
18 |
19 | import java.security.MessageDigest;
20 | import java.security.NoSuchAlgorithmException;
21 | import java.util.Map;
22 | import java.util.Objects;
23 | import java.util.concurrent.ConcurrentHashMap;
24 |
25 |
26 | /**
27 | * Class containing the implementation of Messaged digest functions provided by {@link MessageDigest}.
28 | *
29 | * @author David Bertoldi
30 | * @see Message digests
31 | * @since 1.4.0
32 | */
33 | public class MessageDigestFunction extends AbstractHashingFunction
34 | {
35 | protected static final SaltOption DEFAULT_SALT_OPTION = SaltOption.APPEND;
36 | private static final Map INSTANCES = new ConcurrentHashMap<>();
37 | private final String algorithm;
38 |
39 | private final SaltOption saltOption;
40 |
41 |
42 | MessageDigestFunction(String algorithm, SaltOption saltOption)
43 | {
44 | this.algorithm = algorithm;
45 | this.saltOption = saltOption;
46 | }
47 |
48 | /**
49 | * Creates a singleton instance, depending on the provided
50 | * algorithm, number of iterations and key length.
51 | *
52 | * @param algorithm message digest algorithm
53 | * @return a singleton instance
54 | * @since 1.4.0
55 | */
56 | public static MessageDigestFunction getInstance(String algorithm)
57 | {
58 | return getInstance(algorithm, DEFAULT_SALT_OPTION);
59 | }
60 |
61 | /**
62 | * Creates a singleton instance, depending on the provided
63 | * algorithm, number of iterations and key length.
64 | *
65 | * @param algorithm hmac algorithm
66 | * @param saltOption a configuration that specifies how the salt is concatenated to the plain text password
67 | * @return a singleton instance
68 | * @since 1.4.0
69 | */
70 | public static MessageDigestFunction getInstance(String algorithm, SaltOption saltOption)
71 | {
72 | String key = getUID(algorithm, saltOption);
73 | if (INSTANCES.containsKey(key))
74 | {
75 | return INSTANCES.get(key);
76 | }
77 | else
78 | {
79 | MessageDigestFunction function = new MessageDigestFunction(algorithm, saltOption);
80 | INSTANCES.put(key, function);
81 | return function;
82 | }
83 | }
84 |
85 | protected static String getUID(String algorithm, SaltOption saltOption)
86 | {
87 | return algorithm + "|" + saltOption.name();
88 | }
89 |
90 | protected static String toString(String algorithm, SaltOption saltOption)
91 | {
92 | return "a=" + algorithm + ", o=" + saltOption.name();
93 | }
94 |
95 | @Override
96 | public Hash hash(CharSequence plainTextPassword)
97 | {
98 | return hash(plainTextPassword, null);
99 | }
100 |
101 | @Override
102 | public Hash hash(byte[] plainTextPasswordAsBytes)
103 | {
104 | return hash(plainTextPasswordAsBytes, null);
105 | }
106 |
107 | @Override
108 | public Hash hash(CharSequence plainTextPassword, String salt)
109 | {
110 | return internalHash(Utils.fromCharSequenceToBytes(plainTextPassword), Utils.fromCharSequenceToBytes(salt));
111 | }
112 |
113 | @Override
114 | public Hash hash(byte[] plainTextPasswordAsBytes, byte[] saltAsBytes)
115 | {
116 | return internalHash(plainTextPasswordAsBytes, saltAsBytes);
117 | }
118 |
119 | protected Hash internalHash(byte[] plainTextPassword, byte[] salt)
120 | {
121 | byte[] finalCharSequence = concatenateSalt(plainTextPassword, salt);
122 |
123 | byte[] result = getMessageDigest().digest(finalCharSequence);
124 | return new Hash(this, Utils.toHex(result), result, salt);
125 | }
126 |
127 | protected MessageDigest getMessageDigest()
128 | {
129 | try
130 | {
131 | return MessageDigest.getInstance(algorithm);
132 | }
133 | catch (NoSuchAlgorithmException nsae)
134 | {
135 | throw new UnsupportedOperationException("`" + algorithm + "` is not supported by your system.", nsae);
136 | }
137 | }
138 |
139 | @Override
140 | public boolean check(CharSequence plainTextPassword, String hashed)
141 | {
142 | return check(plainTextPassword, hashed, null);
143 | }
144 |
145 | @Override
146 | public boolean check(byte[] plainTextPasswordAsBytes, byte[] hashed)
147 | {
148 | return check(plainTextPasswordAsBytes, hashed, null);
149 | }
150 |
151 | @Override
152 | public boolean check(CharSequence plainTextPassword, String hashed, String salt)
153 | {
154 | Hash hash = internalHash(Utils.fromCharSequenceToBytes(plainTextPassword), Utils.fromCharSequenceToBytes(salt));
155 | return slowEquals(hash.getResult(), hashed);
156 | }
157 |
158 | @Override
159 | public boolean check(byte[] plainTextPassword, byte[] hashed, byte[] salt)
160 | {
161 | Hash hash = internalHash(plainTextPassword, salt);
162 | return slowEquals(hash.getResultAsBytes(), hashed);
163 | }
164 |
165 | /**
166 | * The salt option describes if the Salt is appended or prepended to
167 | * the plain text password.
168 | *
169 | * @return how the salt is concatenated
170 | * @since 1.5.1
171 | */
172 | public SaltOption getSaltOption()
173 | {
174 | return saltOption;
175 | }
176 |
177 | /**
178 | * The algorithm in use by this instance.
179 | *
180 | * @return the algorithm in use
181 | * @since 1.5.1
182 | */
183 | public String getAlgorithm()
184 | {
185 | return algorithm;
186 | }
187 |
188 |
189 | private byte[] concatenateSalt(byte[] plainTextPassword, byte[] salt)
190 | {
191 | if (salt == null || salt.length == 0)
192 | {
193 | return plainTextPassword;
194 | }
195 |
196 | if (saltOption == SaltOption.PREPEND)
197 | {
198 | return Utils.append(salt, plainTextPassword);
199 | }
200 | return Utils.append(plainTextPassword, salt);
201 | }
202 |
203 | @Override
204 | public String toString()
205 | {
206 | return getClass().getSimpleName() + '(' + toString(this.algorithm, this.saltOption) + ')';
207 | }
208 |
209 | @Override
210 | public boolean equals(Object o)
211 | {
212 | if (this == o)
213 | return true;
214 | if (!(o instanceof MessageDigestFunction))
215 | return false;
216 | MessageDigestFunction other = (MessageDigestFunction) o;
217 | return algorithm.equals(other.algorithm) //
218 | && saltOption == other.saltOption;
219 | }
220 |
221 | @Override
222 | public int hashCode()
223 | {
224 | return Objects.hash(algorithm, saltOption);
225 | }
226 | }
227 |
--------------------------------------------------------------------------------
/src/main/java/com/password4j/CompressedPBKDF2Function.java:
--------------------------------------------------------------------------------
1 | /*
2 | * (C) Copyright 2020 Password4j (http://password4j.com/).
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package com.password4j;
19 |
20 | import com.password4j.types.Hmac;
21 |
22 | import java.util.List;
23 | import java.util.Map;
24 | import java.util.concurrent.ConcurrentHashMap;
25 |
26 |
27 | /**
28 | * Class containing the implementation of PBKDF2 function and its parameters.
29 | *
30 | * The main difference between {@link PBKDF2Function} is the hash produced: the configurations of the CHF,
31 | * the salt and the hash are encoded inside it.
32 | *
33 | * The produced hash is in the form
34 | *
35 | *
36 | * $algorithm$parameters$salt$hash
37 | *
38 | *
39 | * Assuming {@code $} as delimiter.
40 | *
41 | *
42 | *
43 | * The algorithm is encoded with its numeric uid {@link Hmac#code()}
44 | *
45 | *
46 | * Parameters are encoded in one integer where the length occupies the first 32bit and
47 | * the number of iterations the remaining 32 bits.
48 | *
49 | *
50 | * Salt is encoded in Base64
51 | *
52 | *
53 | * Hash is encoded in Base64 as in {@link PBKDF2Function}
54 | *
55 | *
56 | *
57 | * @author David Bertoldi
58 | * @see PBKDF2
59 | * @since 0.1.0
60 | */
61 | public class CompressedPBKDF2Function extends PBKDF2Function
62 | {
63 |
64 | private static final Map INSTANCES = new ConcurrentHashMap<>();
65 |
66 | private static final char DELIMITER = PropertyReader.readChar("hash.pbkdf2.delimiter", '$');
67 |
68 | protected CompressedPBKDF2Function()
69 | {
70 | super();
71 | }
72 |
73 | protected CompressedPBKDF2Function(Hmac fromCode, int iterations, int length)
74 | {
75 | super(fromCode, iterations, length);
76 | }
77 |
78 | /**
79 | * Creates a singleton instance, depending on the provided
80 | * algorithm, number of iterations and key length.
81 | *
82 | * @param algorithm hmac algorithm
83 | * @param iterations number of iterations
84 | * @param length length of the derived key
85 | * @return a singleton instance
86 | * @since 0.1.0
87 | */
88 | public static CompressedPBKDF2Function getInstance(Hmac algorithm, int iterations, int length)
89 | {
90 | String key = getUID(algorithm.name(), iterations, length);
91 | if (INSTANCES.containsKey(key))
92 | {
93 | return INSTANCES.get(key);
94 | }
95 | else
96 | {
97 | CompressedPBKDF2Function function = new CompressedPBKDF2Function(algorithm, iterations, length);
98 | INSTANCES.put(key, function);
99 | return function;
100 | }
101 | }
102 |
103 | /**
104 | * Creates a singleton instance, depending on the provided
105 | * algorithm, number of iterations and key length.
106 | *
107 | * @param algorithm string version of hmac algorithm. This must me mapped in {@link Hmac}.
108 | * @param iterations number of iterations
109 | * @param length length of the derived key
110 | * @return a singleton instance
111 | * @throws IllegalArgumentException if {@code algorithm} is not mapped in {@link Hmac}.
112 | * @since 0.1.0
113 | */
114 | public static CompressedPBKDF2Function getInstance(String algorithm, int iterations, int length)
115 | {
116 | try
117 | {
118 | return getInstance(Hmac.valueOf(algorithm), iterations, length);
119 | }
120 | catch (IllegalArgumentException iae)
121 | {
122 | throw new UnsupportedOperationException("Algorithm `" + algorithm + "` is not recognized.", iae);
123 | }
124 | }
125 |
126 | /**
127 | * Reads the configuration contained in the given hash and
128 | * builds a singleton instance based on these configurations.
129 | *
130 | * @param hashed an already hashed password
131 | * @return a singleton instance based on the given hash
132 | * @since 1.0.0
133 | */
134 | public static CompressedPBKDF2Function getInstanceFromHash(String hashed)
135 | {
136 | String[] parts = getParts(hashed);
137 | if (parts.length == 5)
138 | {
139 | int algorithm = Integer.parseInt(parts[1]);
140 | long configuration = Long.parseLong(parts[2]);
141 |
142 | int iterations = (int) (configuration >> 32);
143 | int length = (int) configuration;
144 |
145 | return CompressedPBKDF2Function.getInstance(Hmac.fromCode(algorithm), iterations, length);
146 | }
147 | throw new BadParametersException("`" + hashed + "` is not a valid hash");
148 | }
149 |
150 | protected static List getParts(byte[] hashed)
151 | {
152 | return Utils.split(hashed, (byte) DELIMITER);
153 | }
154 |
155 | protected static String[] getParts(String hashed)
156 | {
157 | String regex = "\\" + DELIMITER;
158 | return hashed.split(regex);
159 | }
160 |
161 | @Override
162 | protected String getHash(byte[] encodedKey, byte[] salt)
163 | {
164 | String params = Long.toString((((long) getIterations()) << 32) | (getLength() & 0xffffffffL));
165 | String salt64 = Utils.encodeBase64(salt);
166 | String hash64 = super.getHash(encodedKey, salt);
167 | return "$" + algorithm.code() + "$" + params + "$" + salt64 + "$" + hash64;
168 | }
169 |
170 | @Override
171 | public boolean check(CharSequence plainTextPassword, String hashed)
172 | {
173 | return check(Utils.fromCharSequenceToBytes(plainTextPassword), Utils.fromCharSequenceToBytes(hashed));
174 | }
175 |
176 | @Override
177 | public boolean check(byte[] plainTextPassword, byte[] hashed)
178 | {
179 | byte[] salt = getSaltFromHash(hashed);
180 | Hash internalHas = hash(plainTextPassword, salt);
181 |
182 | return slowEquals(internalHas.getResultAsBytes(), hashed);
183 | }
184 |
185 | @Override
186 | public boolean check(CharSequence plainTextPassword, String hashed, String salt)
187 | {
188 | byte[] hashAsBytes = Utils.fromCharSequenceToBytes(hashed);
189 | byte[] realSalt = getSaltFromHash(hashAsBytes);
190 | byte[] plainTextPasswordAsBytes = Utils.fromCharSequenceToBytes(plainTextPassword);
191 | Hash internalHash = hash(plainTextPasswordAsBytes, realSalt);
192 | return slowEquals(internalHash.getResult(), hashed);
193 | }
194 |
195 | @Override
196 | public boolean check(byte[] plainTextPassword, byte[] hashed, byte[] salt)
197 | {
198 | byte[] realSalt = getSaltFromHash(hashed);
199 | Hash internalHash = hash(plainTextPassword, realSalt);
200 | return slowEquals(internalHash.getResultAsBytes(), hashed);
201 | }
202 |
203 | private byte[] getSaltFromHash(byte[] hashed)
204 | {
205 | List parts = getParts(hashed);
206 | if (parts.size() == 5)
207 | {
208 | return Utils.decodeBase64(parts.get(3));
209 | }
210 | throw new BadParametersException("`" + Utils.fromBytesToString(hashed) + "` is not a valid hash");
211 | }
212 | }
213 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 |
5 | com.password4j
6 | password4j
7 | 1.8.4
8 | jar
9 |
10 |
11 | Password4j
12 |
13 | Java cryptographic library that supports Argon2, bcrypt, scrypt and PBKDF2 aimed to protect passwords in databases.
14 | Easy to use by design, highly customizable, secure and portable.
15 | All the implementations follow the standards and have been reviewed to perform better in the JVM.
16 |
17 | https://password4j.com/
18 |
19 |
20 |
21 | scm:git:git@github.com/Password4j/${project.artifactId}.git
22 | scm:git:ssh://git@github.com/Password4j/${project.artifactId}.git
23 | https://github.com/Password4j/password4j
24 | 1.8.4
25 |
26 |
27 |
28 |
29 | Apache License, Version 2.0
30 | http://www.apache.org/licenses/LICENSE-2.0.txt
31 | repo
32 |
33 |
34 |
35 |
36 |
37 | David Bertoldi
38 | dav.bertoldi@gmail.com
39 |
40 | Java Developer
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | org.slf4j
49 | slf4j-api
50 | 2.0.12
51 |
52 |
53 |
54 | junit
55 | junit
56 | 4.13.2
57 | test
58 |
59 |
60 |
61 | org.slf4j
62 | slf4j-simple
63 | 2.0.12
64 | test
65 |
66 |
67 |
68 |
69 |
70 |
71 | ossrh
72 | https://oss.sonatype.org/content/repositories/snapshots
73 |
74 |
75 | ossrh
76 | https://oss.sonatype.org/service/local/staging/deploy/maven2/
77 |
78 |
79 |
80 |
81 | src/test
82 |
83 |
84 | org.codehaus.mojo
85 | animal-sniffer-maven-plugin
86 | 1.23
87 |
88 |
89 | net.sf.androidscents.signature
90 | android-api-level-21
91 | 5.0.1_r2
92 |
93 |
94 |
95 |
96 | org.apache.maven.plugins
97 | maven-compiler-plugin
98 | 3.12.1
99 |
100 | 8
101 | 8
102 |
103 |
104 |
105 |
106 | org.jacoco
107 | jacoco-maven-plugin
108 | 0.8.11
109 |
110 |
111 |
112 | prepare-agent
113 |
114 |
115 |
116 | report
117 | test
118 |
119 | report
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 | release
131 |
132 |
133 |
134 | org.apache.maven.plugins
135 | maven-gpg-plugin
136 | 3.0.1
137 |
138 |
139 | sign-artifacts
140 | verify
141 |
142 | sign
143 |
144 |
145 |
146 |
147 |
148 |
149 | org.sonatype.central
150 | central-publishing-maven-plugin
151 | 0.7.0
152 | true
153 |
154 | central
155 |
156 |
157 |
158 |
159 | org.apache.maven.plugins
160 | maven-source-plugin
161 | 3.2.1
162 |
163 |
164 | attach-sources
165 |
166 | jar-no-fork
167 |
168 |
169 |
170 |
171 |
172 |
173 | org.apache.maven.plugins
174 | maven-release-plugin
175 | 2.5.3
176 |
177 | true
178 | false
179 | release
180 | deploy
181 |
182 |
183 |
184 |
185 | org.apache.maven.plugins
186 | maven-javadoc-plugin
187 | 2.9.1
188 |
189 |
190 | attach-javadocs
191 |
192 | jar
193 |
194 |
195 | -Xdoclint:none
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 | UTF-8
207 | UTF-8
208 | password4j
209 | https://sonarcloud.io
210 |
211 |
212 |
213 |
214 |
--------------------------------------------------------------------------------
/src/test/com/password4j/ScryptFunctionTest.java:
--------------------------------------------------------------------------------
1 | package com.password4j;
2 |
3 | import com.password4j.types.Bcrypt;
4 | import org.junit.Assert;
5 | import org.junit.Test;
6 |
7 | import java.util.Base64;
8 | import java.util.Properties;
9 |
10 | import static org.junit.Assert.assertEquals;
11 |
12 |
13 | public class ScryptFunctionTest
14 | {
15 |
16 | @Test(expected = BadParametersException.class)
17 | public void testBadHash()
18 | {
19 | // GIVEN
20 | String badHash = "bad$hash&";
21 |
22 | // WHEN
23 | ScryptFunction.getInstanceFromHash(badHash);
24 |
25 | }
26 |
27 | @Test(expected = BadParametersException.class)
28 | public void testNullPassword()
29 | {
30 | // GIVEN
31 | ScryptFunction scrypt = ScryptFunction.getInstance(Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE);
32 |
33 | // WHEN
34 | scrypt.hash("password", "salt");
35 |
36 | }
37 |
38 | @Test
39 | public void testHash1()
40 | {
41 | // GIVEN
42 | String password = "password";
43 | String salt = "salt";
44 |
45 | // WHEN
46 | Hash hash = new ScryptFunction(16384, 8, 1).hash(password, salt);
47 | String result = hash.getResult();
48 | byte[] bytes = hash.getBytes();
49 |
50 | // THEN
51 | String expected = "$e0801$c2FsdA==$dFcxr0SE8yOWiWntoomu7gBbWQOsVh5kpayhIXl793NO+f1YQi4uIhg7ysup7Ie6DIO3oueI8Dzg2gZGNDPNpg==";
52 | byte[] expectedBytes = Base64.getDecoder().decode(expected.split("\\$")[3]);
53 | Assert.assertEquals(expected, result);
54 | Assert.assertArrayEquals(expectedBytes, bytes);
55 |
56 | }
57 |
58 | @Test
59 | public void testHash2()
60 | {
61 | // GIVEN
62 | String password = "password";
63 |
64 | // WHEN
65 | boolean result = new ScryptFunction(16384, 8, 1)
66 | .check(password, "$e0801$c2FsdA==$dFcxr0SE8yOWiWntoomu7gBbWQOsVh5kpayhIXl793NO+f1YQi4uIhg7ysup7Ie6DIO3oueI8Dzg2gZGNDPNpg==");
67 |
68 | // THEN
69 | Assert.assertTrue(result);
70 | }
71 |
72 | @Test
73 | public void testHash3()
74 | {
75 | // GIVEN
76 | String password = "password";
77 | String salt = "salt";
78 |
79 | // WHEN
80 | boolean result = new ScryptFunction(16384, 8, 1).check(password, "$e0801$c2FsdA==$c2FsdA==");
81 |
82 | // THEN
83 | Assert.assertFalse(result);
84 | }
85 |
86 | @Test
87 | public void testHashRandomSalt()
88 | {
89 | // GIVEN
90 | String password = "password";
91 | ScryptFunction sCryptFunction = new ScryptFunction(16384, 8, 1);
92 |
93 | // WHEN
94 | Hash hash = sCryptFunction.hash(password);
95 |
96 | // THEN
97 | Assert.assertTrue(hash.getSalt() != null && hash.getSalt().length() > 0);
98 | Assert.assertEquals(sCryptFunction, ScryptFunction.getInstanceFromHash(hash.getResult()));
99 | }
100 |
101 | @Test
102 | public void testWrongCheck()
103 | {
104 | // GIVEN
105 | String password = "password";
106 | String salt = "salt";
107 |
108 | // WHEN
109 | Hash hash = new ScryptFunction(16384, 8, 1).hash(password, salt);
110 |
111 | // THEN
112 | Assert.assertFalse(hash.getHashingFunction().check(password, "$e0801$c2FsdA==$YXNkYXNkYXNkYXNk"));
113 | }
114 |
115 | @Test
116 | public void testEquality()
117 | {
118 | // GIVEN
119 | int r = 1;
120 | int N = 2;
121 | int p = 3;
122 | ScryptFunction scrypt = ScryptFunction.getInstance(N, r, p);
123 |
124 | // THEN
125 | boolean eqNull = scrypt.equals(null);
126 | boolean eqClass = scrypt.equals(new BcryptFunction(Bcrypt.A, 10));
127 | boolean difInst = scrypt.equals(ScryptFunction.getInstance(5, 4, 6));
128 | boolean sameInst = scrypt.equals(ScryptFunction.getInstance(N, r, p));
129 | boolean sameInst2 = scrypt.equals(new ScryptFunction(N, r, p));
130 | String toString = scrypt.toString();
131 | int hashCode = scrypt.hashCode();
132 | boolean notSameInst1 = scrypt.equals(new ScryptFunction(N + 1, r, p));
133 | boolean notSameInst2 = scrypt.equals(new ScryptFunction(N, r + 1 + 1, p));
134 | boolean notSameInst3 = scrypt.equals(new ScryptFunction(N, r, p + 1));
135 |
136 | // END
137 | Assert.assertFalse(eqNull);
138 | Assert.assertFalse(eqClass);
139 | Assert.assertFalse(difInst);
140 | Assert.assertTrue(sameInst);
141 | Assert.assertTrue(sameInst2);
142 | Assert.assertNotEquals(toString, new ScryptFunction(5, 4, 6).toString());
143 | Assert.assertNotEquals(hashCode, new ScryptFunction(5, 4, 6).hashCode());
144 | Assert.assertFalse(notSameInst1);
145 | Assert.assertFalse(notSameInst2);
146 | Assert.assertFalse(notSameInst3);
147 | }
148 |
149 | @Test
150 | public void testResources()
151 | {
152 | // GIVEN
153 |
154 | // WHEN
155 | ScryptFunction scrypt1 = ScryptFunction.getInstance(5, 3, 7);
156 | ScryptFunction scrypt2 = ScryptFunction.getInstance(25, 30, 14);
157 | ScryptFunction scrypt3 = ScryptFunction.getInstance(1, 1, 1);
158 |
159 | // THEN
160 | Assert.assertEquals(13_440, scrypt1.getRequiredBytes());
161 | Assert.assertTrue(scrypt1.getRequiredMemory().indexOf("KB") > 0);
162 | Assert.assertEquals(1_344_000, scrypt2.getRequiredBytes());
163 | Assert.assertTrue(scrypt2.getRequiredMemory().indexOf("MB") > 0);
164 | Assert.assertEquals(128, scrypt3.getRequiredBytes());
165 | Assert.assertEquals("128B", scrypt3.getRequiredMemory());
166 | }
167 |
168 | @Test(expected = BadParametersException.class)
169 | public void testBadParameters1()
170 | {
171 | // GIVEN
172 | int r = 5;
173 |
174 | // WHEN
175 | ScryptFunction.getInstance((16777215 / r) + 1, r, 1).hash("password");
176 | }
177 |
178 | @Test(expected = BadParametersException.class)
179 | public void testBadParameters2()
180 | {
181 | // GIVEN
182 | int p = 5;
183 |
184 | // WHEN
185 | ScryptFunction.getInstance(16, (16777215 / p) + 1, p).hash("password");
186 | }
187 |
188 | @Test(expected = BadParametersException.class)
189 | public void testBadParameters3()
190 | {
191 | // GIVEN
192 | int k = 16777215;
193 |
194 | // WHEN
195 | ScryptFunction.getInstance(2, 2 << 20, 16777215).hash("password");
196 | }
197 |
198 | @Test(expected = BadParametersException.class)
199 | public void testBadParameters4()
200 | {
201 | // GIVEN
202 |
203 | // WHEN
204 | ScryptFunction.getInstance(1, 4, 3).hash("password");
205 | }
206 |
207 | @Test(expected = BadParametersException.class)
208 | public void testBadParameters5()
209 | {
210 | // GIVEN
211 |
212 | // WHEN
213 | ScryptFunction.getInstance(50, 4, 3).hash("password");
214 | }
215 |
216 | @Test(expected = BadParametersException.class)
217 | public void testBadParameters6()
218 | {
219 | // GIVEN
220 | int p = 5;
221 |
222 | // WHEN
223 | new ScryptFunction(16384, 8, 1).check("password", "$s1$e0801$c2FsdA==$dFcxr0SE8yOWiWntoomu7gBbWQOsVh5kpayhIXl793NO+f1YQi4uIhg7ysup7Ie6DIO3oueI8Dzg2gZGNDPNpg==");
224 | }
225 |
226 | @Test(expected = BadParametersException.class)
227 | public void testBadParameters7()
228 | {
229 | // GIVEN
230 |
231 | // WHEN
232 | new ScryptFunction(16384, 8, 1).check("password", "$e0801c2FsdA==$dFcxr0SE8yOWiWntoomu7gBbWQOsVh5kpayhIXl793NO+f1YQi4uIhg7ysup7Ie6DIO3oueI8Dzg2gZGNDPNpg==");
233 | }
234 |
235 | @Test
236 | public void testAccessors()
237 | {
238 | // GIVEN
239 | int workFactor = 3;
240 | int resources = 5;
241 | int parallelization = 7;
242 | int derivedKeyLength = 32;
243 |
244 | // WHEN
245 | ScryptFunction scrypt = ScryptFunction.getInstance(workFactor, resources, parallelization, derivedKeyLength);
246 |
247 | // THEN
248 | Assert.assertEquals(workFactor, scrypt.getWorkFactor());
249 | Assert.assertEquals(resources, scrypt.getResources());
250 | Assert.assertEquals(parallelization, scrypt.getParallelization());
251 | Assert.assertEquals(derivedKeyLength, scrypt.getDerivedKeyLength());
252 | Assert.assertEquals("ScryptFunction(N=3, r=5, p=7, l=32)", scrypt.toString());
253 | }
254 |
255 | @Test
256 | public void testOWASP()
257 | {
258 | // GIVEN
259 | Properties oldProps = PropertyReader.properties;
260 | PropertyReader.properties = null;
261 |
262 | // WHEN
263 | ScryptFunction scrypt = AlgorithmFinder.getScryptInstance();
264 |
265 | // THEN
266 | assertEquals(1 << 16, scrypt.getWorkFactor());
267 | assertEquals(8, scrypt.getResources());
268 | assertEquals(1, scrypt.getParallelization());
269 | assertEquals(ScryptFunction.DERIVED_KEY_LENGTH, scrypt.getDerivedKeyLength());
270 |
271 | PropertyReader.properties = oldProps;
272 | }
273 |
274 |
275 |
276 | }
277 |
--------------------------------------------------------------------------------
/src/test/com/password4j/IssuesTest.java:
--------------------------------------------------------------------------------
1 | package com.password4j;
2 |
3 | import com.password4j.types.Argon2;
4 | import org.junit.Assert;
5 | import org.junit.Assume;
6 | import org.junit.Test;
7 |
8 | import java.lang.reflect.Field;
9 | import java.security.Provider;
10 | import java.security.Security;
11 | import java.util.List;
12 | import java.util.Map;
13 | import java.util.Set;
14 | import java.util.concurrent.ConcurrentMap;
15 | import java.util.stream.Collectors;
16 |
17 | import static org.junit.Assert.*;
18 |
19 | public class IssuesTest
20 | {
21 |
22 | /**
23 | * @see issue #92
24 | */
25 | @Test
26 | public void issue92()
27 | {
28 | String hash = "$argon2id$v=19$m=16384,t=2,p=1$nlm7oNI5zquzSYkyby6oVw$JOkJAYrDB0i2gmiJrXC6o2r+u1rszCm/RO9gIQtnxlY";
29 | String plain = "Test123!";
30 | Argon2Function function = Argon2Function.getInstanceFromHash(hash);
31 |
32 | boolean verified = Password.check(plain, hash).with(function);
33 | Hash newHash = Password.hash(plain).addSalt("Y9ΫI2o.W").with(function);
34 | boolean verified2 = Password.check(plain, newHash);
35 |
36 | assertTrue(verified);
37 | assertTrue(verified2);
38 | assertEquals("$argon2id$v=19$m=16384,t=2,p=1$WTnOq0kyby5X$SewIdM+Ywctw0lfNQ0xKYoUIlyRs3qF+gVmEVtpdmyg", newHash.getResult());
39 | }
40 |
41 |
42 | /**
43 | * @see issue #99
44 | */
45 | @Test
46 | public void issue99()
47 | {
48 | int memory = 65536;
49 | int iterations = 2;
50 | int parallelism = 3;
51 | int outputLength = 32;
52 | int version = 0x13;
53 | byte[] salt =
54 | {
55 | (byte) 0x6b, (byte) 0x25, (byte) 0xc9, (byte) 0xd7, (byte) 0x0e, (byte) 0x5c, (byte) 0x19, (byte) 0xac,
56 | (byte) 0x51, (byte) 0x74, (byte) 0xd7, (byte) 0x74, (byte) 0x53, (byte) 0xad, (byte) 0x23, (byte) 0x70,
57 | (byte) 0x15, (byte) 0x27, (byte) 0x56, (byte) 0x2e, (byte) 0x02, (byte) 0xb8, (byte) 0xec, (byte) 0x5c,
58 | (byte) 0xac, (byte) 0x89, (byte) 0x2d, (byte) 0xc3, (byte) 0xe4, (byte) 0xb5, (byte) 0x1c, (byte) 0x12
59 | };
60 | byte[] password="Test".getBytes();
61 | Argon2 type = Argon2.ID;
62 | Argon2Function instance=Argon2Function.getInstance(memory, iterations, parallelism, outputLength, type, version);
63 |
64 | Hash hash = instance.hash(password, salt);
65 |
66 |
67 | String expResult = "cbcfdee482c233e525ca405c7014e89cd33142758a2f1d23c420690f950c988c";
68 | assertEquals(expResult, printBytesToString(hash.getBytes()));
69 | }
70 |
71 | /**
72 | * @see issue #93
73 | */
74 | @Test
75 | public void issue93()
76 | {
77 | String hash = "$argon2id$v=19$m=16384,t=2,p=1$nlm7oNI5zquzSYkyby6oVw$JOkJAYrDB0i2gmiJrXC6o2r+u1rszCm/RO9gIQtnxlY";
78 | Argon2Function function = Argon2Function.getInstanceFromHash(hash);
79 |
80 | boolean test1 = Password.check("Test123!", hash).with(function);
81 | assertTrue(test1);
82 |
83 | boolean test2 = function.check("Test123!", hash);
84 | assertTrue(test2);
85 | }
86 |
87 |
88 | /**
89 | * @see issue #120
90 | */
91 | @Test(expected = Test.None.class)
92 | public void issue120()
93 | {
94 | // GIVEN
95 | String name = "issue120FakeProvider";
96 | Provider emptyProvider = new Provider(name, 1, "info")
97 | {
98 | @Override
99 | public synchronized Set getServices()
100 | {
101 | return null;
102 | }
103 | };
104 | Security.addProvider(emptyProvider);
105 |
106 | // WHEN
107 | Password.hash("hash");
108 |
109 | // THEN
110 | Security.removeProvider(name);
111 | }
112 |
113 |
114 | /**
115 | * @see issue #126
116 | */
117 | @Test
118 | public void issue126()
119 | {
120 | byte[] hashBytes = Password.hash("’(っ^▿^)۶\uD83C\uDF78\uD83C\uDF1F\uD83C\uDF7A٩(˘◡˘ ) ❌❌ ❌❌❌")
121 | .addSalt("\uD83E\uDDC2")
122 | .withScrypt()
123 | .getBytes();
124 |
125 | Assert.assertEquals("827b022b411e712e5ae4855d8c71cb047d882b2457120d1019974d17dcf6f1bf59644d9a93e470ab14ee5f7a88ae9b0140d2db121de58f6d830fc9c16c82f212", printBytesToString(hashBytes));
126 |
127 |
128 | hashBytes = Password.hash("ŸŁĀPRČ")
129 | .addSalt("ŸŁĀPRČAA")
130 | .withArgon2()
131 | .getBytes();
132 |
133 | Assert.assertEquals("59dedcf45d7a8604926ca66f6abe3990ce8b6ba108f535836fa18e95b7d94e9f56301e422c1d487dd06dc26061261402a5f7fe912bd545b6aeec866fec74df81", printBytesToString(hashBytes));
134 |
135 | }
136 |
137 | @Test
138 | public void issue162() throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException
139 | {
140 | // GIVEN
141 | String versionProperty = System.getProperty("java.specification.version");
142 | if(!versionProperty.contains("."))
143 | {
144 | int major = Integer.parseInt(versionProperty);
145 | Assume.assumeTrue(major < 17 );
146 | }
147 |
148 | Argon2Function argon2Function = Argon2Function.getInstance(1, 1, 2, 32, Argon2.I);
149 |
150 | // WHEN
151 | Password.hash("password").with(argon2Function);
152 |
153 | // THEN
154 | Class> clazz = Class.forName("java.lang.ApplicationShutdownHooks");
155 | Field field = clazz.getDeclaredField("hooks");
156 | field.setAccessible(true);
157 | List hooks = ((Map) field.get(null)).values().stream().filter(thread -> "password4j-shutdownhook".equals(thread.getName())).collect(Collectors.toList());
158 |
159 | Assert.assertFalse(hooks.isEmpty());
160 | }
161 |
162 |
163 | private static String printBytesToString(byte[] bytes)
164 | {
165 | StringBuilder byteString= new StringBuilder();
166 | if (bytes!=null)
167 | {
168 | for (byte aByte : bytes)
169 | {
170 | byteString.append(String.format("%02x", aByte));
171 | }
172 | }
173 | else
174 | {
175 | byteString = new StringBuilder("-");
176 | }
177 | return byteString.toString();
178 | }
179 |
180 | /**
181 | * Issue #163: Tomcat Thread Leak Prevention
182 | *
183 | * Problem: Tomcat was reporting thread leak warnings:
184 | *
185 | * "The web application appears to have started a thread named [password4j-worker-X]
186 | * but has failed to stop it. This is very likely to create a memory leak."
187 | *
188 | *
189 | * Root Cause: Non-daemon threads prevent JVM shutdown and cause
190 | * application server warnings when web applications are undeployed.
191 | *
192 | * Solution: Changed thread.setDaemon(false) to
193 | * thread.setDaemon(true) in Utils.createExecutorService()
194 | *
195 | * Impact: Daemon threads automatically terminate when the main
196 | * application shuts down, preventing memory leaks in containerized environments.
197 | *
198 | * @see GitHub Issue #163
199 | * @see Utils#createExecutorService()
200 | * @see Oracle: Daemon Threads
201 | * @since 1.8.1
202 | * @category Threading
203 | * @category MemoryLeak
204 | */
205 |
206 | @Test
207 | public void issue163() {
208 | final String plainText = "The quick brown fox jumps over the lazy dog";
209 | final String hash = Password.hash(plainText)
210 | .withArgon2()
211 | .getResult();
212 | assertNotNull("Password hashing should work", hash);
213 | try {
214 | Thread.sleep(200);
215 | } catch (InterruptedException e) {
216 | Thread.currentThread().interrupt();
217 | }
218 |
219 | boolean foundWorkerThread = false;
220 | for (Thread thread : Thread.getAllStackTraces().keySet()) {
221 | if (thread.getName().startsWith("password4j-worker-")) {
222 | foundWorkerThread = true;
223 |
224 | // THE FIX: This thread MUST be daemon to prevent Tomcat memory leak warning
225 | final String assertionMessage = "Thread " + thread.getName() + " must be daemon thread to prevent Tomcat leak warning";
226 | assertTrue(assertionMessage, thread.isDaemon());
227 | }
228 | }
229 | assertTrue("Should have found at least one password4j-worker thread", foundWorkerThread);
230 | }
231 |
232 | }
233 |
--------------------------------------------------------------------------------
/src/main/java/com/password4j/PBKDF2Function.java:
--------------------------------------------------------------------------------
1 | /*
2 | * (C) Copyright 2020 Password4j (http://password4j.com/).
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 | package com.password4j;
18 |
19 | import com.password4j.types.Hmac;
20 |
21 | import javax.crypto.SecretKey;
22 | import javax.crypto.SecretKeyFactory;
23 | import javax.crypto.spec.PBEKeySpec;
24 | import java.security.NoSuchAlgorithmException;
25 | import java.security.spec.InvalidKeySpecException;
26 | import java.util.Arrays;
27 | import java.util.Map;
28 | import java.util.Objects;
29 | import java.util.concurrent.ConcurrentHashMap;
30 |
31 |
32 | /**
33 | * Class containing the implementation of PBKDF2 function and its parameters.
34 | *
35 | * @author David Bertoldi
36 | * @see PBKDF2
37 | * @since 0.1.0
38 | */
39 | public class PBKDF2Function extends AbstractHashingFunction
40 | {
41 | private static final Map INSTANCES = new ConcurrentHashMap<>();
42 |
43 | private static final String ALGORITHM_PREFIX = "PBKDF2WithHmac";
44 |
45 | protected Hmac algorithm;
46 |
47 | protected String algorithmAsString;
48 |
49 | protected int iterations;
50 |
51 | protected int length;
52 |
53 | protected PBKDF2Function()
54 | {
55 | //
56 | }
57 |
58 | protected PBKDF2Function(int iterations, int length)
59 | {
60 | this.iterations = iterations;
61 | this.length = length;
62 | }
63 |
64 | protected PBKDF2Function(Hmac algorithm, int iterations, int length)
65 | {
66 | this(iterations, length);
67 | this.algorithm = algorithm;
68 | this.algorithmAsString = algorithm.name();
69 | }
70 |
71 | protected PBKDF2Function(String algorithm, int iterations, int length)
72 | {
73 | this(iterations, length);
74 | this.algorithmAsString = algorithm;
75 | }
76 |
77 | /**
78 | * Creates a singleton instance, depending on the provided
79 | * algorithm, number of iterations and key length.
80 | *
81 | * @param algorithm hmac algorithm
82 | * @param iterations number of iterations
83 | * @param length length of the derived key
84 | * @return a singleton instance
85 | * @since 0.1.0
86 | */
87 | public static PBKDF2Function getInstance(Hmac algorithm, int iterations, int length)
88 | {
89 | return getInstance(algorithm.name(), iterations, length);
90 | }
91 |
92 | /**
93 | * Creates a singleton instance, depending on the provided
94 | * algorithm, number of iterations and key length.
95 | *
96 | * @param algorithm string version of hmac algorithm
97 | * @param iterations number of iterations
98 | * @param length length of the derived key
99 | * @return a singleton instance
100 | * @since 0.1.0
101 | */
102 | public static PBKDF2Function getInstance(String algorithm, int iterations, int length)
103 | {
104 | String key = getUID(algorithm, iterations, length);
105 | if (INSTANCES.containsKey(key))
106 | {
107 | return INSTANCES.get(key);
108 | }
109 | else
110 | {
111 | PBKDF2Function function = new PBKDF2Function(algorithm, iterations, length);
112 | INSTANCES.put(key, function);
113 | return function;
114 | }
115 | }
116 |
117 | protected static SecretKey internalHash(byte[] plainTextPassword, byte[] salt, String algorithm, int iterations, int length) throws NoSuchAlgorithmException, InvalidKeySpecException
118 | {
119 | if (salt == null)
120 | {
121 | throw new IllegalArgumentException("Salt cannot be null");
122 | }
123 | return internalHash(Utils.fromBytesToChars(plainTextPassword), salt, algorithm, iterations, length);
124 | }
125 |
126 | protected static SecretKey internalHash(char[] plain, byte[] salt, String algorithm, int iterations, int length)
127 | throws NoSuchAlgorithmException, InvalidKeySpecException
128 | {
129 | SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(ALGORITHM_PREFIX + algorithm);
130 | PBEKeySpec spec = new PBEKeySpec(plain, salt, iterations, length);
131 | try
132 | {
133 | return secretKeyFactory.generateSecret(spec);
134 | }
135 | finally
136 | {
137 | spec.clearPassword();
138 | }
139 | }
140 |
141 | protected static String getUID(String algorithm, int iterations, int length)
142 | {
143 | return algorithm + "|" + iterations + "|" + length;
144 | }
145 |
146 | protected static String toString(String algorithm, int iterations, int length)
147 | {
148 | return "a=" + algorithm + ", i=" + iterations + ", l=" + length;
149 | }
150 |
151 | @Override
152 | public Hash hash(CharSequence plainTextPassword)
153 | {
154 | byte[] salt = SaltGenerator.generate();
155 | return hash(Utils.fromCharSequenceToBytes(plainTextPassword), salt);
156 | }
157 |
158 | @Override
159 | public Hash hash(byte[] plainTextPasswordAsBytes)
160 | {
161 | byte[] salt = SaltGenerator.generate();
162 | return hash(plainTextPasswordAsBytes, salt);
163 | }
164 |
165 | @Override
166 | public Hash hash(CharSequence plainTextPassword, String salt)
167 | {
168 | return hash(Utils.fromCharSequenceToBytes(plainTextPassword), Utils.fromCharSequenceToBytes(salt));
169 | }
170 |
171 | @Override
172 | public Hash hash(byte[] plainTextPassword, byte[] salt)
173 | {
174 | try
175 | {
176 | SecretKey key = internalHash(plainTextPassword, salt, this.algorithmAsString, this.iterations, this.length);
177 | byte[] encodedKey = key.getEncoded();
178 | return new Hash(this, getHash(encodedKey, salt), encodedKey, salt);
179 | }
180 | catch (NoSuchAlgorithmException nsae)
181 | {
182 | String message = "`" + algorithm + "` is not a valid algorithm";
183 | throw new UnsupportedOperationException(message, nsae);
184 | }
185 | catch (IllegalArgumentException | InvalidKeySpecException e)
186 | {
187 | String message = "Invalid specification with salt=" + Arrays.toString(salt) + ", iterations=" + iterations + " and length=" + length;
188 | throw new BadParametersException(message, e);
189 | }
190 | }
191 |
192 | /**
193 | * Overridable PBKDF2 generator
194 | *
195 | * @param encodedKey secret encodedKey
196 | * @param salt cryptographic salt
197 | * @return the PBKDF2 hash string
198 | */
199 | protected String getHash(byte[] encodedKey, byte[] salt)
200 | {
201 | return Utils.encodeBase64(encodedKey);
202 | }
203 |
204 | @Override
205 | public boolean check(CharSequence plainTextPassword, String hashed)
206 | {
207 | return check((byte[]) null, null);
208 | }
209 |
210 | @Override
211 | public boolean check(byte[] plainTextPasswordAsBytes, byte[] hashed)
212 | {
213 | throw new UnsupportedOperationException("This implementation requires an explicit salt.");
214 |
215 | }
216 |
217 | @Override
218 | public boolean check(CharSequence plainTextPassword, String hashed, String salt)
219 | {
220 | Hash internalHash = hash(plainTextPassword, salt);
221 | return slowEquals(internalHash.getResult(), hashed);
222 | }
223 |
224 | @Override
225 | public boolean check(byte[] plainTextPasswordAsBytes, byte[] hashed, byte[] salt)
226 | {
227 | Hash internalHash = hash(plainTextPasswordAsBytes, salt);
228 | return slowEquals(internalHash.getResultAsBytes(), hashed);
229 | }
230 |
231 |
232 | public String getAlgorithm()
233 | {
234 | return algorithmAsString;
235 | }
236 |
237 | public int getIterations()
238 | {
239 | return iterations;
240 | }
241 |
242 | public int getLength()
243 | {
244 | return length;
245 | }
246 |
247 | @Override
248 | public boolean equals(Object obj)
249 | {
250 | if (obj == null || !getClass().equals(obj.getClass()))
251 | {
252 | return false;
253 | }
254 |
255 | PBKDF2Function otherStrategy = (PBKDF2Function) obj;
256 | return this.algorithmAsString.equals(otherStrategy.algorithmAsString) //
257 | && this.iterations == otherStrategy.iterations //
258 | && this.length == otherStrategy.length;
259 | }
260 |
261 | @Override
262 | public String toString()
263 | {
264 | return getClass().getSimpleName() + '(' + toString(this.algorithmAsString, this.iterations, this.length) + ')';
265 | }
266 |
267 | @Override
268 | public int hashCode()
269 | {
270 | return Objects.hash(algorithmAsString, iterations, length);
271 | }
272 |
273 | }
274 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | # [1.8.4](https://github.com/Password4j/password4j/releases/tag/1.8.4) - (2025-06-30)
4 | ### Fixed
5 | * Daemon Threads belonging to the parallelization pool are now shutdown during JVM shutdown in order to prevent memory leaks ([#163](../../issues/163))
6 |
7 | # [1.8.3](https://github.com/Password4j/password4j/releases/tag/1.8.3) - (2025-05-08)
8 | ### Fixed
9 | * Threads belonging to the parallelization pool are now shutdown during JVM shutdown in order to prevent memory leaks ([#162](../../issues/162))
10 |
11 |
12 | # [1.8.2](https://github.com/Password4j/password4j/releases/tag/1.8.2) - (2024-05-01)
13 | ### Fixed
14 | * Multi thread algorithms use daemon threads in order to not block the application shutdown if there is no explicit `System.exit()` ([#151](../../issues/151))
15 | * Password4j works even when there is no access to `psw4j.properties` file due to restrictive security policies ([#153](../../issues/153))
16 |
17 |
18 | ## [1.8.1](https://github.com/Password4j/password4j/releases/tag/1.8.1) - (2024-03-08)
19 | ### Fixed
20 | * `Argon2Function#internalHash(...)` used a double conversion from `byte[]` to `String` and back to `byte[]` that created inconsistencies in `Hash#salt ([#143](../../issues/143))
21 |
22 | ### Changed
23 | * `Hash#Hash(HashingFunction, String, byte[], String)` marked deprecated
24 |
25 | ## [1.8.0](https://github.com/Password4j/password4j/releases/tag/1.8.0) - (2024-03-03)
26 | ### Added
27 | * Balloon Hashing implementation ([#131](../../issues/131))
28 |
29 | ### Fixed
30 | * Parallelism is achieved by an `ExecutorService` instantiated during object creation instead of during the hashing process
31 |
32 | ### Changed
33 | * Banner is disabled by default
34 |
35 | ## [1.7.3](https://github.com/Password4j/password4j/releases/tag/1.7.3) - (2023-09-14)
36 | ### Fixed
37 | * Wrong hashes when the password contains non ISO 8859-1 characters ([#126](../../issues/126))
38 |
39 |
40 | ## [1.7.2](https://github.com/Password4j/password4j/releases/tag/1.7.2) - (2023-08-20)
41 | ### Fixed
42 | * Suppressed warning for usage of `java.security.AccessController`. This is how the java development team fixed the problem for the moment ([#119](../../issues/119))
43 | * In some custom JDK implementations `java.security.Provider#getServices()` can return `null` instead of empty `java.security.Provider.Service[]` ([#120](../../issues/120))
44 |
45 |
46 | ## [1.7.1](https://github.com/Password4j/password4j/releases/tag/1.7.1) - (2023-06-02)
47 | ### Fixed
48 | * Bcrypt used negative rounds when cost factor = 31 ([#114](../../issues/114))
49 |
50 | ## [1.7.0](https://github.com/Password4j/password4j/releases/tag/1.7.0) - (2023-02-18)
51 | ### Added
52 | * APIs now accepts `byte[]` arguments ([#99](../../issues/99))
53 | * `.forceUpdate()` forces Password4j to recalculate a new hash even if the parameters didn't change ([#102](../../issues/102))
54 | * configurable salt length with property `global.salt.length` when using `#addRandomSalt()` ([#97](../../issues/97))
55 | ### Changed
56 | * `.andUpdate()` no more recalculations of the hash if the algorithm, salt or pepper changed from the hash found in `Password.check()` ([#102](../../issues/102))
57 | ### Fixed
58 | * Inconsistency between public and internal APIs for Argon2 ([#93](../../issues/93))
59 |
60 | ## [1.6.3](https://github.com/Password4j/password4j/releases/tag/1.6.3) - (2022-12-08)
61 | ### Fixed
62 | * Inconsistency of Argon2 with some kind of salts generated from external libraries [#92](../../issues/92)
63 |
64 | ## [1.6.2](https://github.com/Password4j/password4j/releases/tag/1.6.2) - (2022-10-20)
65 | ### Added
66 | * Application banner ([#83](../../issues/83)).
67 | ### Fixed
68 | * Typo for issue [#80](../../issues/80)
69 | ### Removed
70 | * Dependencies to Apache Commons Text, which had been vulnerable to arbitrary code execution in the past ([#84](../../issues/84)).
71 |
72 | ## [1.6.1](https://github.com/Password4j/password4j/releases/tag/1.6.1) - (2022-10-07)
73 | ### Changed
74 | * Algorithms' default values are aligned to [OWASP recommendation](https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html) ([#80](../../issues/80))
75 |
76 | ## [1.6.0](https://github.com/Password4j/password4j/releases/tag/1.6.0) - (2022-06-17)
77 | ### Changed
78 | * `BCryptFunction`, `SCryptFunction`, `#withBCrypt()`, `#withSCrypt()`, `getBCryptInstance()`, `getSCryptInstance()` to `BcryptFunction`, `ScryptFunction`, `#withBcrypt()`, `#withScrypt()`, `getBcryptInstance()`, `getScryptInstance()` ([#36](../../issues/36)).
79 | ### Fixed
80 | * Scrypt never prepends `$s0` to the result ([#64](../../issues/64)).
81 |
82 | ## [1.5.4](https://github.com/Password4j/password4j/releases/tag/1.5.4) - (2021-11-19)
83 | ### Fixed
84 | * Removed `slf4j-nop` which can cause issues if not excluded from the dependency tree ([#46](../../issues/46))
85 |
86 | ## [1.5.3](https://github.com/Password4j/password4j/releases/tag/1.5.3) - (2021-04-14)
87 | ### Fixed
88 | * `byte[]` are converted to `String` with environment-based encoding instead of UTF-8 ([#35](../../issues/35) and [#16](../../issues/16)).
89 |
90 | ## [1.5.2](https://github.com/Password4j/password4j/releases/tag/1.5.2) - (2021-02-21)
91 | ### Changed
92 | * Raised the compatibility with Android API level from 26+ (Android 8.0) to 21+ (Android 5.0).
93 | * `SystemChecker`'s benchmark tools returns a prototype of the function and the real elapsed time ([#23](../../issues/23)).
94 | ### Fixed
95 | * Argon2 was not using the given pepper with `Password.check(String, Hash)`.
96 | * Salt was converted from `String` to `byte[]` too many times. ([#31](../../issues/31)).
97 | ### Removed
98 | * Dependency with Apache Commons Text.
99 |
100 | ## [1.5.1](https://github.com/Password4j/password4j/releases/tag/1.5.1) - (2021-02-05)
101 | ### Added
102 | * `Hash` stores the byte array containing the calculated hash without algorithm's parameters and salt ([#26](../../issues/26)).
103 | ### Changed
104 | * Scrypt accepts dynamic key length ([#24](../../issues/24)).
105 | ### Fixed
106 | * Improved `toString()` methods' readability.
107 |
108 | ## [1.5.0](https://github.com/Password4j/password4j/releases/tag/1.5.0) - (2021-02-02)
109 | ### Added
110 | * Argon2 support.
111 | ### Changed
112 | * Enums `BCrypt` and `Hmac` are moved from `com.password4j` to `com.password4j.types`.
113 | ### Fixed
114 | * Some typos along the code.
115 |
116 | ## [1.4.0](https://github.com/Password4j/password4j/releases/tag/1.4.0) - (2020-11-15)
117 | ### Added
118 | * CHFs like MD5, SHA-1, SHA-2 and SHA-3 in order to increase compatibility with legacy systems.
119 | ### Changed
120 | * `PBKDF2Function.getAlgorithm()` returns a `String` instead of an `Hmac` enum. This make `PBKDF2Function.toString()` and `CompressedPBKDF2Function.toString()` more readable.
121 | * `SystemChecker.isPBKDF2Supported()` accepts a `String` instead of an `Hmac` enum.
122 | ### Fixed
123 | * Some typos along the code.
124 |
125 | ## [1.3.2](https://github.com/Password4j/password4j/releases/tag/1.3.2) - (2020-09-09)
126 | ### Fixed
127 | * The location of the configuration file is now customizable ([#5](../../issues/5)).
128 | ### Security
129 | * `SecureString.toString()` now hides the length of the string ([#6](../../issues/6)).
130 |
131 | ## [1.3.1](https://github.com/Password4j/password4j/releases/tag/1.3.1) - (2020-03-25)
132 | ### Fixed
133 | * `toString()` of some `HashingFunction` produced non-unique output ([#3](../../issues/3)).
134 | * added missing getters for some `HashingFunction`s ([#4](../../issues/4)).
135 |
136 | ## [1.3.0](https://github.com/Password4j/password4j/releases/tag/1.3.0) - (2020-03-19)
137 | ### Added
138 | * Capability of updating the hash (re-hash) with a new configuration just after the verification process.
139 | ### Changed
140 | * `HashBuilder` and `HashChecker` are less extendable because there are more maintainability issues than effective advantages
141 | * Pepper can be provided either with `SecureString` or `String`.
142 | ### Removed
143 | * `Password.hash()` and `Password.check()` methods that accepts a custom `HashBuilder` or a custom `HashChecker`.
144 |
145 | ## [1.2.1](https://github.com/Password4j/password4j/releases/tag/1.2.1) - (2020-03-17)
146 | ### Added
147 | * Constant time equality in `SecureString`.
148 | ### Changed
149 | * Enum `WithHmac` renamed to `Hmac`.
150 |
151 | ## [1.2.0](https://github.com/Password4j/password4j/releases/tag/1.2.0) - (2020-03-15)
152 | ### Added
153 | * This CHANGELOG.md file.
154 | ### Security
155 | * Plain text passwords can be provided either with `SecureString` or `String`.
156 |
157 | ## [1.1.0](https://github.com/Password4j/password4j/releases/tag/1.1.0) - (2020-03-14)
158 | ### Added
159 | * Configurable delimiter for `CompressedPBKDF2Function` (before was `$`).
160 | ### Removed
161 | * `Hash.check()` method because `Password.check()` should be the only way to verify passwords.
162 | ### Fixed
163 | * Values from `psw4j.properties` are not properly cached.
164 | * Typos in README.md
165 | * Typos in `SystemChecker`'s methods' signature.
166 |
167 | ## [1.0.2](https://github.com/Password4j/password4j/releases/tag/1.0.2) - (2020-03-12)
168 | ### Change
169 | * `SystemChecker.java` has no more a `main` method but must be called from end user's code.
170 | Removed UI and execution from Maven profile.
171 |
172 | ## [1.0.1](https://github.com/Password4j/password4j/releases/tag/password4j-1.0.1) - (2020-03-11)
173 | ### Change
174 | * POM structure and dependencies.
175 |
176 | ## [1.0.0](https://github.com/Password4j/password4j/releases/tag/1.0.0) - (2020-03-11)
177 | ### Change
178 | * API are more readable for end users.
179 |
--------------------------------------------------------------------------------
/src/main/java/com/password4j/Blake2b.java:
--------------------------------------------------------------------------------
1 | /*
2 | * (C) Copyright 2020 Password4j (http://password4j.com/).
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 | package com.password4j;
18 |
19 | import java.util.Arrays;
20 |
21 |
22 | class Blake2b
23 | {
24 | private static final long[] IV = {0x6a09e667f3bcc908L, 0xbb67ae8584caa73bL, 0x3c6ef372fe94f82bL, 0xa54ff53a5f1d36f1L,
25 | 0x510e527fade682d1L, 0x9b05688c2b3e6c1fL, 0x1f83d9abfb41bd6bL, 0x5be0cd19137e2179L};
26 |
27 | private static final byte[][] SIGMA = {{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},
28 | {14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3}, {11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4},
29 | {7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8}, {9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13},
30 | {2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9}, {12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11},
31 | {13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10}, {6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5},
32 | {10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0}, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},
33 | {14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3}};
34 |
35 | private static final int ROUNDS = 12;
36 |
37 | private static final int BLOCK_LENGTH_BYTES = 128;
38 |
39 | private final int digestLength;
40 |
41 | private final int keyLength;
42 |
43 | private final byte[] buffer;
44 |
45 | private final long[] internalState = new long[16];
46 |
47 | private int bufferPos = 0;
48 |
49 | private long[] chainValue = null;
50 |
51 | private long t0 = 0L;
52 |
53 | private long t1 = 0L;
54 |
55 | private long f0 = 0L;
56 |
57 | /**
58 | * Basic sized constructor - size in bytes.
59 | *
60 | * @param digestSize size of the digest in bytes
61 | */
62 | Blake2b(int digestSize)
63 | {
64 | if (digestSize < 1 || digestSize > 64)
65 | {
66 | throw new BadParametersException("BLAKE2b digest bytes length must be not greater than 64");
67 | }
68 |
69 | buffer = new byte[BLOCK_LENGTH_BYTES];
70 | keyLength = 0;
71 | this.digestLength = digestSize;
72 | init();
73 | }
74 |
75 | // initialize chainValue
76 | private void init()
77 | {
78 | chainValue = new long[8];
79 | chainValue[0] = IV[0] ^ (digestLength | ((long) keyLength << 8) | 0x1010000);
80 | chainValue[1] = IV[1];
81 | chainValue[2] = IV[2];
82 | chainValue[3] = IV[3];
83 | chainValue[4] = IV[4];
84 | chainValue[5] = IV[5];
85 | chainValue[6] = IV[6];
86 | chainValue[7] = IV[7];
87 | }
88 |
89 | private void initializeInternalState()
90 | {
91 | System.arraycopy(chainValue, 0, internalState, 0, chainValue.length);
92 | System.arraycopy(IV, 0, internalState, chainValue.length, 4);
93 | internalState[12] = t0 ^ IV[4];
94 | internalState[13] = t1 ^ IV[5];
95 | internalState[14] = f0 ^ IV[6];
96 | internalState[15] = IV[7];// ^ f1 with f1 = 0
97 | }
98 |
99 | void update(byte[] message)
100 | {
101 | if (message == null)
102 | {
103 | return;
104 | }
105 | update(message, 0, message.length);
106 | }
107 |
108 | /**
109 | * update the message digest with a block of bytes.
110 | *
111 | * @param message the byte array containing the data.
112 | * @param offset the offset into the byte array where the data starts.
113 | * @param len the length of the data.
114 | */
115 | void update(byte[] message, int offset, int len)
116 | {
117 | int remainingLength = 0;
118 |
119 | if (bufferPos != 0)
120 | {
121 | remainingLength = BLOCK_LENGTH_BYTES - bufferPos;
122 | if (remainingLength < len)
123 | {
124 | System.arraycopy(message, offset, buffer, bufferPos, remainingLength);
125 | t0 += BLOCK_LENGTH_BYTES;
126 | if (t0 == 0)
127 | {
128 | t1++;
129 | }
130 | compress(buffer, 0);
131 | bufferPos = 0;
132 | Arrays.fill(buffer, (byte) 0);// clear buffer
133 | }
134 | else
135 | {
136 | System.arraycopy(message, offset, buffer, bufferPos, len);
137 | bufferPos += len;
138 | return;
139 | }
140 | }
141 |
142 | int messagePos;
143 | int blockWiseLastPos = offset + len - BLOCK_LENGTH_BYTES;
144 | for (messagePos = offset + remainingLength; messagePos < blockWiseLastPos; messagePos += BLOCK_LENGTH_BYTES)
145 | {
146 | t0 += BLOCK_LENGTH_BYTES;
147 | if (t0 == 0)
148 | {
149 | t1++;
150 | }
151 | compress(message, messagePos);
152 | }
153 |
154 | // fill the buffer with left bytes, this might be a full block
155 | System.arraycopy(message, messagePos, buffer, 0, offset + len - messagePos);
156 | bufferPos += offset + len - messagePos;
157 | }
158 |
159 | /**
160 | * close the digest, producing the final digest value. The doFinal
161 | * call leaves the digest reset.
162 | * Key, salt and personal string remain.
163 | *
164 | * @param out the array the digest is to be copied into.
165 | * @param outOffset the offset into the out array the digest is to start at.
166 | */
167 | void doFinal(byte[] out, int outOffset)
168 | {
169 |
170 | f0 = 0xFFFFFFFFFFFFFFFFL;
171 | t0 += bufferPos;
172 | if (bufferPos > 0 && t0 == 0)
173 | {
174 | t1++;
175 | }
176 | compress(buffer, 0);
177 | Arrays.fill(buffer, (byte) 0);// Holds eventually the key if input is null
178 | Arrays.fill(internalState, 0L);
179 |
180 | for (int i = 0; i < chainValue.length && (i * 8 < digestLength); i++)
181 | {
182 | byte[] bytes = Utils.longToLittleEndian(chainValue[i]);
183 |
184 | if (i * 8 < digestLength - 8)
185 | {
186 | System.arraycopy(bytes, 0, out, outOffset + i * 8, 8);
187 | }
188 | else
189 | {
190 | System.arraycopy(bytes, 0, out, outOffset + i * 8, digestLength - (i * 8));
191 | }
192 | }
193 |
194 | Arrays.fill(chainValue, 0L);
195 |
196 | reset();
197 | }
198 |
199 | /**
200 | * Reset the digest back to it's initial state.
201 | * The key, the salt and the personal string will
202 | * remain for further computations.
203 | */
204 | void reset()
205 | {
206 | bufferPos = 0;
207 | f0 = 0L;
208 | t0 = 0L;
209 | t1 = 0L;
210 | chainValue = null;
211 | Arrays.fill(buffer, (byte) 0);
212 | init();
213 | }
214 |
215 | private void compress(byte[] message, int messagePos)
216 | {
217 |
218 | initializeInternalState();
219 |
220 | long[] m = new long[16];
221 | for (int j = 0; j < 16; j++)
222 | {
223 | m[j] = Utils.littleEndianToLong(message, messagePos + j * 8);
224 | }
225 |
226 | for (int round = 0; round < ROUNDS; round++)
227 | {
228 |
229 | // G apply to columns of internalState:m[blake2b_sigma[round][2 *
230 | // blockPos]] /+1
231 | functionG(m[SIGMA[round][0]], m[SIGMA[round][1]], 0, 4, 8, 12);
232 | functionG(m[SIGMA[round][2]], m[SIGMA[round][3]], 1, 5, 9, 13);
233 | functionG(m[SIGMA[round][4]], m[SIGMA[round][5]], 2, 6, 10, 14);
234 | functionG(m[SIGMA[round][6]], m[SIGMA[round][7]], 3, 7, 11, 15);
235 | // G apply to diagonals of internalState:
236 | functionG(m[SIGMA[round][8]], m[SIGMA[round][9]], 0, 5, 10, 15);
237 | functionG(m[SIGMA[round][10]], m[SIGMA[round][11]], 1, 6, 11, 12);
238 | functionG(m[SIGMA[round][12]], m[SIGMA[round][13]], 2, 7, 8, 13);
239 | functionG(m[SIGMA[round][14]], m[SIGMA[round][15]], 3, 4, 9, 14);
240 | }
241 |
242 | // update chain values:
243 | for (int offset = 0; offset < chainValue.length; offset++)
244 | {
245 | chainValue[offset] = chainValue[offset] ^ internalState[offset] ^ internalState[offset + 8];
246 | }
247 | }
248 |
249 | private void functionG(long m1, long m2, int posA, int posB, int posC, int posD)
250 | {
251 |
252 | internalState[posA] = internalState[posA] + internalState[posB] + m1;
253 | internalState[posD] = Long.rotateRight(internalState[posD] ^ internalState[posA], 32);
254 | internalState[posC] = internalState[posC] + internalState[posD];
255 | internalState[posB] = Long.rotateRight(internalState[posB] ^ internalState[posC], 24); // replaces 25 of BLAKE
256 | internalState[posA] = internalState[posA] + internalState[posB] + m2;
257 | internalState[posD] = Long.rotateRight(internalState[posD] ^ internalState[posA], 16);
258 | internalState[posC] = internalState[posC] + internalState[posD];
259 | internalState[posB] = Long.rotateRight(internalState[posB] ^ internalState[posC], 63); // replaces 11 of BLAKE
260 | }
261 | }
262 |
--------------------------------------------------------------------------------
/src/main/java/com/password4j/HashBuilder.java:
--------------------------------------------------------------------------------
1 | /*
2 | * (C) Copyright 2020 Password4j (http://password4j.com/).
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 | package com.password4j;
18 |
19 | /**
20 | * Builder class that helps to create a chain of parameters to be used
21 | * in the hashing process.
22 | *
23 | * @author David Bertoldi
24 | * @since 1.0.0
25 | */
26 | public class HashBuilder
27 | {
28 | protected byte[] salt;
29 | protected CharSequence pepper;
30 | private byte[] plainTextPassword;
31 |
32 | @SuppressWarnings("unused")
33 | private HashBuilder()
34 | {
35 | //
36 | }
37 |
38 | /**
39 | * @param plainTextPassword the plain text password
40 | * @since 1.0.0
41 | */
42 | protected HashBuilder(CharSequence plainTextPassword)
43 | {
44 | this.plainTextPassword = Utils.fromCharSequenceToBytes(plainTextPassword);
45 | }
46 |
47 | /**
48 | * @param plainTextPasswordAsBytes the plain text password as bytes array
49 | * @since 1.7.0
50 | */
51 | protected HashBuilder(byte[] plainTextPasswordAsBytes)
52 | {
53 | this.plainTextPassword = plainTextPasswordAsBytes;
54 | }
55 |
56 | /**
57 | * Add a cryptographic salt in the hashing process.
58 | * The salt is applied differently depending on the chosen algorithm.
59 | *
60 | * @param salt cryptographic salt
61 | * @return this builder
62 | * @since 1.0.0
63 | */
64 | public HashBuilder addSalt(String salt)
65 | {
66 | this.salt = Utils.fromCharSequenceToBytes(salt);
67 | return this;
68 | }
69 |
70 | /**
71 | * Add a cryptographic salt in the hashing process.
72 | * The salt is applied differently depending on the chosen algorithm.
73 | *
74 | * @param saltAsBytes cryptographic salt as bytes array
75 | * @return this builder
76 | * @since 1.7.0
77 | */
78 | public HashBuilder addSalt(byte[] saltAsBytes)
79 | {
80 | this.salt = saltAsBytes;
81 | return this;
82 | }
83 |
84 | /**
85 | * Add a random cryptographic salt in the hashing process.
86 | * The salt is applied differently depending on the chosen algorithm.
87 | *
88 | * Calling this method can be omitted for all the CHFs that require a salt.
89 | *
90 | * @return this builder
91 | * @see SaltGenerator#generate() for more information about the length of the product
92 | * @since 1.0.0
93 | */
94 | public HashBuilder addRandomSalt()
95 | {
96 | this.salt = SaltGenerator.generate();
97 | return this;
98 | }
99 |
100 | /**
101 | * Add a random cryptographic salt in the hashing process with a given length.
102 | * The salt is applied differently depending on the chosen algorithm.
103 | *
104 | * @param length the length of the salt produced
105 | * @return this builder
106 | * @throws BadParametersException if the length is non-positive
107 | * @see SaltGenerator#generate() for more information about the length of the product
108 | * @since 1.0.0
109 | */
110 | public HashBuilder addRandomSalt(int length)
111 | {
112 | if (length <= 0)
113 | {
114 | throw new BadParametersException("Salt cannot have a non-positive length");
115 | }
116 | else
117 | {
118 | this.salt = SaltGenerator.generate(length);
119 | }
120 | return this;
121 | }
122 |
123 | /**
124 | * Concatenates the pepper configured in your `psw4j.properties` file with the plain text password.
125 | * The produced sequence (in the form {@code pepper+password}) is processed by the algorithm.
126 | *
127 | * @return this builder
128 | * @see PepperGenerator#get()
129 | */
130 | public HashBuilder addPepper()
131 | {
132 | this.pepper = PepperGenerator.get();
133 | return this;
134 | }
135 |
136 | /**
137 | * Concatenates the provided string with the plain text password.
138 | * The produced sequence (in the form {@code pepper+password}) is processed by the algorithm.
139 | *
140 | * @param pepper cryptographic pepper
141 | * @return this builder
142 | * @since 1.0.0
143 | */
144 | public HashBuilder addPepper(CharSequence pepper)
145 | {
146 | this.pepper = pepper;
147 | return this;
148 | }
149 |
150 | /**
151 | * Hashes the previously given plain text password
152 | * with a specific implementation of {@link HashingFunction}.
153 | *
154 | * This method does not read the configurations in the `psw4j.properties` file.
155 | *
156 | * @param hashingFunction a CHF
157 | * @return a {@link Hash} object
158 | * @since 1.0.0
159 | */
160 | public Hash with(HashingFunction hashingFunction)
161 | {
162 | return hashingFunction.hash(plainTextPassword, salt, pepper);
163 | }
164 |
165 | /**
166 | * Hashes the previously given plain text password
167 | * with {@link PBKDF2Function}.
168 | *
169 | * This method reads the configurations in the `psw4j.properties` file. If no configuration is found,
170 | * then the default parameters are used.
171 | *
172 | * Finally calls {@link #with(HashingFunction)}
173 | *
174 | * @return a {@link Hash} object
175 | * @see AlgorithmFinder#getPBKDF2Instance()
176 | * @see #with(HashingFunction)
177 | * @since 1.0.0
178 | */
179 | public Hash withPBKDF2()
180 | {
181 | return with(AlgorithmFinder.getPBKDF2Instance());
182 | }
183 |
184 | /**
185 | * Hashes the previously given plain text password
186 | * with {@link CompressedPBKDF2Function}.
187 | *
188 | * This method reads the configurations in the `psw4j.properties` file. If no configuration is found,
189 | * then the default parameters are used.
190 | *
191 | * Finally calls {@link #with(HashingFunction)}
192 | *
193 | * @return an {@link Hash} object
194 | * @see AlgorithmFinder#getCompressedPBKDF2Instance()
195 | * @see #with(HashingFunction)
196 | * @since 1.0.0
197 | */
198 | public Hash withCompressedPBKDF2()
199 | {
200 | return with(AlgorithmFinder.getCompressedPBKDF2Instance());
201 | }
202 |
203 | /**
204 | * Hashes the previously given plain text password
205 | * with {@link BcryptFunction}.
206 | *
207 | * This method reads the configurations in the `psw4j.properties` file. If no configuration is found,
208 | * then the default parameters are used.
209 | *
210 | * Finally calls {@link #with(HashingFunction)}
211 | *
212 | * @return an {@link Hash} object
213 | * @see AlgorithmFinder#getBcryptInstance()
214 | * @see #with(HashingFunction)
215 | * @since 1.0.0
216 | */
217 | public Hash withBcrypt()
218 | {
219 | return with(AlgorithmFinder.getBcryptInstance());
220 | }
221 |
222 | /**
223 | * Hashes the previously given plain text password
224 | * with {@link ScryptFunction}.
225 | *
226 | * This method reads the configurations in the `psw4j.properties` file. If no configuration is found,
227 | * then the default parameters are used.
228 | *
229 | * Finally calls {@link #with(HashingFunction)}
230 | *
231 | * @return an {@link Hash} object
232 | * @see AlgorithmFinder#getScryptInstance()
233 | * @see #with(HashingFunction)
234 | * @since 1.0.0
235 | */
236 | public Hash withScrypt()
237 | {
238 | return with(AlgorithmFinder.getScryptInstance());
239 | }
240 |
241 | /**
242 | * Hashes the previously given plain text password
243 | * with {@link MessageDigestFunction}.
244 | *
245 | * This method reads the configurations in the `psw4j.properties` file. If no configuration is found,
246 | * then the default parameters are used.
247 | *
248 | * Finally calls {@link #with(HashingFunction)}
249 | *
250 | * @return a {@link Hash} object
251 | * @see AlgorithmFinder#getPBKDF2Instance()
252 | * @see #with(HashingFunction)
253 | * @since 1.4.0
254 | */
255 | public Hash withMessageDigest()
256 | {
257 | return with(AlgorithmFinder.getMessageDigestInstance());
258 | }
259 |
260 | /**
261 | * Hashes the previously given plain text password
262 | * with {@link Argon2Function}.
263 | *
264 | * This method reads the configurations in the `psw4j.properties` file. If no configuration is found,
265 | * then the default parameters are used.
266 | *
267 | * Finally calls {@link #with(HashingFunction)}
268 | *
269 | * @return a {@link Hash} object
270 | * @see AlgorithmFinder#getArgon2Instance()
271 | * @see #with(HashingFunction)
272 | * @since 1.5.0
273 | */
274 | public Hash withArgon2()
275 | {
276 | return with(AlgorithmFinder.getArgon2Instance());
277 | }
278 |
279 | /**
280 | * Hashes the previously given plain text password
281 | * with {@link BalloonHashingFunction}.
282 | *
283 | * This method reads the configurations in the `psw4j.properties` file. If no configuration is found,
284 | * then the default parameters are used.
285 | *
286 | * Finally calls {@link #with(HashingFunction)}
287 | *
288 | * @return a {@link Hash} object
289 | * @see AlgorithmFinder#getArgon2Instance()
290 | * @see #with(HashingFunction)
291 | * @since 1.8.0
292 | */
293 | public Hash withBalloonHashing()
294 | {
295 | return with(AlgorithmFinder.getBalloonHashingInstance());
296 | }
297 |
298 | }
299 |
--------------------------------------------------------------------------------
/src/main/java/com/password4j/HashChecker.java:
--------------------------------------------------------------------------------
1 | /*
2 | * (C) Copyright 2020 Password4j (http://password4j.com/).
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 | package com.password4j;
18 |
19 | /**
20 | * Builder class that helps to create a chain of parameters to be used
21 | * in the verification process.
22 | *
23 | * @author David Bertoldi
24 | * @since 1.0.0
25 | */
26 | public class HashChecker
27 | {
28 | protected byte[] hashed;
29 | protected byte[] salt;
30 | private byte[] plainTextPassword;
31 | private CharSequence pepper;
32 |
33 |
34 | @SuppressWarnings("unused")
35 | private HashChecker()
36 | {
37 | //
38 | }
39 |
40 | /**
41 | * @param plainTextPassword the plain text password
42 | * @param hashed the hash to verify
43 | * @since 1.0.0
44 | */
45 | HashChecker(CharSequence plainTextPassword, String hashed)
46 | {
47 | this.hashed = Utils.fromCharSequenceToBytes(hashed);
48 | this.plainTextPassword = Utils.fromCharSequenceToBytes(plainTextPassword);
49 | }
50 |
51 | /**
52 | * @param plainTextPassword the plain text password as bytes array
53 | * @param hashed the hash to verify as bytes array
54 | * @since 1.7.0
55 | */
56 | HashChecker(byte[] plainTextPassword, byte[] hashed)
57 | {
58 | this.hashed = hashed;
59 | this.plainTextPassword = plainTextPassword;
60 | }
61 |
62 | /**
63 | * Concatenates the provided string with the plain text password.
64 | * The produced sequence (in the form {@code pepper+password}) is processed by the algorithm.
65 | *
66 | * @param pepper cryptographic pepper
67 | * @return this builder
68 | * @since 1.0.0
69 | */
70 | public HashChecker addPepper(CharSequence pepper)
71 | {
72 | this.pepper = pepper;
73 | return this;
74 | }
75 |
76 | /**
77 | * Concatenates the pepper configured in your `psw4j.properties` file with the plain text password.
78 | * The produced sequence (in the form {@code pepper+password}) is processed by the algorithm.
79 | *
80 | * @return this builder
81 | * @see PepperGenerator#get()
82 | */
83 | public HashChecker addPepper()
84 | {
85 | this.pepper = PepperGenerator.get();
86 | return this;
87 | }
88 |
89 | /**
90 | * Add a cryptographic salt in the verifying process.
91 | * The salt is applied differently depending on the chosen algorithm.
92 | *
93 | * @param salt cryptographic salt
94 | * @return this builder
95 | * @since 1.0.0
96 | */
97 | public HashChecker addSalt(String salt)
98 | {
99 | this.salt = Utils.fromCharSequenceToBytes(salt);
100 | return this;
101 | }
102 |
103 | /**
104 | * Add a cryptographic salt in the verifying process.
105 | * The salt is applied differently depending on the chosen algorithm.
106 | *
107 | * @param salt cryptographic salt as bytes array
108 | * @return this builder
109 | * @since 1.7.0
110 | */
111 | public HashChecker addSalt(byte[] salt)
112 | {
113 | this.salt = salt;
114 | return this;
115 | }
116 |
117 | /**
118 | * Creates a builder to update the hash.
119 | * The actual salt and pepper are taken from the original check request.
120 | *
121 | * In order to declare a new salt or pepper use ,}
122 | *
123 | * @return the updater
124 | * @since 1.3.0
125 | */
126 | public HashUpdater andUpdate()
127 | {
128 | return new HashUpdater(this, new HashBuilder(plainTextPassword).addPepper(pepper).addSalt(salt));
129 | }
130 |
131 | /**
132 | * Check if the previously given hash was produced from the given plain text password
133 | * with a specific implementation of {@link HashingFunction}.
134 | *
135 | * This method does not read the configurations in the `psw4j.properties` file.
136 | *
137 | * @param hashingFunction a CHF
138 | * @return true if the hash was produced by the given plain text password; false otherwise.
139 | * @since 1.0.0
140 | */
141 | public boolean with(HashingFunction hashingFunction)
142 | {
143 | if (plainTextPassword == null || plainTextPassword.length == 0)
144 | {
145 | return false;
146 | }
147 |
148 | return hashingFunction.check(plainTextPassword, hashed, salt, pepper);
149 | }
150 |
151 | /**
152 | * Check if the previously given hash was produced from the given plain text password
153 | * with {@link PBKDF2Function}.
154 | *
155 | * This method reads the configurations in the `psw4j.properties` file. If no configuration is found,
156 | * then the default parameters are used.
157 | *
158 | * @return true if the hash was produced by the given plain text password; false otherwise.
159 | * @see AlgorithmFinder#getPBKDF2Instance()
160 | * @since 1.0.0
161 | */
162 | public boolean withPBKDF2()
163 | {
164 | PBKDF2Function pbkdf2 = AlgorithmFinder.getPBKDF2Instance();
165 | return with(pbkdf2);
166 | }
167 |
168 | /**
169 | * Check if the previously given hash was produced from the given plain text password
170 | * with {@link CompressedPBKDF2Function}.
171 | *
172 | * This method reads the configurations in the `psw4j.properties` file. If no configuration is found,
173 | * then the default parameters are used.
174 | *
175 | * @return true if the hash was produced by the given plain text password; false otherwise.
176 | * @see AlgorithmFinder#getCompressedPBKDF2Instance()
177 | * @since 1.0.0
178 | */
179 | public boolean withCompressedPBKDF2()
180 | {
181 | PBKDF2Function pbkdf2 = AlgorithmFinder.getCompressedPBKDF2Instance();
182 | return with(pbkdf2);
183 | }
184 |
185 | /**
186 | * Check if the previously given hash was produced from the given plain text password
187 | * with {@link ScryptFunction}.
188 | *
189 | * This method reads the configurations in the `psw4j.properties` file. If no configuration is found,
190 | * then the default parameters are used.
191 | *
192 | * @return true if the hash was produced by the given plain text password; false otherwise.
193 | * @see AlgorithmFinder#getScryptInstance()
194 | * @since 1.0.0
195 | */
196 | public boolean withScrypt()
197 | {
198 | ScryptFunction scrypt = AlgorithmFinder.getScryptInstance();
199 | return with(scrypt);
200 | }
201 |
202 | /**
203 | * Check if the previously given hash was produced from the given plain text password
204 | * with {@link BcryptFunction}.
205 | *
206 | * This method reads the configurations in the `psw4j.properties` file. If no configuration is found,
207 | * then the default parameters are used.
208 | *
209 | * @return true if the hash was produced by the given plain text password; false otherwise.
210 | * @see AlgorithmFinder#getBcryptInstance()
211 | * @since 1.0.0
212 | */
213 | public boolean withBcrypt()
214 | {
215 | return with(AlgorithmFinder.getBcryptInstance());
216 | }
217 |
218 | /**
219 | * Check if the previously given hash was produced from the given plain text password
220 | * with {@link MessageDigestFunction}.
221 | *
222 | * This method reads the configurations in the `psw4j.properties` file. If no configuration is found,
223 | * then the default parameters are used.
224 | *
225 | * @return true if the hash was produced by the given plain text password; false otherwise.
226 | * @see AlgorithmFinder#getMessageDigestInstance()
227 | * @since 1.4.0
228 | */
229 | public boolean withMessageDigest()
230 | {
231 | return with(AlgorithmFinder.getMessageDigestInstance());
232 | }
233 |
234 | /**
235 | * Check if the previously given hash was produced from the given plain text password
236 | * with {@link Argon2Function}.
237 | *
238 | * This method reads the configurations in the `psw4j.properties` file. If no configuration is found,
239 | * then the default parameters are used.
240 | *
241 | * @return true if the hash was produced by the given plain text password; false otherwise.
242 | * @see AlgorithmFinder#getArgon2Instance()
243 | * @since 1.5.0
244 | */
245 | public boolean withArgon2()
246 | {
247 | Argon2Function argon2 = AlgorithmFinder.getArgon2Instance();
248 | return with(argon2);
249 | }
250 |
251 | /**
252 | * Check if the previously given hash was produced from the given plain text password
253 | * with {@link BalloonHashingFunction}.
254 | *
255 | * This method reads the configurations in the `psw4j.properties` file. If no configuration is found,
256 | * then the default parameters are used.
257 | *
258 | * @return true if the hash was produced by the given plain text password; false otherwise.
259 | * @see AlgorithmFinder#getBalloonHashingInstance()
260 | * @since 1.5.0
261 | */
262 | public boolean withBalloonHashing()
263 | {
264 | BalloonHashingFunction balloon = AlgorithmFinder.getBalloonHashingInstance();
265 | return with(balloon);
266 | }
267 |
268 | /**
269 | * This method returns the String version of the hash bytes. This
270 | * should be always a safe operation when using ISO-8859-1 encoding.
271 | *
272 | * @return String version of the hash
273 | */
274 | protected String getHashed()
275 | {
276 | return Utils.fromBytesToString(hashed);
277 | }
278 |
279 | }
280 |
--------------------------------------------------------------------------------
/src/main/java/com/password4j/BalloonHashingFunction.java:
--------------------------------------------------------------------------------
1 | /*
2 | * (C) Copyright 2023 Password4j (http://password4j.com/).
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | package com.password4j;
19 |
20 | import java.math.BigInteger;
21 | import java.security.MessageDigest;
22 | import java.security.NoSuchAlgorithmException;
23 | import java.util.ArrayList;
24 | import java.util.List;
25 | import java.util.Map;
26 | import java.util.Objects;
27 | import java.util.concurrent.ConcurrentHashMap;
28 | import java.util.concurrent.ExecutionException;
29 | import java.util.concurrent.ExecutorService;
30 | import java.util.concurrent.Future;
31 |
32 | /**
33 | * Class containing the implementation of Balloon hashing function and its parameters.
34 | *
35 | * @author David Bertoldi
36 | * @since 1.8.0
37 | */
38 | public class BalloonHashingFunction extends AbstractHashingFunction
39 | {
40 |
41 | private static final Map INSTANCES = new ConcurrentHashMap<>();
42 | private static final int DEFAULT_DELTA = 3;
43 |
44 | private final String algorithm;
45 | private final int spaceCost;
46 | private final int timeCost;
47 | private final int parallelism;
48 | private final int delta;
49 | private ExecutorService service;
50 |
51 | BalloonHashingFunction(String algorithm, int spaceCost, int timeCost, int parallelism, int delta)
52 | {
53 | this.algorithm = algorithm;
54 | this.spaceCost = spaceCost;
55 | this.timeCost = timeCost;
56 | this.parallelism = parallelism;
57 | this.delta = delta;
58 | if (parallelism > 1)
59 | {
60 | this.service = Utils.createExecutorService();
61 | }
62 |
63 | }
64 |
65 |
66 | public static BalloonHashingFunction getInstance(String algorithm, int spaceCost, int timeCost, int parallelism, int delta)
67 | {
68 | String key = getUID(algorithm, spaceCost, timeCost, parallelism, delta);
69 | if (INSTANCES.containsKey(key))
70 | {
71 | return INSTANCES.get(key);
72 | }
73 | else
74 | {
75 | BalloonHashingFunction function = new BalloonHashingFunction(algorithm, spaceCost, timeCost, parallelism, delta);
76 | INSTANCES.put(key, function);
77 | return function;
78 | }
79 | }
80 |
81 | public static BalloonHashingFunction getInstance(String algorithm, int spaceCost, int timeCost, int parallelism)
82 | {
83 | return getInstance(algorithm, spaceCost, timeCost, parallelism, DEFAULT_DELTA);
84 | }
85 |
86 | private static String getUID(String algorithm, int spaceCost, int timeCost, int parallelism, int delta)
87 | {
88 | return algorithm + '|' + spaceCost + '|' + timeCost + '|' + parallelism + '|' + delta;
89 | }
90 |
91 | protected static String toString(String algorithm, int spaceCost, int timeCost, int parallelism, int delta)
92 | {
93 | return "a=" + algorithm + ", s=" + spaceCost + ", t=" + timeCost + ", p=" + parallelism + ", d=" + delta;
94 | }
95 |
96 | @Override
97 | public Hash hash(CharSequence plainTextPassword)
98 | {
99 | return null;
100 | }
101 |
102 | @Override
103 | public Hash hash(byte[] plainTextPassword)
104 | {
105 | return null;
106 | }
107 |
108 | @Override
109 | public Hash hash(CharSequence plainTextPassword, String salt)
110 | {
111 | return hash(Utils.fromCharSequenceToBytes(plainTextPassword), Utils.fromCharSequenceToBytes(salt));
112 | }
113 |
114 | @Override
115 | public Hash hash(byte[] plainTextPassword, byte[] salt)
116 | {
117 | return internalHash(plainTextPassword, salt);
118 | }
119 |
120 | protected Hash internalHash(byte[] plainTextPassword, byte[] salt)
121 | {
122 |
123 | byte[] output;
124 |
125 | if (parallelism == 1)
126 | {
127 | byte[] parallelSalt = Utils.append(salt, Utils.longToLittleEndian((1)));
128 | MessageDigest messageDigest = getMessageDigest();
129 | output = balloon(messageDigest, plainTextPassword, parallelSalt);
130 | output = hashFunc(messageDigest, plainTextPassword, salt, output);
131 | }
132 | else if (parallelism > 1)
133 | {
134 |
135 | List> futures = new ArrayList<>();
136 |
137 | for (int i = 0; i < parallelism; i++)
138 | {
139 | byte[] parallelSalt = Utils.append(salt, Utils.longToLittleEndian((i + 1)));
140 | Future future = service.submit(() -> balloon(getMessageDigest(), plainTextPassword, parallelSalt));
141 |
142 | futures.add(future);
143 | }
144 |
145 | MessageDigest messageDigest = getMessageDigest();
146 | output = new byte[messageDigest.getDigestLength()];
147 |
148 | try
149 | {
150 | byte[] tmp;
151 | output = (byte[]) futures.get(0).get();
152 | for (int f = 1; f < futures.size(); f++)
153 | {
154 | tmp = ((byte[]) futures.get(f).get());
155 |
156 | for (int i = 0; i < output.length; i++)
157 | {
158 | output[i] ^= tmp[i];
159 | }
160 |
161 | }
162 | }
163 | catch (InterruptedException | ExecutionException e)
164 | {
165 | Thread.currentThread().interrupt();
166 | }
167 |
168 |
169 | output = hashFunc(messageDigest, plainTextPassword, salt, output);
170 | }
171 | else
172 | {
173 | output = balloon(getMessageDigest(), plainTextPassword, salt);
174 | }
175 |
176 | return new Hash(this, Utils.toHex(output), output, salt);
177 | }
178 |
179 | protected MessageDigest getMessageDigest()
180 | {
181 | try
182 | {
183 | return MessageDigest.getInstance(algorithm);
184 | }
185 | catch (NoSuchAlgorithmException nsae)
186 | {
187 | throw new UnsupportedOperationException("`" + algorithm + "` is not supported by your system.", nsae);
188 | }
189 | }
190 |
191 | private byte[] balloon(MessageDigest messageDigest, byte[] plainTextPassword, byte[] salt)
192 | {
193 | List buffer = new ArrayList<>();
194 | buffer.add(hashFunc(messageDigest, 0, plainTextPassword, salt));
195 |
196 | int cnt = 1;
197 |
198 | cnt = expand(messageDigest, buffer, cnt);
199 | mix(messageDigest, buffer, cnt, salt);
200 | return extract(buffer);
201 | }
202 |
203 | private int expand(MessageDigest messageDigest, List buffer, int cnt)
204 | {
205 | int newCnt = cnt;
206 | for (int i = 1; i < spaceCost; i++)
207 | {
208 | buffer.add(hashFunc(messageDigest, newCnt, buffer.get(i - 1)));
209 | newCnt += 1;
210 | }
211 | return newCnt;
212 | }
213 |
214 | private void mix(MessageDigest messageDigest, List buffer, int cnt, byte[] salt)
215 | {
216 | int newCnt = cnt;
217 | for (int t = 0; t < timeCost; t++)
218 | {
219 | for (int s = 0; s < spaceCost; s++)
220 | {
221 | buffer.set(s, hashFunc(messageDigest, newCnt, get(buffer, s - 1), get(buffer, s)));
222 | newCnt += 1;
223 |
224 | for (int d = 0; d < delta; d++)
225 | {
226 | byte[] indexBlock = hashFunc(messageDigest, t, s, d);
227 | int other = Utils.bytesToInt(hashFunc(messageDigest, newCnt, salt, indexBlock)).mod(BigInteger.valueOf(spaceCost)).intValue();
228 | newCnt += 1;
229 | buffer.set(s, hashFunc(messageDigest, newCnt, buffer.get(s), get(buffer, other)));
230 | newCnt += 1;
231 | }
232 | }
233 | }
234 | }
235 |
236 | private byte[] extract(List buffer)
237 | {
238 | return buffer.get(buffer.size() - 1);
239 | }
240 |
241 | private byte[] get(List buffer, int position)
242 | {
243 | if (position < 0)
244 | {
245 | return buffer.get(buffer.size() + position);
246 | }
247 | return buffer.get(position);
248 | }
249 |
250 | private byte[] hashFunc(MessageDigest md, Object... args)
251 | {
252 | byte[] t = new byte[0];
253 |
254 | for (Object arg : args)
255 | {
256 | if (arg instanceof Integer)
257 | {
258 | t = Utils.append(t, Utils.intToLittleEndianBytes((Integer) arg, 8));
259 | }
260 | else if (arg instanceof CharSequence)
261 | {
262 | t = Utils.append(t, Utils.fromCharSequenceToBytes((CharSequence) arg));
263 | }
264 | else if (arg instanceof byte[])
265 | {
266 | t = Utils.append(t, (byte[]) arg);
267 | }
268 | }
269 |
270 | return md.digest(t);
271 | }
272 |
273 | @Override
274 | public boolean check(CharSequence plainTextPassword, String hashed)
275 | {
276 | return check(plainTextPassword, hashed, null);
277 | }
278 |
279 | @Override
280 | public boolean check(CharSequence plainTextPassword, String hashed, String salt)
281 | {
282 | Hash hash = internalHash(Utils.fromCharSequenceToBytes(plainTextPassword), Utils.fromCharSequenceToBytes(salt));
283 | return slowEquals(hash.getResult(), hashed);
284 | }
285 |
286 | @Override
287 | public boolean check(byte[] plainTextPassword, byte[] hashed)
288 | {
289 | return check(plainTextPassword, new byte[0], hashed, null);
290 | }
291 |
292 | @Override
293 | public boolean check(byte[] plainTextPassword, byte[] hashed, byte[] salt)
294 | {
295 | Hash hash = internalHash(plainTextPassword, salt);
296 | return slowEquals(hash.getResultAsBytes(), hashed);
297 | }
298 |
299 | @Override
300 | public boolean equals(Object o)
301 | {
302 | if (this == o)
303 | return true;
304 | if (!(o instanceof BalloonHashingFunction))
305 | return false;
306 | BalloonHashingFunction other = (BalloonHashingFunction) o;
307 | return algorithm.equals(other.algorithm) //
308 | && spaceCost == other.spaceCost //
309 | && timeCost == other.timeCost //
310 | && parallelism == other.parallelism //
311 | && delta == other.delta;
312 | }
313 |
314 | @Override
315 | public int hashCode()
316 | {
317 | return Objects.hash(algorithm, spaceCost, timeCost, parallelism, delta);
318 | }
319 |
320 | @Override
321 | public String toString()
322 | {
323 | return getClass().getSimpleName() + '[' + toString(algorithm, spaceCost, timeCost, parallelism, delta) + ']';
324 | }
325 | }
326 |
--------------------------------------------------------------------------------