From 1e15994326f6dc901c3b562966e84664fce668e0 Mon Sep 17 00:00:00 2001
From: Benjamin Marwell The default algorithm can change between minor versions and does not introduce
+ * API incompatibility by design.AuthenticationInfo
to add into this instance.
*/
+ @Override
@SuppressWarnings("unchecked")
public void merge(AuthenticationInfo info) {
if (info == null || info.getPrincipals() == null || info.getPrincipals().isEmpty()) {
@@ -249,14 +256,21 @@ public void merge(AuthenticationInfo info) {
* @return true
if the Object argument is an instanceof SimpleAuthenticationInfo
and
* its {@link #getPrincipals() principals} are equal to this instance's principals, false
otherwise.
*/
+ @Override
public boolean equals(Object o) {
- if (this == o) return true;
- if (!(o instanceof SimpleAuthenticationInfo)) return false;
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof SimpleAuthenticationInfo)) {
+ return false;
+ }
SimpleAuthenticationInfo that = (SimpleAuthenticationInfo) o;
//noinspection RedundantIfStatement
- if (principals != null ? !principals.equals(that.principals) : that.principals != null) return false;
+ if (!Objects.equals(principals, that.principals)) {
+ return false;
+ }
return true;
}
@@ -266,6 +280,7 @@ public boolean equals(Object o) {
*
* @return the hashcode of the internal {@link #getPrincipals() principals} instance.
*/
+ @Override
public int hashCode() {
return (principals != null ? principals.hashCode() : 0);
}
@@ -275,6 +290,7 @@ public int hashCode() {
*
* @return {@link #getPrincipals() principals}.toString()
*/
+ @Override
public String toString() {
return principals.toString();
}
diff --git a/core/src/main/java/org/apache/shiro/authc/credential/DefaultPasswordService.java b/core/src/main/java/org/apache/shiro/authc/credential/DefaultPasswordService.java
index ea12668e4f..6c0578f16e 100644
--- a/core/src/main/java/org/apache/shiro/authc/credential/DefaultPasswordService.java
+++ b/core/src/main/java/org/apache/shiro/authc/credential/DefaultPasswordService.java
@@ -18,32 +18,36 @@
*/
package org.apache.shiro.authc.credential;
-import java.security.MessageDigest;
-
import org.apache.shiro.crypto.hash.DefaultHashService;
import org.apache.shiro.crypto.hash.Hash;
import org.apache.shiro.crypto.hash.HashRequest;
import org.apache.shiro.crypto.hash.HashService;
-import org.apache.shiro.crypto.hash.format.*;
+import org.apache.shiro.crypto.hash.format.DefaultHashFormatFactory;
+import org.apache.shiro.crypto.hash.format.HashFormat;
+import org.apache.shiro.crypto.hash.format.HashFormatFactory;
+import org.apache.shiro.crypto.hash.format.ParsableHashFormat;
+import org.apache.shiro.crypto.hash.format.Shiro2CryptFormat;
import org.apache.shiro.lang.util.ByteSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.security.MessageDigest;
+
+import static java.util.Objects.requireNonNull;
+
/**
* Default implementation of the {@link PasswordService} interface that relies on an internal
* {@link HashService}, {@link HashFormat}, and {@link HashFormatFactory} to function:
* Hashing Passwords
*
* Comparing Passwords
- * All hashing operations are performed by the internal {@link #getHashService() hashService}. After the hash
- * is computed, it is formatted into a String value via the internal {@link #getHashFormat() hashFormat}.
+ * All hashing operations are performed by the internal {@link #getHashService() hashService}.
*
* @since 1.2
*/
public class DefaultPasswordService implements HashingPasswordService {
- public static final String DEFAULT_HASH_ALGORITHM = "SHA-256";
- public static final int DEFAULT_HASH_ITERATIONS = 500000; //500,000
+ public static final String DEFAULT_HASH_ALGORITHM = "argon2id";
private static final Logger log = LoggerFactory.getLogger(DefaultPasswordService.class);
@@ -53,25 +57,33 @@ public class DefaultPasswordService implements HashingPasswordService {
private volatile boolean hashFormatWarned; //used to avoid excessive log noise
+ /**
+ * Constructs a new PasswordService with a default hash service and the default
+ * algorithm name {@value #DEFAULT_HASH_ALGORITHM}, a default hash format (shiro2) and
+ * a default hashformat factory.
+ *
+ *
CredentialsMatcher
implementations.
- *
- * @since 0.9
- * @deprecated since 1.1 - use the HashedCredentialsMatcher directly and set its
- * {@link HashedCredentialsMatcher#setHashAlgorithmName(String) hashAlgorithmName} property.
- */
-@Deprecated
-public class Md2CredentialsMatcher extends HashedCredentialsMatcher {
-
- public Md2CredentialsMatcher() {
- super();
- setHashAlgorithmName(Md2Hash.ALGORITHM_NAME);
- }
-}
diff --git a/core/src/main/java/org/apache/shiro/authc/credential/Md5CredentialsMatcher.java b/core/src/main/java/org/apache/shiro/authc/credential/Md5CredentialsMatcher.java
deleted file mode 100644
index 81b8f13c97..0000000000
--- a/core/src/main/java/org/apache/shiro/authc/credential/Md5CredentialsMatcher.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.shiro.authc.credential;
-
-import org.apache.shiro.crypto.hash.AbstractHash;
-import org.apache.shiro.crypto.hash.Hash;
-import org.apache.shiro.crypto.hash.Md5Hash;
-
-
-/**
- * {@code HashedCredentialsMatcher} implementation that expects the stored {@code AuthenticationInfo} credentials to be
- * MD5 hashed.
- *
- * Note: MD5 and
- * SHA-1 algorithms are now known to be vulnerable to
- * compromise and/or collisions (read the linked pages for more). While most applications are ok with either of these
- * two, if your application mandates high security, use the SHA-256 (or higher) hashing algorithms and their
- * supporting CredentialsMatcher
implementations.
- *
- * @since 0.9
- * @deprecated since 1.1 - use the HashedCredentialsMatcher directly and set its
- * {@link HashedCredentialsMatcher#setHashAlgorithmName(String) hashAlgorithmName} property.
- */
-public class Md5CredentialsMatcher extends HashedCredentialsMatcher {
-
- public Md5CredentialsMatcher() {
- super();
- setHashAlgorithmName(Md5Hash.ALGORITHM_NAME);
- }
-}
diff --git a/core/src/main/java/org/apache/shiro/authc/credential/PasswordMatcher.java b/core/src/main/java/org/apache/shiro/authc/credential/PasswordMatcher.java
index e687dcc1a1..dd60a850b4 100644
--- a/core/src/main/java/org/apache/shiro/authc/credential/PasswordMatcher.java
+++ b/core/src/main/java/org/apache/shiro/authc/credential/PasswordMatcher.java
@@ -21,6 +21,7 @@
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.crypto.hash.Hash;
+import org.apache.shiro.lang.util.ByteSource;
/**
* A {@link CredentialsMatcher} that employs best-practices comparisons for hashed text passwords.
@@ -39,6 +40,7 @@ public PasswordMatcher() {
this.passwordService = new DefaultPasswordService();
}
+ @Override
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
PasswordService service = ensurePasswordService();
@@ -49,23 +51,11 @@ public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo
if (storedCredentials instanceof Hash) {
Hash hashedPassword = (Hash)storedCredentials;
- HashingPasswordService hashingService = assertHashingPasswordService(service);
- return hashingService.passwordsMatch(submittedPassword, hashedPassword);
+ return hashedPassword.matchesPassword(ByteSource.Util.bytes(submittedPassword));
}
//otherwise they are a String (asserted in the 'assertStoredCredentialsType' method call above):
String formatted = (String)storedCredentials;
- return passwordService.passwordsMatch(submittedPassword, formatted);
- }
-
- private HashingPasswordService assertHashingPasswordService(PasswordService service) {
- if (service instanceof HashingPasswordService) {
- return (HashingPasswordService) service;
- }
- String msg = "AuthenticationInfo's stored credentials are a Hash instance, but the " +
- "configured passwordService is not a " +
- HashingPasswordService.class.getName() + " instance. This is required to perform Hash " +
- "object password comparisons.";
- throw new IllegalStateException(msg);
+ return service.passwordsMatch(submittedPassword, formatted);
}
private PasswordService ensurePasswordService() {
diff --git a/core/src/main/java/org/apache/shiro/authc/credential/Sha1CredentialsMatcher.java b/core/src/main/java/org/apache/shiro/authc/credential/Sha1CredentialsMatcher.java
deleted file mode 100644
index 6cdd328923..0000000000
--- a/core/src/main/java/org/apache/shiro/authc/credential/Sha1CredentialsMatcher.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.shiro.authc.credential;
-
-import org.apache.shiro.crypto.hash.AbstractHash;
-import org.apache.shiro.crypto.hash.Hash;
-import org.apache.shiro.crypto.hash.Sha1Hash;
-
-
-/**
- * {@code HashedCredentialsMatcher} implementation that expects the stored {@code AuthenticationInfo} credentials to be
- * SHA hashed.
- *
- * Note: MD5 and
- * SHA-1 algorithms are now known to be vulnerable to
- * compromise and/or collisions (read the linked pages for more). While most applications are ok with either of these
- * two, if your application mandates high security, use the SHA-256 (or higher) hashing algorithms and their
- * supporting CredentialsMatcher
implementations.
- *
- * @since 0.9
- * @deprecated since 1.1 - use the HashedCredentialsMatcher directly and set its
- * {@link HashedCredentialsMatcher#setHashAlgorithmName(String) hashAlgorithmName} property.
- */
-public class Sha1CredentialsMatcher extends HashedCredentialsMatcher {
-
- public Sha1CredentialsMatcher() {
- super();
- setHashAlgorithmName(Sha1Hash.ALGORITHM_NAME);
- }
-}
diff --git a/core/src/main/java/org/apache/shiro/realm/text/TextConfigurationRealm.java b/core/src/main/java/org/apache/shiro/realm/text/TextConfigurationRealm.java
index 0439f93bb1..53d16ed756 100644
--- a/core/src/main/java/org/apache/shiro/realm/text/TextConfigurationRealm.java
+++ b/core/src/main/java/org/apache/shiro/realm/text/TextConfigurationRealm.java
@@ -184,6 +184,7 @@ protected void processUserDefinitions(MapThese implementations must contain a salt, a salt length, can format themselves to a valid String + * suitable for the {@code /etc/shadow} file.
+ * + *It also defines the hex and base64 output by wrapping the output of {@link #formatToCryptString()}.
+ * + *Implementation notice: Implementations should provide a static {@code fromString()} method.
+ * + * @since 2.0 + */ +public abstract class AbstractCryptHash implements Hash, Serializable { + + private static final long serialVersionUID = 2483214646921027859L; + + protected static final Pattern DELIMITER = Pattern.compile("\\$"); + + private final String algorithmName; + private final byte[] hashedData; + private final ByteSource salt; + + /** + * Cached value of the {@link #toHex() toHex()} call so multiple calls won't incur repeated overhead. + */ + private String hexEncoded; + /** + * Cached value of the {@link #toBase64() toBase64()} call so multiple calls won't incur repeated overhead. + */ + private String base64Encoded; + + /** + * Constructs an {@link AbstractCryptHash} using the algorithm name, hashed data and salt parameters. + * + *Other required parameters must be stored by the implementation.
+ * + * @param algorithmName internal algorithm name, e.g. {@code 2y} for bcrypt and {@code argon2id} for argon2. + * @param hashedData the hashed data as a byte array. Does not include the salt or other parameters. + * @param salt the salt which was used when generating the hash. + * @throws IllegalArgumentException if the salt is not the same size as {@link #getSaltLength()}. + */ + public AbstractCryptHash(final String algorithmName, final byte[] hashedData, final ByteSource salt) { + this.algorithmName = algorithmName; + this.hashedData = Arrays.copyOf(hashedData, hashedData.length); + this.salt = requireNonNull(salt); + checkValid(); + } + + protected final void checkValid() { + checkValidAlgorithm(); + + checkValidSalt(); + } + + /** + * Algorithm-specific checks of the algorithm’s parameters. + * + *While the salt length will be checked by default, other checks will be useful. + * Examples are: Argon2 checking for the memory and parallelism parameters, bcrypt checking + * for the cost parameters being in a valid range.
+ * + * @throws IllegalArgumentException if any of the parameters are invalid. + */ + protected abstract void checkValidAlgorithm(); + + /** + * Default check method for a valid salt. Can be overridden, because multiple salt lengths could be valid. + * + * By default, this method checks if the number of bytes in the salt + * are equal to the int returned by {@link #getSaltLength()}. + * + * @throws IllegalArgumentException if the salt length does not match the returned value of {@link #getSaltLength()}. + */ + protected void checkValidSalt() { + int length = salt.getBytes().length; + if (length != getSaltLength()) { + String message = String.format( + Locale.ENGLISH, + "Salt length is expected to be [%d] bytes, but was [%d] bytes.", + getSaltLength(), + length + ); + throw new IllegalArgumentException(message); + } + } + + /** + * Implemented by subclasses, this specifies the KDF algorithm name + * to use when performing the hash. + * + *When multiple algorithm names are acceptable, then this method should return the primary algorithm name.
+ * + *Example: Bcrypt hashed can be identified by {@code 2y} and {@code 2a}. The method will return {@code 2y} + * for newly generated hashes by default, unless otherwise overridden.
+ * + * @return the KDF algorithm name to use when performing the hash. + */ + @Override + public String getAlgorithmName() { + return this.algorithmName; + } + + /** + * The length in number of bytes of the salt which is needed for this algorithm. + * + * @return the expected length of the salt (in bytes). + */ + public abstract int getSaltLength(); + + @Override + public ByteSource getSalt() { + return this.salt; + } + + /** + * Returns only the hashed data. Those are of no value on their own. If you need to serialize + * the hash, please refer to {@link #formatToCryptString()}. + * + * @return A copy of the hashed data as bytes. + * @see #formatToCryptString() + */ + @Override + public byte[] getBytes() { + return Arrays.copyOf(this.hashedData, this.hashedData.length); + } + + @Override + public boolean isEmpty() { + return false; + } + + /** + * Returns a hex-encoded string of the underlying {@link #formatToCryptString()} formatted output}. + * + * This implementation caches the resulting hex string so multiple calls to this method remain efficient. + * + * @return a hex-encoded string of the underlying {@link #formatToCryptString()} formatted output}. + */ + @Override + public String toHex() { + if (this.hexEncoded == null) { + this.hexEncoded = Hex.encodeToString(this.formatToCryptString().getBytes(StandardCharsets.UTF_8)); + } + return this.hexEncoded; + } + + /** + * Returns a Base64-encoded string of the underlying {@link #formatToCryptString()} formatted output}. + * + * This implementation caches the resulting Base64 string so multiple calls to this method remain efficient. + * + * @return a Base64-encoded string of the underlying {@link #formatToCryptString()} formatted output}. + */ + @Override + public String toBase64() { + if (this.base64Encoded == null) { + //cache result in case this method is called multiple times. + this.base64Encoded = Base64.encodeToString(this.formatToCryptString().getBytes(StandardCharsets.UTF_8)); + } + return this.base64Encoded; + } + + /** + * This method MUST return a single-lined string which would also be recognizable by + * a posix {@code /etc/passwd} file. + * + * @return a formatted string, e.g. {@code $2y$10$7rOjsAf2U/AKKqpMpCIn6e$tuOXyQ86tp2Tn9xv6FyXl2T0QYc3.G.} for bcrypt. + */ + public abstract String formatToCryptString(); + + /** + * Returns {@code true} if the specified object is an AbstractCryptHash and its + * {@link #formatToCryptString()} formatted output} is identical to + * this AbstractCryptHash's formatted output, {@code false} otherwise. + * + * @param other the object (AbstractCryptHash) to check for equality. + * @return {@code true} if the specified object is a AbstractCryptHash + * and its {@link #formatToCryptString()} formatted output} is identical to + * this AbstractCryptHash's formatted output, {@code false} otherwise. + */ + @Override + public boolean equals(final Object other) { + if (other instanceof AbstractCryptHash) { + final AbstractCryptHash that = (AbstractCryptHash) other; + return this.formatToCryptString().equals(that.formatToCryptString()); + } + return false; + } + + /** + * Hashes the formatted crypt string. + * + *Implementations should not override this method, as different algorithms produce different output formats + * and require different parameters.
+ * @return a hashcode from the {@link #formatToCryptString() formatted output}. + */ + @Override + public int hashCode() { + return Objects.hash(this.formatToCryptString()); + } + + /** + * Simple implementation that merely returns {@link #toHex() toHex()}. + * + * @return the {@link #toHex() toHex()} value. + */ + @Override + public String toString() { + return new StringJoiner(", ", AbstractCryptHash.class.getSimpleName() + "[", "]") + .add("super=" + super.toString()) + .add("algorithmName='" + algorithmName + "'") + .add("hashedData=" + Arrays.toString(hashedData)) + .add("salt=" + salt) + .add("hexEncoded='" + hexEncoded + "'") + .add("base64Encoded='" + base64Encoded + "'") + .toString(); + } +} diff --git a/crypto/hash/src/main/java/org/apache/shiro/crypto/hash/AbstractHash.java b/crypto/hash/src/main/java/org/apache/shiro/crypto/hash/AbstractHash.java index 4bf8373ae0..684647c7dc 100644 --- a/crypto/hash/src/main/java/org/apache/shiro/crypto/hash/AbstractHash.java +++ b/crypto/hash/src/main/java/org/apache/shiro/crypto/hash/AbstractHash.java @@ -18,11 +18,11 @@ */ package org.apache.shiro.crypto.hash; +import org.apache.shiro.crypto.UnknownAlgorithmException; import org.apache.shiro.lang.codec.Base64; import org.apache.shiro.lang.codec.CodecException; import org.apache.shiro.lang.codec.CodecSupport; import org.apache.shiro.lang.codec.Hex; -import org.apache.shiro.crypto.UnknownAlgorithmException; import java.io.Serializable; import java.security.MessageDigest; @@ -46,6 +46,7 @@ @Deprecated public abstract class AbstractHash extends CodecSupport implements Hash, Serializable { + private static final long serialVersionUID = -4723044219611288405L; /** * The hashed data */ @@ -142,8 +143,10 @@ public AbstractHash(Object source, Object salt, int hashIterations) throws Codec * * @return the {@link MessageDigest MessageDigest} algorithm name to use when performing the hash. */ + @Override public abstract String getAlgorithmName(); + @Override public byte[] getBytes() { return this.bytes; } @@ -233,6 +236,7 @@ protected byte[] hash(byte[] bytes, byte[] salt, int hashIterations) throws Unkn * * @return a hex-encoded string of the underlying {@link #getBytes byte array}. */ + @Override public String toHex() { if (this.hexEncoded == null) { this.hexEncoded = Hex.encodeToString(getBytes()); @@ -249,6 +253,7 @@ public String toHex() { * * @return a Base64-encoded string of the underlying {@link #getBytes byte array}. */ + @Override public String toBase64() { if (this.base64Encoded == null) { //cache result in case this method is called multiple times. @@ -262,6 +267,7 @@ public String toBase64() { * * @return the {@link #toHex() toHex()} value. */ + @Override public String toString() { return toHex(); } @@ -274,6 +280,7 @@ public String toString() { * @return {@code true} if the specified object is a Hash and its {@link #getBytes byte array} is identical to * this Hash's byte array, {@code false} otherwise. */ + @Override public boolean equals(Object o) { if (o instanceof Hash) { Hash other = (Hash) o; @@ -287,6 +294,7 @@ public boolean equals(Object o) { * * @return toHex().hashCode() */ + @Override public int hashCode() { if (this.bytes == null || this.bytes.length == 0) { return 0; @@ -294,68 +302,4 @@ public int hashCode() { return Arrays.hashCode(this.bytes); } - private static void printMainUsage(Class extends AbstractHash> clazz, String type) { - System.out.println("Prints an " + type + " hash value."); - System.out.println("Usage: java " + clazz.getName() + " [-base64] [-saltUsually implementations will re-create {@code this} but with the given plaintext bytes as secret.
+ * + * @param plaintextBytes the plaintext bytes from a user. + * @return {@code true} if the given plaintext generates an equal hash with the same parameters as from this hash. + */ + boolean matchesPassword(ByteSource plaintextBytes); } diff --git a/crypto/hash/src/main/java/org/apache/shiro/crypto/hash/HashProvider.java b/crypto/hash/src/main/java/org/apache/shiro/crypto/hash/HashProvider.java new file mode 100644 index 0000000000..64de6f935b --- /dev/null +++ b/crypto/hash/src/main/java/org/apache/shiro/crypto/hash/HashProvider.java @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.shiro.crypto.hash; + +import java.util.Optional; +import java.util.ServiceLoader; +import java.util.stream.StreamSupport; + +import static java.util.Objects.requireNonNull; + +/** + * Hashes used by the Shiro2CryptFormat class. + * + *Instead of maintaining them as an {@code Enum}, ServiceLoaders would provide a pluggable alternative.
+ * + * @since 2.0 + */ +public final class HashProvider { + + private HashProvider() { + // utility class + } + + /** + * Find a KDF implementation by searching the algorithms. + * + * @param algorithmName the algorithmName to match. This is case-sensitive. + * @return an instance of {@link HashProvider} if found, otherwise {@link Optional#empty()}. + * @throws NullPointerException if the given parameter algorithmName is {@code null}. + */ + public static OptionalIf the map is empty for a specific parameter, the implementation must select the default.
+ * + *Implementations should provide a nested {@code .Parameters} class with {@code public static final String}s + * for convenience.
+ * + *Example parameters the number of requested hash iterations (does not apply to bcrypt), + * memory and cpu constrains, etc. + * Please find their specific names in the implementation’s nested {@code .Parameters} class.
+ * + * @return the parameters for the requested hash to be used when computing the final {@code Hash} result. + * @throws NullPointerException if any of the values is {@code null}. */ - String getAlgorithmName(); + MapApache Shiro will load algorithm implementations based on the method {@link #getImplementedAlgorithms()}. + * Loaded providers are expected to return a suitable hash implementation.
+ * + *Modern kdf-based hash implementations can extend the {@link AbstractCryptHash} class.
+ * + * @since 2.0 + */ +public interface HashSpi { + + /** + * A list of algorithms recognized by this implementation. + * + *Example values are {@code argon2id} and {@code argon2i} for the Argon2 service provider and + * {@code 2y} and {@code 2a} for the BCrypt service provider.
+ * + * @return a set of recognized algorithms. + */ + SetThere is no global format which this provider must accept. Each provider can define their own + * format, but they are usually based on the {@code crypt(3)} formats used in {@code /etc/shadow} files.
+ * + *Implementations should overwrite this javadoc to add examples of the accepted formats.
+ * + * @param format the format string to be parsed by this implementation. + * @return a class extending Hash. + */ + Hash fromString(String format); + + /** + * A factory class for the hash of the type {@codeImplementations are highly encouraged to use the given random parameter as + * source of random bytes (e.g. for seeds).
+ * + * @param random a source of {@link Random}, usually {@code SecureRandom}. + * @return a factory class for creating instances of {@codeIf the hash requests’ optional parameters are not set, the {@link HashFactory} implementation + * should use default parameters where applicable.
+ *If the hash requests’ salt is missing or empty, the implementation should create a salt + * with a default size.
+ * @param hashRequest the request to build a Hash from. + * @return a generated Hash according to the specs. + * @throws IllegalArgumentException if any of the parameters is outside of valid boundaries (algorithm-specific) + * or if the given algorithm is not applicable for this {@link HashFactory}. + */ + Hash generate(HashRequest hashRequest); + } +} diff --git a/crypto/hash/src/main/java/org/apache/shiro/crypto/hash/Md2Hash.java b/crypto/hash/src/main/java/org/apache/shiro/crypto/hash/Md2Hash.java deleted file mode 100644 index dbfb9cb3db..0000000000 --- a/crypto/hash/src/main/java/org/apache/shiro/crypto/hash/Md2Hash.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.shiro.crypto.hash; - -import org.apache.shiro.lang.codec.Base64; -import org.apache.shiro.lang.codec.Hex; - - -/** - * Generates an MD2 Hash (RFC 1319) from a given input source with an optional salt and - * hash iterations. - * - * See the {@link SimpleHash SimpleHash} parent class JavaDoc for a detailed explanation of Hashing - * techniques and how the overloaded constructors function. - * - * @since 0.9 - */ -public class Md2Hash extends SimpleHash { - - public static final String ALGORITHM_NAME = "MD2"; - - public Md2Hash() { - super(ALGORITHM_NAME); - } - - public Md2Hash(Object source) { - super(ALGORITHM_NAME, source); - } - - public Md2Hash(Object source, Object salt) { - super(ALGORITHM_NAME, source, salt); - } - - public Md2Hash(Object source, Object salt, int hashIterations) { - super(ALGORITHM_NAME, source, salt, hashIterations); - } - - public static Md2Hash fromHexString(String hex) { - Md2Hash hash = new Md2Hash(); - hash.setBytes(Hex.decode(hex)); - return hash; - } - - public static Md2Hash fromBase64String(String base64) { - Md2Hash hash = new Md2Hash(); - hash.setBytes(Base64.decode(base64)); - return hash; - } -} diff --git a/crypto/hash/src/main/java/org/apache/shiro/crypto/hash/Md5Hash.java b/crypto/hash/src/main/java/org/apache/shiro/crypto/hash/Md5Hash.java deleted file mode 100644 index a83740a642..0000000000 --- a/crypto/hash/src/main/java/org/apache/shiro/crypto/hash/Md5Hash.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.shiro.crypto.hash; - -import org.apache.shiro.lang.codec.Base64; -import org.apache.shiro.lang.codec.Hex; - -/** - * Generates an MD5 Hash (RFC 1321) from a given input source with an optional salt and - * hash iterations. - * - * See the {@link SimpleHash SimpleHash} parent class JavaDoc for a detailed explanation of Hashing - * techniques and how the overloaded constructors function. - * - * @since 0.9 - */ -public class Md5Hash extends SimpleHash { - - //TODO - complete JavaDoc - - public static final String ALGORITHM_NAME = "MD5"; - - public Md5Hash() { - super(ALGORITHM_NAME); - } - - public Md5Hash(Object source) { - super(ALGORITHM_NAME, source); - } - - public Md5Hash(Object source, Object salt) { - super(ALGORITHM_NAME, source, salt); - } - - public Md5Hash(Object source, Object salt, int hashIterations) { - super(ALGORITHM_NAME, source, salt, hashIterations); - } - - public static Md5Hash fromHexString(String hex) { - Md5Hash hash = new Md5Hash(); - hash.setBytes(Hex.decode(hex)); - return hash; - } - - public static Md5Hash fromBase64String(String base64) { - Md5Hash hash = new Md5Hash(); - hash.setBytes(Base64.decode(base64)); - return hash; - } -} diff --git a/crypto/hash/src/main/java/org/apache/shiro/crypto/hash/Sha1Hash.java b/crypto/hash/src/main/java/org/apache/shiro/crypto/hash/Sha1Hash.java deleted file mode 100644 index e844b70408..0000000000 --- a/crypto/hash/src/main/java/org/apache/shiro/crypto/hash/Sha1Hash.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.shiro.crypto.hash; - -import org.apache.shiro.lang.codec.Base64; -import org.apache.shiro.lang.codec.Hex; - - -/** - * Generates an SHA-1 Hash (Secure Hash Standard, NIST FIPS 180-1) from a given input source with an - * optional salt and hash iterations. - * - * See the {@link SimpleHash SimpleHash} parent class JavaDoc for a detailed explanation of Hashing - * techniques and how the overloaded constructors function. - * - * @since 0.9 - */ -public class Sha1Hash extends SimpleHash { - - //TODO - complete JavaDoc - - public static final String ALGORITHM_NAME = "SHA-1"; - - public Sha1Hash() { - super(ALGORITHM_NAME); - } - - public Sha1Hash(Object source) { - super(ALGORITHM_NAME, source); - } - - public Sha1Hash(Object source, Object salt) { - super(ALGORITHM_NAME, source, salt); - } - - public Sha1Hash(Object source, Object salt, int hashIterations) { - super(ALGORITHM_NAME, source, salt, hashIterations); - } - - public static Sha1Hash fromHexString(String hex) { - Sha1Hash hash = new Sha1Hash(); - hash.setBytes(Hex.decode(hex)); - return hash; - } - - public static Sha1Hash fromBase64String(String base64) { - Sha1Hash hash = new Sha1Hash(); - hash.setBytes(Base64.decode(base64)); - return hash; - } -} diff --git a/crypto/hash/src/main/java/org/apache/shiro/crypto/hash/SimpleHash.java b/crypto/hash/src/main/java/org/apache/shiro/crypto/hash/SimpleHash.java index 8c1fb6e939..eb58a895ef 100644 --- a/crypto/hash/src/main/java/org/apache/shiro/crypto/hash/SimpleHash.java +++ b/crypto/hash/src/main/java/org/apache/shiro/crypto/hash/SimpleHash.java @@ -18,17 +18,22 @@ */ package org.apache.shiro.crypto.hash; +import org.apache.shiro.crypto.UnknownAlgorithmException; import org.apache.shiro.lang.codec.Base64; import org.apache.shiro.lang.codec.CodecException; import org.apache.shiro.lang.codec.Hex; -import org.apache.shiro.crypto.UnknownAlgorithmException; import org.apache.shiro.lang.util.ByteSource; +import org.apache.shiro.lang.util.SimpleByteSource; import org.apache.shiro.lang.util.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Arrays; +import static java.util.Objects.requireNonNull; + /** * A {@code Hash} implementation that allows any {@link java.security.MessageDigest MessageDigest} algorithm name to * be used. This class is a less type-safe variant than the other {@code AbstractHash} subclasses @@ -43,6 +48,9 @@ public class SimpleHash extends AbstractHash { private static final int DEFAULT_ITERATIONS = 1; + private static final long serialVersionUID = -6689895264902387303L; + + private static final Logger LOG = LoggerFactory.getLogger(SimpleHash.class); /** * The {@link java.security.MessageDigest MessageDigest} algorithm name to use when performing the hash. @@ -114,7 +122,7 @@ public SimpleHash(String algorithmName) { */ public SimpleHash(String algorithmName, Object source) throws CodecException, UnknownAlgorithmException { //noinspection NullableProblems - this(algorithmName, source, null, DEFAULT_ITERATIONS); + this(algorithmName, source, SimpleByteSource.empty(), DEFAULT_ITERATIONS); } /** @@ -139,6 +147,28 @@ public SimpleHash(String algorithmName, Object source, Object salt) throws Codec this(algorithmName, source, salt, DEFAULT_ITERATIONS); } + /** + * Creates an {@code algorithmName}-specific hash of the specified {@code source} using the given {@code salt} + * using a single hash iteration. + * + * It is a convenience constructor that merely executesthis( algorithmName, source, salt, 1);
.
+ *
+ * Please see the
+ * {@link #SimpleHash(String algorithmName, Object source, Object salt, int numIterations) SimpleHashHash(algorithmName, Object,Object,int)}
+ * constructor for the types of Objects that may be passed into this constructor, as well as how to support further
+ * types.
+ *
+ * @param algorithmName the {@link java.security.MessageDigest MessageDigest} algorithm name to use when
+ * performing the hash.
+ * @param source the source object to be hashed.
+ * @param hashIterations the number of times the {@code source} argument hashed for attack resiliency.
+ * @throws CodecException if either constructor argument cannot be converted into a byte array.
+ * @throws UnknownAlgorithmException if the {@code algorithmName} is not available.
+ */
+ public SimpleHash(String algorithmName, Object source, int hashIterations) throws CodecException, UnknownAlgorithmException {
+ this(algorithmName, source, SimpleByteSource.empty(), hashIterations);
+ }
+
/**
* Creates an {@code algorithmName}-specific hash of the specified {@code source} using the given
* {@code salt} a total of {@code hashIterations} times.
@@ -169,11 +199,8 @@ public SimpleHash(String algorithmName, Object source, Object salt, int hashIter
}
this.algorithmName = algorithmName;
this.iterations = Math.max(DEFAULT_ITERATIONS, hashIterations);
- ByteSource saltBytes = null;
- if (salt != null) {
- saltBytes = convertSaltToBytes(salt);
- this.salt = saltBytes;
- }
+ ByteSource saltBytes = convertSaltToBytes(salt);
+ this.salt = saltBytes;
ByteSource sourceBytes = convertSourceToBytes(source);
hash(sourceBytes, saltBytes, hashIterations);
}
@@ -209,23 +236,20 @@ protected ByteSource convertSaltToBytes(Object salt) {
/**
* Converts a given object into a {@code ByteSource} instance. Assumes the object can be converted to bytes.
*
- * @param o the Object to convert into a {@code ByteSource} instance.
+ * @param object the Object to convert into a {@code ByteSource} instance.
* @return the {@code ByteSource} representation of the specified object's bytes.
* @since 1.2
*/
- protected ByteSource toByteSource(Object o) {
- if (o == null) {
- return null;
+ protected ByteSource toByteSource(Object object) {
+ if (object instanceof ByteSource) {
+ return (ByteSource) object;
}
- if (o instanceof ByteSource) {
- return (ByteSource) o;
- }
- byte[] bytes = toBytes(o);
+ byte[] bytes = toBytes(object);
return ByteSource.Util.bytes(bytes);
}
private void hash(ByteSource source, ByteSource salt, int hashIterations) throws CodecException, UnknownAlgorithmException {
- byte[] saltBytes = salt != null ? salt.getBytes() : null;
+ byte[] saltBytes = requireNonNull(salt).getBytes();
byte[] hashedBytes = hash(source.getBytes(), saltBytes, hashIterations);
setBytes(hashedBytes);
}
@@ -235,18 +259,34 @@ private void hash(ByteSource source, ByteSource salt, int hashIterations) throws
*
* @return the {@link java.security.MessageDigest MessageDigest} algorithm name to use when performing the hash.
*/
+ @Override
public String getAlgorithmName() {
return this.algorithmName;
}
+ @Override
public ByteSource getSalt() {
return this.salt;
}
+ @Override
public int getIterations() {
return this.iterations;
}
+ @Override
+ public boolean matchesPassword(ByteSource plaintextBytes) {
+ try {
+ SimpleHash otherHash = new SimpleHash(this.getAlgorithmName(), plaintextBytes, this.getSalt(), this.getIterations());
+ return this.equals(otherHash);
+ } catch (IllegalArgumentException illegalArgumentException) {
+ // cannot recreate hash. Do not log password.
+ LOG.warn("Cannot recreate a hash using the same parameters.", illegalArgumentException);
+ return false;
+ }
+ }
+
+ @Override
public byte[] getBytes() {
return this.bytes;
}
@@ -259,6 +299,7 @@ public byte[] getBytes() {
*
* @param alreadyHashedBytes the raw already-hashed bytes to store in this instance.
*/
+ @Override
public void setBytes(byte[] alreadyHashedBytes) {
this.bytes = alreadyHashedBytes;
this.hexEncoded = null;
@@ -298,6 +339,7 @@ public void setSalt(ByteSource salt) {
* @return the MessageDigest object for the specified {@code algorithm}.
* @throws UnknownAlgorithmException if the specified algorithm name is not available.
*/
+ @Override
protected MessageDigest getDigest(String algorithmName) throws UnknownAlgorithmException {
try {
return MessageDigest.getInstance(algorithmName);
@@ -314,6 +356,7 @@ protected MessageDigest getDigest(String algorithmName) throws UnknownAlgorithmE
* @return the hashed bytes.
* @throws UnknownAlgorithmException if the configured {@link #getAlgorithmName() algorithmName} is not available.
*/
+ @Override
protected byte[] hash(byte[] bytes) throws UnknownAlgorithmException {
return hash(bytes, null, DEFAULT_ITERATIONS);
}
@@ -326,6 +369,7 @@ protected byte[] hash(byte[] bytes) throws UnknownAlgorithmException {
* @return the hashed bytes
* @throws UnknownAlgorithmException if the configured {@link #getAlgorithmName() algorithmName} is not available.
*/
+ @Override
protected byte[] hash(byte[] bytes, byte[] salt) throws UnknownAlgorithmException {
return hash(bytes, salt, DEFAULT_ITERATIONS);
}
@@ -339,9 +383,10 @@ protected byte[] hash(byte[] bytes, byte[] salt) throws UnknownAlgorithmExceptio
* @return the hashed bytes.
* @throws UnknownAlgorithmException if the {@link #getAlgorithmName() algorithmName} is not available.
*/
+ @Override
protected byte[] hash(byte[] bytes, byte[] salt, int hashIterations) throws UnknownAlgorithmException {
MessageDigest digest = getDigest(getAlgorithmName());
- if (salt != null) {
+ if (salt.length != 0) {
digest.reset();
digest.update(salt);
}
@@ -355,6 +400,7 @@ protected byte[] hash(byte[] bytes, byte[] salt, int hashIterations) throws Unkn
return hashed;
}
+ @Override
public boolean isEmpty() {
return this.bytes == null || this.bytes.length == 0;
}
@@ -368,6 +414,7 @@ public boolean isEmpty() {
*
* @return a hex-encoded string of the underlying {@link #getBytes byte array}.
*/
+ @Override
public String toHex() {
if (this.hexEncoded == null) {
this.hexEncoded = Hex.encodeToString(getBytes());
@@ -384,6 +431,7 @@ public String toHex() {
*
* @return a Base64-encoded string of the underlying {@link #getBytes byte array}.
*/
+ @Override
public String toBase64() {
if (this.base64Encoded == null) {
//cache result in case this method is called multiple times.
@@ -397,6 +445,7 @@ public String toBase64() {
*
* @return the {@link #toHex() toHex()} value.
*/
+ @Override
public String toString() {
return toHex();
}
@@ -409,6 +458,7 @@ public String toString() {
* @return {@code true} if the specified object is a Hash and its {@link #getBytes byte array} is identical to
* this Hash's byte array, {@code false} otherwise.
*/
+ @Override
public boolean equals(Object o) {
if (o instanceof Hash) {
Hash other = (Hash) o;
@@ -422,6 +472,7 @@ public boolean equals(Object o) {
*
* @return toHex().hashCode()
*/
+ @Override
public int hashCode() {
if (this.bytes == null || this.bytes.length == 0) {
return 0;
diff --git a/crypto/hash/src/main/java/org/apache/shiro/crypto/hash/SimpleHashProvider.java b/crypto/hash/src/main/java/org/apache/shiro/crypto/hash/SimpleHashProvider.java
new file mode 100644
index 0000000000..5b4a44df2c
--- /dev/null
+++ b/crypto/hash/src/main/java/org/apache/shiro/crypto/hash/SimpleHashProvider.java
@@ -0,0 +1,219 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.shiro.crypto.hash;
+
+import org.apache.shiro.crypto.hash.format.Shiro1CryptFormat;
+import org.apache.shiro.lang.util.ByteSource;
+import org.apache.shiro.lang.util.SimpleByteSource;
+
+import java.util.Arrays;
+import java.util.Base64;
+import java.util.NoSuchElementException;
+import java.util.Optional;
+import java.util.Random;
+import java.util.Set;
+
+import static java.util.Collections.unmodifiableSet;
+import static java.util.stream.Collectors.toSet;
+
+/**
+ * Creates a hash provider for salt (+pepper) and Hash-based KDFs, i.e. where the algorithm name
+ * is a SHA algorithm or similar.
+ * @since 2.0
+ */
+public class SimpleHashProvider implements HashSpi {
+
+ private static final Set