Add crypto PasswordEncoder from 5.0.x
Fixes: gh-5302
This commit is contained in:
+3
@@ -22,7 +22,10 @@ package org.springframework.security.authentication.encoding;
|
||||
* </p>
|
||||
*
|
||||
* @author colin sampaleanu
|
||||
* @deprecated This is deprecated and marked for deletion. Replace with an implementation
|
||||
* of {@link org.springframework.security.crypto.password.PasswordEncoder}
|
||||
*/
|
||||
@Deprecated
|
||||
public abstract class BaseDigestPasswordEncoder extends BasePasswordEncoder {
|
||||
// ~ Instance fields
|
||||
// ================================================================================================
|
||||
|
||||
+3
@@ -22,7 +22,10 @@ package org.springframework.security.authentication.encoding;
|
||||
* </p>
|
||||
*
|
||||
* @author Ben Alex
|
||||
* @deprecated This is deprecated and marked for deletion. Replace with an implementation
|
||||
* of {@link org.springframework.security.crypto.password.PasswordEncoder}
|
||||
*/
|
||||
@Deprecated
|
||||
public abstract class BasePasswordEncoder implements PasswordEncoder {
|
||||
// ~ Methods
|
||||
// ========================================================================================================
|
||||
|
||||
+2
@@ -32,6 +32,8 @@ import org.springframework.util.Assert;
|
||||
* and non-encoded passwords are in use or when a null implementation is required.
|
||||
*
|
||||
* @author Luke Taylor
|
||||
* @deprecated @deprecated This is deprecated and marked for deletion. Replace with
|
||||
* of {@link org.springframework.security.crypto.password.LdapShaPasswordEncoder}
|
||||
*/
|
||||
public class LdapShaPasswordEncoder implements PasswordEncoder {
|
||||
// ~ Static fields/initializers
|
||||
|
||||
+3
@@ -31,7 +31,10 @@ import org.springframework.security.crypto.codec.Utf8;
|
||||
* legacy applications, it's not secure, don't use it for anything new!
|
||||
*
|
||||
* @author Alan Stewart
|
||||
* @deprecated This is deprecated and marked for deletion. Replace with an implementation
|
||||
* of {@link org.springframework.security.crypto.password.Md4PasswordEncoder}
|
||||
*/
|
||||
@Deprecated
|
||||
public class Md4PasswordEncoder extends BaseDigestPasswordEncoder {
|
||||
|
||||
// ~ Methods
|
||||
|
||||
+4
@@ -33,7 +33,11 @@ package org.springframework.security.authentication.encoding;
|
||||
* @author Ray Krueger
|
||||
* @author colin sampaleanu
|
||||
* @author Ben Alex
|
||||
* @deprecated This is deprecated and marked for deletion. Replace with an implementation
|
||||
* of {@link org.springframework.security.crypto.password.MessageDigestPasswordEncoder}
|
||||
* with an algorithm of "MD5"
|
||||
*/
|
||||
@Deprecated
|
||||
public class Md5PasswordEncoder extends MessageDigestPasswordEncoder {
|
||||
|
||||
public Md5PasswordEncoder() {
|
||||
|
||||
+3
@@ -51,7 +51,10 @@ import org.springframework.util.Assert;
|
||||
* @author Ray Krueger
|
||||
* @author Luke Taylor
|
||||
* @since 1.0.1
|
||||
* @deprecated This is deprecated and marked for deletion. Replace with an implementation
|
||||
* of {@link org.springframework.security.crypto.password.MessageDigestPasswordEncoder}
|
||||
*/
|
||||
@Deprecated
|
||||
public class MessageDigestPasswordEncoder extends BaseDigestPasswordEncoder {
|
||||
|
||||
private final String algorithm;
|
||||
|
||||
+3
@@ -29,7 +29,10 @@ import java.util.Locale;
|
||||
*
|
||||
* @author colin sampaleanu
|
||||
* @author Ben Alex
|
||||
* @deprecated This class will be removed in Spring Security 5. For passivity switch to
|
||||
* {@link org.springframework.security.crypto.password.NoOpPasswordEncoder}.
|
||||
*/
|
||||
@Deprecated
|
||||
public class PlaintextPasswordEncoder extends BasePasswordEncoder {
|
||||
// ~ Instance fields
|
||||
// ================================================================================================
|
||||
|
||||
+4
@@ -40,7 +40,11 @@ package org.springframework.security.authentication.encoding;
|
||||
* @author Ray Krueger
|
||||
* @author colin sampaleanu
|
||||
* @author Ben Alex
|
||||
* @deprecated This is deprecated and marked for deletion. Replace with an implementation
|
||||
* of {@link org.springframework.security.crypto.password.MessageDigestPasswordEncoder}
|
||||
* with an algorithm "SHA-$strength" (i.e. "SHA-1" or "SHA-256").
|
||||
*/
|
||||
@Deprecated
|
||||
public class ShaPasswordEncoder extends MessageDigestPasswordEncoder {
|
||||
|
||||
/**
|
||||
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.crypto.factory;
|
||||
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.DelegatingPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.LdapShaPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.Md4PasswordEncoder;
|
||||
import org.springframework.security.crypto.password.MessageDigestPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.crypto.password.Pbkdf2PasswordEncoder;
|
||||
import org.springframework.security.crypto.password.StandardPasswordEncoder;
|
||||
import org.springframework.security.crypto.scrypt.SCryptPasswordEncoder;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Used for creating {@link PasswordEncoder} instances
|
||||
* @author Rob Winch
|
||||
* @since 4.2.6
|
||||
*/
|
||||
public class PasswordEncoderFactories {
|
||||
|
||||
/**
|
||||
* Creates a {@link DelegatingPasswordEncoder} with default mappings. Additional
|
||||
* mappings may be added and the encoding will be updated to conform with best
|
||||
* practices. However, due to the nature of {@link DelegatingPasswordEncoder} the
|
||||
* updates should not impact users. The mappings current are:
|
||||
*
|
||||
* <ul>
|
||||
* <li>bcrypt - {@link BCryptPasswordEncoder} (Also used for encoding)</li>
|
||||
* <li>ldap - {@link LdapShaPasswordEncoder}</li>
|
||||
* <li>MD4 - {@link Md4PasswordEncoder}</li>
|
||||
* <li>MD5 - {@code new MessageDigestPasswordEncoder("MD5")}</li>
|
||||
* <li>noop - {@link NoOpPasswordEncoder}</li>
|
||||
* <li>pbkdf2 - {@link Pbkdf2PasswordEncoder}</li>
|
||||
* <li>scrypt - {@link SCryptPasswordEncoder}</li>
|
||||
* <li>SHA-1 - {@code new MessageDigestPasswordEncoder("SHA-1")}</li>
|
||||
* <li>SHA-256 - {@code new MessageDigestPasswordEncoder("SHA-256")}</li>
|
||||
* <li>sha256 - {@link StandardPasswordEncoder}</li>
|
||||
* </ul>
|
||||
*
|
||||
* @return the {@link PasswordEncoder} to use
|
||||
*/
|
||||
public static PasswordEncoder createDelegatingPasswordEncoder() {
|
||||
String encodingId = "bcrypt";
|
||||
Map<String, PasswordEncoder> encoders = new HashMap<String, PasswordEncoder>();
|
||||
encoders.put(encodingId, new BCryptPasswordEncoder());
|
||||
encoders.put("ldap", new LdapShaPasswordEncoder());
|
||||
encoders.put("MD4", new Md4PasswordEncoder());
|
||||
encoders.put("MD5", new MessageDigestPasswordEncoder("MD5"));
|
||||
encoders.put("noop", NoOpPasswordEncoder.getInstance());
|
||||
encoders.put("pbkdf2", new Pbkdf2PasswordEncoder());
|
||||
encoders.put("scrypt", new SCryptPasswordEncoder());
|
||||
encoders.put("SHA-1", new MessageDigestPasswordEncoder("SHA-1"));
|
||||
encoders.put("SHA-256", new MessageDigestPasswordEncoder("SHA-256"));
|
||||
encoders.put("sha256", new StandardPasswordEncoder());
|
||||
|
||||
return new DelegatingPasswordEncoder(encodingId, encoders);
|
||||
}
|
||||
|
||||
private PasswordEncoderFactories() {}
|
||||
}
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.security.crypto.keygen;
|
||||
|
||||
import java.util.Base64;
|
||||
|
||||
/**
|
||||
* A StringKeyGenerator that generates base64-encoded String keys. Delegates to a
|
||||
* {@link BytesKeyGenerator} for the actual key generation.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @author Rob Winch
|
||||
* @since 4.2.6
|
||||
*/
|
||||
public class Base64StringKeyGenerator implements StringKeyGenerator {
|
||||
private static final int DEFAULT_KEY_LENGTH = 32;
|
||||
private final BytesKeyGenerator keyGenerator;
|
||||
private final Base64.Encoder encoder;
|
||||
|
||||
/**
|
||||
* Creates an instance with keyLength of 32 bytes and standard Base64 encoding.
|
||||
*/
|
||||
public Base64StringKeyGenerator() {
|
||||
this(DEFAULT_KEY_LENGTH);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance with the provided key length in bytes and standard Base64
|
||||
* encoding.
|
||||
* @param keyLength the key length in bytes
|
||||
*/
|
||||
public Base64StringKeyGenerator(int keyLength) {
|
||||
this(Base64.getEncoder(), keyLength);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance with keyLength of 32 bytes and the provided encoder.
|
||||
* @param encoder the encoder to use
|
||||
*/
|
||||
public Base64StringKeyGenerator(Base64.Encoder encoder) {
|
||||
this(encoder, DEFAULT_KEY_LENGTH);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance with the provided key length and encoder.
|
||||
* @param encoder the encoder to use
|
||||
* @param keyLength the key length to use
|
||||
*/
|
||||
public Base64StringKeyGenerator(Base64.Encoder encoder, int keyLength) {
|
||||
if(encoder == null) {
|
||||
throw new IllegalArgumentException("encode cannot be null");
|
||||
}
|
||||
if(keyLength < DEFAULT_KEY_LENGTH) {
|
||||
throw new IllegalArgumentException("keyLength must be greater than or equal to" + DEFAULT_KEY_LENGTH);
|
||||
}
|
||||
this.encoder = encoder;
|
||||
this.keyGenerator = KeyGenerators.secureRandom(keyLength);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String generateKey() {
|
||||
byte[] key = this.keyGenerator.generateKey();
|
||||
byte[] base64EncodedKey = this.encoder.encode(key);
|
||||
return new String(base64EncodedKey);
|
||||
}
|
||||
}
|
||||
+241
@@ -0,0 +1,241 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.crypto.password;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A password encoder that delegates to another PasswordEncoder based upon a prefixed
|
||||
* identifier.
|
||||
*
|
||||
* <h2>Constructing an instance</h2>
|
||||
*
|
||||
* You can easily construct an instance using
|
||||
* {@link org.springframework.security.crypto.factory.PasswordEncoderFactories}.
|
||||
* Alternatively, you may create your own custom instance. For example:
|
||||
*
|
||||
* <pre>
|
||||
* String idForEncode = "bcrypt";
|
||||
* Map<String,PasswordEncoder> encoders = new HashMap<>();
|
||||
* encoders.put(idForEncode, new BCryptPasswordEncoder());
|
||||
* encoders.put("noop", NoOpPasswordEncoder.getInstance());
|
||||
* encoders.put("pbkdf2", new Pbkdf2PasswordEncoder());
|
||||
* encoders.put("scrypt", new SCryptPasswordEncoder());
|
||||
* encoders.put("sha256", new StandardPasswordEncoder());
|
||||
*
|
||||
* PasswordEncoder passwordEncoder = new DelegatingPasswordEncoder(idForEncode, encoders);
|
||||
* </pre>
|
||||
*
|
||||
*
|
||||
* <h2>Password Storage Format</h2>
|
||||
*
|
||||
* The general format for a password is:
|
||||
*
|
||||
* <pre>
|
||||
* {id}encodedPassword
|
||||
* </pre>
|
||||
*
|
||||
* Such that "id" is an identifier used to look up which {@link PasswordEncoder} should
|
||||
* be used and "encodedPassword" is the original encoded password for the selected
|
||||
* {@link PasswordEncoder}. The "id" must be at the beginning of the password, start with
|
||||
* "{" and end with "}". If the "id" cannot be found, the "id" will be null.
|
||||
*
|
||||
* For example, the following might be a list of passwords encoded using different "id".
|
||||
* All of the original passwords are "password".
|
||||
*
|
||||
* <pre>
|
||||
* {bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG
|
||||
* {noop}password
|
||||
* {pbkdf2}5d923b44a6d129f3ddf3e3c8d29412723dcbde72445e8ef6bf3b508fbf17fa4ed4d6b99ca763d8dc
|
||||
* {scrypt}$e0801$8bWJaSu2IKSn9Z9kM+TPXfOc/9bdYSrN1oD9qfVThWEwdRTnO7re7Ei+fUZRJ68k9lTyuTeUp4of4g24hHnazw==$OAOec05+bXxvuu/1qZ6NUR+xQYvYv7BeL1QxwRpY5Pc=
|
||||
* {sha256}97cde38028ad898ebc02e690819fa220e88c62e0699403e94fff291cfffaf8410849f27605abcbc0
|
||||
* </pre>
|
||||
*
|
||||
* For the DelegatingPasswordEncoder that we constructed above:
|
||||
*
|
||||
* <ol>
|
||||
* <li>The first password would have a {@code PasswordEncoder} id of "bcrypt" and
|
||||
* encodedPassword of "$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG".
|
||||
* When matching it would delegate to
|
||||
* {@link org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder}</li>
|
||||
* <li>The second password would have a {@code PasswordEncoder} id of "noop" and
|
||||
* encodedPassword of "password". When matching it would delegate to
|
||||
* {@link NoOpPasswordEncoder}</li>
|
||||
* <li>The third password would have a {@code PasswordEncoder} id of "pbkdf2" and
|
||||
* encodedPassword of
|
||||
* "5d923b44a6d129f3ddf3e3c8d29412723dcbde72445e8ef6bf3b508fbf17fa4ed4d6b99ca763d8dc".
|
||||
* When matching it would delegate to {@link Pbkdf2PasswordEncoder}</li>
|
||||
* <li>The fourth password would have a {@code PasswordEncoder} id of "scrypt" and
|
||||
* encodedPassword of
|
||||
* "$e0801$8bWJaSu2IKSn9Z9kM+TPXfOc/9bdYSrN1oD9qfVThWEwdRTnO7re7Ei+fUZRJ68k9lTyuTeUp4of4g24hHnazw==$OAOec05+bXxvuu/1qZ6NUR+xQYvYv7BeL1QxwRpY5Pc="
|
||||
* When matching it would delegate to
|
||||
* {@link org.springframework.security.crypto.scrypt.SCryptPasswordEncoder}</li>
|
||||
* <li>The final password would have a {@code PasswordEncoder} id of "sha256" and
|
||||
* encodedPassword of
|
||||
* "97cde38028ad898ebc02e690819fa220e88c62e0699403e94fff291cfffaf8410849f27605abcbc0".
|
||||
* When matching it would delegate to {@link StandardPasswordEncoder}</li>
|
||||
* </ol>
|
||||
*
|
||||
* <h2>Password Encoding</h2>
|
||||
*
|
||||
* The {@code idForEncode} passed into the constructor determines which
|
||||
* {@link PasswordEncoder} will be used for encoding passwords. In the
|
||||
* {@code DelegatingPasswordEncoder} we constructed above, that means that the result of
|
||||
* encoding "password" would be delegated to {@code BCryptPasswordEncoder} and be prefixed
|
||||
* with "{bcrypt}". The end result would look like:
|
||||
*
|
||||
* <pre>
|
||||
* {bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG
|
||||
* </pre>
|
||||
*
|
||||
* <h2>Password Matching</h2>
|
||||
*
|
||||
* Matching is done based upon the "id" and the mapping of the "id" to the
|
||||
* {@link PasswordEncoder} provided in the constructor. Our example in "Password Storage
|
||||
* Format" provides a working example of how this is done.
|
||||
*
|
||||
* By default the result of invoking {@link #matches(CharSequence, String)} with a
|
||||
* password with an "id" that is not mapped (including a null id) will result in an
|
||||
* {@link IllegalArgumentException}. This behavior can be customized using
|
||||
* {@link #setDefaultPasswordEncoderForMatches(PasswordEncoder)}.
|
||||
*
|
||||
* @see org.springframework.security.crypto.factory.PasswordEncoderFactories
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @author Michael Simons
|
||||
* @since 4.2.6
|
||||
*/
|
||||
public class DelegatingPasswordEncoder implements PasswordEncoder {
|
||||
private static final String PREFIX = "{";
|
||||
private static final String SUFFIX = "}";
|
||||
private final String idForEncode;
|
||||
private final PasswordEncoder passwordEncoderForEncode;
|
||||
private final Map<String, PasswordEncoder> idToPasswordEncoder;
|
||||
private PasswordEncoder defaultPasswordEncoderForMatches = new UnmappedIdPasswordEncoder();
|
||||
|
||||
/**
|
||||
* Creates a new instance
|
||||
* @param idForEncode the id used to lookup which {@link PasswordEncoder} should be
|
||||
* used for {@link #encode(CharSequence)}
|
||||
* @param idToPasswordEncoder a Map of id to {@link PasswordEncoder} used to determine
|
||||
* which {@link PasswordEncoder} should be used for {@link #matches(CharSequence, String)}
|
||||
*/
|
||||
public DelegatingPasswordEncoder(String idForEncode,
|
||||
Map<String, PasswordEncoder> idToPasswordEncoder) {
|
||||
if(idForEncode == null) {
|
||||
throw new IllegalArgumentException("idForEncode cannot be null");
|
||||
}
|
||||
if(!idToPasswordEncoder.containsKey(idForEncode)) {
|
||||
throw new IllegalArgumentException("idForEncode " + idForEncode + "is not found in idToPasswordEncoder " + idToPasswordEncoder);
|
||||
}
|
||||
for(String id : idToPasswordEncoder.keySet()) {
|
||||
if(id == null) {
|
||||
continue;
|
||||
}
|
||||
if(id.contains(PREFIX)) {
|
||||
throw new IllegalArgumentException("id " + id + " cannot contain " + PREFIX);
|
||||
}
|
||||
if(id.contains(SUFFIX)) {
|
||||
throw new IllegalArgumentException("id " + id + " cannot contain " + SUFFIX);
|
||||
}
|
||||
}
|
||||
this.idForEncode = idForEncode;
|
||||
this.passwordEncoderForEncode = idToPasswordEncoder.get(idForEncode);
|
||||
this.idToPasswordEncoder = new HashMap<String, PasswordEncoder>(idToPasswordEncoder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link PasswordEncoder} to delegate to for
|
||||
* {@link #matches(CharSequence, String)} if the id is not mapped to a
|
||||
* {@link PasswordEncoder}.
|
||||
*
|
||||
* <p>
|
||||
The encodedPassword provided will be the full password
|
||||
* passed in including the {"id"} portion.* For example, if the password of
|
||||
* "{notmapped}foobar" was used, the "id" would be "notmapped" and the encodedPassword
|
||||
* passed into the {@link PasswordEncoder} would be "{notmapped}foobar".
|
||||
* </p>
|
||||
* @param defaultPasswordEncoderForMatches the encoder to use. The default is to
|
||||
* throw an {@link IllegalArgumentException}
|
||||
*/
|
||||
public void setDefaultPasswordEncoderForMatches(
|
||||
PasswordEncoder defaultPasswordEncoderForMatches) {
|
||||
if(defaultPasswordEncoderForMatches == null) {
|
||||
throw new IllegalArgumentException("defaultPasswordEncoderForMatches cannot be null");
|
||||
}
|
||||
this.defaultPasswordEncoderForMatches = defaultPasswordEncoderForMatches;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String encode(CharSequence rawPassword) {
|
||||
return PREFIX + this.idForEncode + SUFFIX + this.passwordEncoderForEncode.encode(rawPassword);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(CharSequence rawPassword, String prefixEncodedPassword) {
|
||||
if(rawPassword == null && prefixEncodedPassword == null) {
|
||||
return true;
|
||||
}
|
||||
String id = extractId(prefixEncodedPassword);
|
||||
PasswordEncoder delegate = this.idToPasswordEncoder.get(id);
|
||||
if(delegate == null) {
|
||||
return this.defaultPasswordEncoderForMatches
|
||||
.matches(rawPassword, prefixEncodedPassword);
|
||||
}
|
||||
String encodedPassword = extractEncodedPassword(prefixEncodedPassword);
|
||||
return delegate.matches(rawPassword, encodedPassword);
|
||||
}
|
||||
|
||||
private String extractId(String prefixEncodedPassword) {
|
||||
if (prefixEncodedPassword == null) {
|
||||
return null;
|
||||
}
|
||||
int start = prefixEncodedPassword.indexOf(PREFIX);
|
||||
if(start != 0) {
|
||||
return null;
|
||||
}
|
||||
int end = prefixEncodedPassword.indexOf(SUFFIX, start);
|
||||
if(end < 0) {
|
||||
return null;
|
||||
}
|
||||
return prefixEncodedPassword.substring(start + 1, end);
|
||||
}
|
||||
|
||||
private String extractEncodedPassword(String prefixEncodedPassword) {
|
||||
int start = prefixEncodedPassword.indexOf(SUFFIX);
|
||||
return prefixEncodedPassword.substring(start + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Default {@link PasswordEncoder} that throws an exception that a id could
|
||||
*/
|
||||
private class UnmappedIdPasswordEncoder implements PasswordEncoder {
|
||||
|
||||
@Override
|
||||
public String encode(CharSequence rawPassword) {
|
||||
throw new UnsupportedOperationException("encode is not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(CharSequence rawPassword,
|
||||
String prefixEncodedPassword) {
|
||||
String id = extractId(prefixEncodedPassword);
|
||||
throw new IllegalArgumentException("There is no PasswordEncoder mapped for the id \"" + id + "\"");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,7 @@ final class Digester {
|
||||
|
||||
private final String algorithm;
|
||||
|
||||
private final int iterations;
|
||||
private int iterations;
|
||||
|
||||
/**
|
||||
* Create a new Digester.
|
||||
@@ -42,7 +42,7 @@ final class Digester {
|
||||
// eagerly validate the algorithm
|
||||
createDigest(algorithm);
|
||||
this.algorithm = algorithm;
|
||||
this.iterations = iterations;
|
||||
setIterations(iterations);
|
||||
}
|
||||
|
||||
public byte[] digest(byte[] value) {
|
||||
@@ -53,6 +53,13 @@ final class Digester {
|
||||
return value;
|
||||
}
|
||||
|
||||
final void setIterations(int iterations) {
|
||||
if(iterations <= 0) {
|
||||
throw new IllegalArgumentException("Iterations value must be greater than zero");
|
||||
}
|
||||
this.iterations = iterations;
|
||||
}
|
||||
|
||||
private static MessageDigest createDigest(String algorithm) {
|
||||
try {
|
||||
return MessageDigest.getInstance(algorithm);
|
||||
|
||||
+211
@@ -0,0 +1,211 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.crypto.password;
|
||||
|
||||
import org.springframework.security.crypto.codec.Utf8;
|
||||
import org.springframework.security.crypto.keygen.BytesKeyGenerator;
|
||||
import org.springframework.security.crypto.keygen.KeyGenerators;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.util.Base64;
|
||||
|
||||
/**
|
||||
* This {@link PasswordEncoder} is provided for legacy purposes only and is not considered
|
||||
* secure.
|
||||
*
|
||||
* A version of {@link PasswordEncoder} which supports Ldap SHA and SSHA (salted-SHA)
|
||||
* encodings. The values are base-64 encoded and have the label "{SHA}" (or "{SSHA}")
|
||||
* prepended to the encoded hash. These can be made lower-case in the encoded password, if
|
||||
* required, by setting the <tt>forceLowerCasePrefix</tt> property to true.
|
||||
*
|
||||
* Also supports plain text passwords, so can safely be used in cases when both encoded
|
||||
* and non-encoded passwords are in use or when a null implementation is required.
|
||||
*
|
||||
* @author Luke Taylor
|
||||
* @since 4.2.6
|
||||
* @deprecated Digest based password encoding is not considered secure. Instead use an
|
||||
* adaptive one way funciton like BCryptPasswordEncoder, Pbkdf2PasswordEncoder, or
|
||||
* SCryptPasswordEncoder. Even better use {@link DelegatingPasswordEncoder} which supports
|
||||
* password upgrades. There are no plans to remove this support. It is deprecated to indicate
|
||||
* that this is a legacy implementation and using it is considered insecure.
|
||||
*/
|
||||
@Deprecated
|
||||
public class LdapShaPasswordEncoder implements PasswordEncoder {
|
||||
// ~ Static fields/initializers
|
||||
// =====================================================================================
|
||||
|
||||
/** The number of bytes in a SHA hash */
|
||||
private static final int SHA_LENGTH = 20;
|
||||
private static final String SSHA_PREFIX = "{SSHA}";
|
||||
private static final String SSHA_PREFIX_LC = SSHA_PREFIX.toLowerCase();
|
||||
private static final String SHA_PREFIX = "{SHA}";
|
||||
private static final String SHA_PREFIX_LC = SHA_PREFIX.toLowerCase();
|
||||
|
||||
// ~ Instance fields
|
||||
// ================================================================================================
|
||||
private BytesKeyGenerator saltGenerator;
|
||||
|
||||
private boolean forceLowerCasePrefix;
|
||||
|
||||
// ~ Constructors
|
||||
// ===================================================================================================
|
||||
|
||||
public LdapShaPasswordEncoder() {
|
||||
this(KeyGenerators.secureRandom());
|
||||
}
|
||||
|
||||
public LdapShaPasswordEncoder(BytesKeyGenerator saltGenerator) {
|
||||
if(saltGenerator == null) {
|
||||
throw new IllegalArgumentException("saltGenerator cannot be null");
|
||||
}
|
||||
this.saltGenerator = saltGenerator;
|
||||
}
|
||||
|
||||
// ~ Methods
|
||||
// ========================================================================================================
|
||||
|
||||
private byte[] combineHashAndSalt(byte[] hash, byte[] salt) {
|
||||
if (salt == null) {
|
||||
return hash;
|
||||
}
|
||||
|
||||
byte[] hashAndSalt = new byte[hash.length + salt.length];
|
||||
System.arraycopy(hash, 0, hashAndSalt, 0, hash.length);
|
||||
System.arraycopy(salt, 0, hashAndSalt, hash.length, salt.length);
|
||||
|
||||
return hashAndSalt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the hash of password (and salt bytes, if supplied) and returns a base64
|
||||
* encoded concatenation of the hash and salt, prefixed with {SHA} (or {SSHA} if salt
|
||||
* was used).
|
||||
*
|
||||
* @param rawPass the password to be encoded.
|
||||
*
|
||||
* @return the encoded password in the specified format
|
||||
*
|
||||
*/
|
||||
public String encode(CharSequence rawPass) {
|
||||
byte[] salt = this.saltGenerator.generateKey();
|
||||
return encode(rawPass, salt);
|
||||
}
|
||||
|
||||
|
||||
private String encode(CharSequence rawPassword, byte[] salt) {
|
||||
MessageDigest sha;
|
||||
|
||||
try {
|
||||
sha = MessageDigest.getInstance("SHA");
|
||||
sha.update(Utf8.encode(rawPassword));
|
||||
}
|
||||
catch (java.security.NoSuchAlgorithmException e) {
|
||||
throw new IllegalStateException("No SHA implementation available!");
|
||||
}
|
||||
|
||||
if (salt != null) {
|
||||
sha.update(salt);
|
||||
}
|
||||
|
||||
byte[] hash = combineHashAndSalt(sha.digest(), (byte[]) salt);
|
||||
|
||||
String prefix;
|
||||
|
||||
if (salt == null || salt.length == 0) {
|
||||
prefix = forceLowerCasePrefix ? SHA_PREFIX_LC : SHA_PREFIX;
|
||||
}
|
||||
else {
|
||||
prefix = forceLowerCasePrefix ? SSHA_PREFIX_LC : SSHA_PREFIX;
|
||||
}
|
||||
|
||||
return prefix + Utf8.decode(Base64.getEncoder().encode(hash));
|
||||
}
|
||||
|
||||
private byte[] extractSalt(String encPass) {
|
||||
String encPassNoLabel = encPass.substring(6);
|
||||
|
||||
byte[] hashAndSalt = Base64.getDecoder().decode(encPassNoLabel.getBytes());
|
||||
int saltLength = hashAndSalt.length - SHA_LENGTH;
|
||||
byte[] salt = new byte[saltLength];
|
||||
System.arraycopy(hashAndSalt, SHA_LENGTH, salt, 0, saltLength);
|
||||
|
||||
return salt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the validity of an unencoded password against an encoded one in the form
|
||||
* "{SSHA}sQuQF8vj8Eg2Y1hPdh3bkQhCKQBgjhQI".
|
||||
*
|
||||
* @param rawPassword unencoded password to be verified.
|
||||
* @param encodedPassword the actual SSHA or SHA encoded password
|
||||
*
|
||||
* @return true if they match (independent of the case of the prefix).
|
||||
*/
|
||||
public boolean matches(CharSequence rawPassword, String encodedPassword) {
|
||||
return matches(rawPassword == null ? null : rawPassword.toString(), encodedPassword);
|
||||
}
|
||||
|
||||
private boolean matches(String rawPassword, String encodedPassword) {
|
||||
String prefix = extractPrefix(encodedPassword);
|
||||
|
||||
if (prefix == null) {
|
||||
return PasswordEncoderUtils.equals(encodedPassword, rawPassword);
|
||||
}
|
||||
|
||||
byte[] salt;
|
||||
if (prefix.equals(SSHA_PREFIX) || prefix.equals(SSHA_PREFIX_LC)) {
|
||||
salt = extractSalt(encodedPassword);
|
||||
}
|
||||
else if (!prefix.equals(SHA_PREFIX) && !prefix.equals(SHA_PREFIX_LC)) {
|
||||
throw new IllegalArgumentException("Unsupported password prefix '" + prefix
|
||||
+ "'");
|
||||
}
|
||||
else {
|
||||
// Standard SHA
|
||||
salt = null;
|
||||
}
|
||||
|
||||
int startOfHash = prefix.length();
|
||||
|
||||
String encodedRawPass = encode(rawPassword, salt).substring(startOfHash);
|
||||
|
||||
return PasswordEncoderUtils
|
||||
.equals(encodedRawPass, encodedPassword.substring(startOfHash));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the hash prefix or null if there isn't one.
|
||||
*/
|
||||
private String extractPrefix(String encPass) {
|
||||
if (!encPass.startsWith("{")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int secondBrace = encPass.lastIndexOf('}');
|
||||
|
||||
if (secondBrace < 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"Couldn't find closing brace for SHA prefix");
|
||||
}
|
||||
|
||||
return encPass.substring(0, secondBrace + 1);
|
||||
}
|
||||
|
||||
public void setForceLowerCasePrefix(boolean forceLowerCasePrefix) {
|
||||
this.forceLowerCasePrefix = forceLowerCasePrefix;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.security.crypto.password;
|
||||
|
||||
/**
|
||||
* Implementation of the MD4 message digest derived from the RSA Data Security, Inc, MD4
|
||||
* Message-Digest Algorithm.
|
||||
*
|
||||
* @author Alan Stewart
|
||||
*/
|
||||
class Md4 {
|
||||
private static final int BLOCK_SIZE = 64;
|
||||
private static final int HASH_SIZE = 16;
|
||||
private final byte[] buffer = new byte[BLOCK_SIZE];
|
||||
private int bufferOffset;
|
||||
private long byteCount;
|
||||
private final int[] state = new int[4];
|
||||
private final int[] tmp = new int[16];
|
||||
|
||||
Md4() {
|
||||
reset();
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
bufferOffset = 0;
|
||||
byteCount = 0;
|
||||
state[0] = 0x67452301;
|
||||
state[1] = 0xEFCDAB89;
|
||||
state[2] = 0x98BADCFE;
|
||||
state[3] = 0x10325476;
|
||||
}
|
||||
|
||||
public byte[] digest() {
|
||||
byte[] resBuf = new byte[HASH_SIZE];
|
||||
digest(resBuf, 0, HASH_SIZE);
|
||||
return resBuf;
|
||||
}
|
||||
|
||||
private void digest(byte[] buffer, int off) {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
for (int j = 0; j < 4; j++) {
|
||||
buffer[off + (i * 4 + j)] = (byte) (state[i] >>> (8 * j));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void digest(byte[] buffer, int offset, int len) {
|
||||
this.buffer[this.bufferOffset++] = (byte) 0x80;
|
||||
int lenOfBitLen = 8;
|
||||
int C = BLOCK_SIZE - lenOfBitLen;
|
||||
if (this.bufferOffset > C) {
|
||||
while (this.bufferOffset < BLOCK_SIZE) {
|
||||
this.buffer[this.bufferOffset++] = (byte) 0x00;
|
||||
}
|
||||
update(this.buffer, 0);
|
||||
this.bufferOffset = 0;
|
||||
}
|
||||
|
||||
while (this.bufferOffset < C) {
|
||||
this.buffer[this.bufferOffset++] = (byte) 0x00;
|
||||
}
|
||||
|
||||
long bitCount = byteCount * 8;
|
||||
for (int i = 0; i < 64; i += 8) {
|
||||
this.buffer[this.bufferOffset++] = (byte) (bitCount >>> (i));
|
||||
}
|
||||
|
||||
update(this.buffer, 0);
|
||||
digest(buffer, offset);
|
||||
}
|
||||
|
||||
public void update(byte[] input, int offset, int length) {
|
||||
byteCount += length;
|
||||
int todo;
|
||||
while (length >= (todo = BLOCK_SIZE - this.bufferOffset)) {
|
||||
System.arraycopy(input, offset, this.buffer, this.bufferOffset, todo);
|
||||
update(this.buffer, 0);
|
||||
length -= todo;
|
||||
offset += todo;
|
||||
this.bufferOffset = 0;
|
||||
}
|
||||
|
||||
System.arraycopy(input, offset, this.buffer, this.bufferOffset, length);
|
||||
bufferOffset += length;
|
||||
}
|
||||
|
||||
private void update(byte[] block, int offset) {
|
||||
for (int i = 0; i < 16; i++) {
|
||||
tmp[i] = (block[offset++] & 0xFF) | (block[offset++] & 0xFF) << 8
|
||||
| (block[offset++] & 0xFF) << 16 | (block[offset++] & 0xFF) << 24;
|
||||
}
|
||||
|
||||
int A = state[0];
|
||||
int B = state[1];
|
||||
int C = state[2];
|
||||
int D = state[3];
|
||||
|
||||
A = FF(A, B, C, D, tmp[0], 3);
|
||||
D = FF(D, A, B, C, tmp[1], 7);
|
||||
C = FF(C, D, A, B, tmp[2], 11);
|
||||
B = FF(B, C, D, A, tmp[3], 19);
|
||||
A = FF(A, B, C, D, tmp[4], 3);
|
||||
D = FF(D, A, B, C, tmp[5], 7);
|
||||
C = FF(C, D, A, B, tmp[6], 11);
|
||||
B = FF(B, C, D, A, tmp[7], 19);
|
||||
A = FF(A, B, C, D, tmp[8], 3);
|
||||
D = FF(D, A, B, C, tmp[9], 7);
|
||||
C = FF(C, D, A, B, tmp[10], 11);
|
||||
B = FF(B, C, D, A, tmp[11], 19);
|
||||
A = FF(A, B, C, D, tmp[12], 3);
|
||||
D = FF(D, A, B, C, tmp[13], 7);
|
||||
C = FF(C, D, A, B, tmp[14], 11);
|
||||
B = FF(B, C, D, A, tmp[15], 19);
|
||||
|
||||
A = GG(A, B, C, D, tmp[0], 3);
|
||||
D = GG(D, A, B, C, tmp[4], 5);
|
||||
C = GG(C, D, A, B, tmp[8], 9);
|
||||
B = GG(B, C, D, A, tmp[12], 13);
|
||||
A = GG(A, B, C, D, tmp[1], 3);
|
||||
D = GG(D, A, B, C, tmp[5], 5);
|
||||
C = GG(C, D, A, B, tmp[9], 9);
|
||||
B = GG(B, C, D, A, tmp[13], 13);
|
||||
A = GG(A, B, C, D, tmp[2], 3);
|
||||
D = GG(D, A, B, C, tmp[6], 5);
|
||||
C = GG(C, D, A, B, tmp[10], 9);
|
||||
B = GG(B, C, D, A, tmp[14], 13);
|
||||
A = GG(A, B, C, D, tmp[3], 3);
|
||||
D = GG(D, A, B, C, tmp[7], 5);
|
||||
C = GG(C, D, A, B, tmp[11], 9);
|
||||
B = GG(B, C, D, A, tmp[15], 13);
|
||||
|
||||
A = HH(A, B, C, D, tmp[0], 3);
|
||||
D = HH(D, A, B, C, tmp[8], 9);
|
||||
C = HH(C, D, A, B, tmp[4], 11);
|
||||
B = HH(B, C, D, A, tmp[12], 15);
|
||||
A = HH(A, B, C, D, tmp[2], 3);
|
||||
D = HH(D, A, B, C, tmp[10], 9);
|
||||
C = HH(C, D, A, B, tmp[6], 11);
|
||||
B = HH(B, C, D, A, tmp[14], 15);
|
||||
A = HH(A, B, C, D, tmp[1], 3);
|
||||
D = HH(D, A, B, C, tmp[9], 9);
|
||||
C = HH(C, D, A, B, tmp[5], 11);
|
||||
B = HH(B, C, D, A, tmp[13], 15);
|
||||
A = HH(A, B, C, D, tmp[3], 3);
|
||||
D = HH(D, A, B, C, tmp[11], 9);
|
||||
C = HH(C, D, A, B, tmp[7], 11);
|
||||
B = HH(B, C, D, A, tmp[15], 15);
|
||||
|
||||
state[0] += A;
|
||||
state[1] += B;
|
||||
state[2] += C;
|
||||
state[3] += D;
|
||||
}
|
||||
|
||||
private int FF(int a, int b, int c, int d, int x, int s) {
|
||||
int t = a + ((b & c) | (~b & d)) + x;
|
||||
return t << s | t >>> (32 - s);
|
||||
}
|
||||
|
||||
private int GG(int a, int b, int c, int d, int x, int s) {
|
||||
int t = a + ((b & (c | d)) | (c & d)) + x + 0x5A827999;
|
||||
return t << s | t >>> (32 - s);
|
||||
}
|
||||
|
||||
private int HH(int a, int b, int c, int d, int x, int s) {
|
||||
int t = a + (b ^ c ^ d) + x + 0x6ED9EBA1;
|
||||
return t << s | t >>> (32 - s);
|
||||
}
|
||||
}
|
||||
+155
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.security.crypto.password;
|
||||
|
||||
import org.springframework.security.crypto.codec.Hex;
|
||||
import org.springframework.security.crypto.codec.Utf8;
|
||||
import org.springframework.security.crypto.keygen.Base64StringKeyGenerator;
|
||||
import org.springframework.security.crypto.keygen.StringKeyGenerator;
|
||||
|
||||
import java.util.Base64;
|
||||
|
||||
/**
|
||||
* This {@link PasswordEncoder} is provided for legacy purposes only and is not considered secure.
|
||||
*
|
||||
* Encodes passwords using MD4. The general format of the password is:
|
||||
*
|
||||
* <pre>
|
||||
* s = salt == null ? "" : "{" + salt + "}"
|
||||
* s + md4(password + s)
|
||||
* </pre>
|
||||
*
|
||||
* Such that "salt" is the salt, md4 is the digest method, and password is the actual
|
||||
* password. For example with a password of "password", and a salt of
|
||||
* "thisissalt":
|
||||
*
|
||||
* <pre>
|
||||
* String s = salt == null ? "" : "{" + salt + "}";
|
||||
* s + md4(password + s)
|
||||
* "{thisissalt}" + md4(password + "{thisissalt}")
|
||||
* "{thisissalt}6cc7924dad12ade79dfb99e424f25260"
|
||||
* </pre>
|
||||
*
|
||||
* If the salt does not exist, then omit "{salt}" like this:
|
||||
*
|
||||
* <pre>
|
||||
* md4(password)
|
||||
* </pre>
|
||||
*
|
||||
* If the salt is an empty String, then only use "{}" like this:
|
||||
*
|
||||
* <pre>
|
||||
* "{}" + md4(password + "{}")
|
||||
* </pre>
|
||||
*
|
||||
* The format is intended to work with the Md4PasswordEncoder that was found in the
|
||||
* Spring Security core module. However, the passwords will need to be migrated to include
|
||||
* any salt with the password since this API provides Salt internally vs making it the
|
||||
* responsibility of the user. To migrate passwords from the SaltSource use the following:
|
||||
*
|
||||
* <pre>
|
||||
* String salt = saltSource.getSalt(user);
|
||||
* String s = salt == null ? null : "{" + salt + "}";
|
||||
* String migratedPassword = s + user.getPassword();
|
||||
* </pre>
|
||||
*
|
||||
* @author Ray Krueger
|
||||
* @author Luke Taylor
|
||||
* @author Rob winch
|
||||
* @since 5.1
|
||||
* @deprecated Digest based password encoding is not considered secure. Instead use an
|
||||
* adaptive one way funciton like BCryptPasswordEncoder, Pbkdf2PasswordEncoder, or
|
||||
* SCryptPasswordEncoder. Even better use {@link DelegatingPasswordEncoder} which supports
|
||||
* password upgrades. There are no plans to remove this support. It is deprecated to indicate
|
||||
* that this is a legacy implementation and using it is considered insecure.
|
||||
*/
|
||||
@Deprecated
|
||||
public class Md4PasswordEncoder implements PasswordEncoder {
|
||||
private static final String PREFIX = "{";
|
||||
private static final String SUFFIX = "}";
|
||||
private StringKeyGenerator saltGenerator = new Base64StringKeyGenerator();
|
||||
private boolean encodeHashAsBase64;
|
||||
|
||||
private Digester digester;
|
||||
|
||||
|
||||
public void setEncodeHashAsBase64(boolean encodeHashAsBase64) {
|
||||
this.encodeHashAsBase64 = encodeHashAsBase64;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the rawPass using a MessageDigest. If a salt is specified it will be merged
|
||||
* with the password before encoding.
|
||||
*
|
||||
* @param rawPassword The plain text password
|
||||
* @return Hex string of password digest (or base64 encoded string if
|
||||
* encodeHashAsBase64 is enabled.
|
||||
*/
|
||||
public String encode(CharSequence rawPassword) {
|
||||
String salt = PREFIX + this.saltGenerator.generateKey() + SUFFIX;
|
||||
return digest(salt, rawPassword);
|
||||
}
|
||||
|
||||
private String digest(String salt, CharSequence rawPassword) {
|
||||
if(rawPassword == null) {
|
||||
rawPassword = "";
|
||||
}
|
||||
String saltedPassword = rawPassword + salt;
|
||||
byte[] saltedPasswordBytes = Utf8.encode(saltedPassword);
|
||||
|
||||
Md4 md4 = new Md4();
|
||||
md4.update(saltedPasswordBytes, 0, saltedPasswordBytes.length);
|
||||
|
||||
byte[] digest = md4.digest();
|
||||
String encoded = encode(digest);
|
||||
return salt + encoded;
|
||||
}
|
||||
|
||||
private String encode(byte[] digest) {
|
||||
if (this.encodeHashAsBase64) {
|
||||
return Utf8.decode(Base64.getEncoder().encode(digest));
|
||||
}
|
||||
else {
|
||||
return new String(Hex.encode(digest));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a previously encoded password and compares it with a rawpassword after mixing
|
||||
* in the salt and encoding that value
|
||||
*
|
||||
* @param rawPassword plain text password
|
||||
* @param encodedPassword previously encoded password
|
||||
* @return true or false
|
||||
*/
|
||||
public boolean matches(CharSequence rawPassword, String encodedPassword) {
|
||||
String salt = extractSalt(encodedPassword);
|
||||
String rawPasswordEncoded = digest(salt, rawPassword);
|
||||
return PasswordEncoderUtils.equals(encodedPassword.toString(), rawPasswordEncoded);
|
||||
}
|
||||
|
||||
private String extractSalt(String prefixEncodedPassword) {
|
||||
int start = prefixEncodedPassword.indexOf(PREFIX);
|
||||
if(start != 0) {
|
||||
return "";
|
||||
}
|
||||
int end = prefixEncodedPassword.indexOf(SUFFIX, start);
|
||||
if(end < 0) {
|
||||
return "";
|
||||
}
|
||||
return prefixEncodedPassword.substring(start, end + 1);
|
||||
}
|
||||
}
|
||||
+174
@@ -0,0 +1,174 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.security.crypto.password;
|
||||
|
||||
import org.springframework.security.crypto.codec.Hex;
|
||||
import org.springframework.security.crypto.codec.Utf8;
|
||||
import org.springframework.security.crypto.keygen.Base64StringKeyGenerator;
|
||||
import org.springframework.security.crypto.keygen.StringKeyGenerator;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.util.Base64;
|
||||
|
||||
/**
|
||||
* This {@link PasswordEncoder} is provided for legacy purposes only and is not considered secure.
|
||||
*
|
||||
* Encodes passwords using the passed in {@link MessageDigest}.
|
||||
*
|
||||
* The general format of the password is:
|
||||
*
|
||||
* <pre>
|
||||
* s = salt == null ? "" : "{" + salt + "}"
|
||||
* s + digest(password + s)
|
||||
* </pre>
|
||||
*
|
||||
* Such that "salt" is the salt, digest is the digest method, and password is the actual
|
||||
* password. For example when using MD5, a password of "password", and a salt of
|
||||
* "thisissalt":
|
||||
*
|
||||
* <pre>
|
||||
* String s = salt == null ? "" : "{" + salt + "}";
|
||||
* s + md5(password + s)
|
||||
* "{thisissalt}" + md5(password + "{thisissalt}")
|
||||
* "{thisissalt}2a4e7104c2780098f50ed5a84bb2323d"
|
||||
* </pre>
|
||||
*
|
||||
* If the salt does not exist, then omit "{salt}" like this:
|
||||
*
|
||||
* <pre>
|
||||
* digest(password)
|
||||
* </pre>
|
||||
*
|
||||
* If the salt is an empty String, then only use "{}" like this:
|
||||
*
|
||||
* <pre>
|
||||
* "{}" + digest(password + "{}")
|
||||
* </pre>
|
||||
*
|
||||
* The format is intended to work with the DigestPasswordEncoder that was found in the
|
||||
* Spring Security core module. However, the passwords will need to be migrated to include
|
||||
* any salt with the password since this API provides Salt internally vs making it the
|
||||
* responsibility of the user. To migrate passwords from the SaltSource use the following:
|
||||
*
|
||||
* <pre>
|
||||
* String salt = saltSource.getSalt(user);
|
||||
* String s = salt == null ? null : "{" + salt + "}";
|
||||
* String migratedPassword = s + user.getPassword();
|
||||
* </pre>
|
||||
*
|
||||
* @author Ray Krueger
|
||||
* @author Luke Taylor
|
||||
* @author Rob Winch
|
||||
* @since 4.2.6
|
||||
* @deprecated Digest based password encoding is not considered secure. Instead use an
|
||||
* adaptive one way funciton like BCryptPasswordEncoder, Pbkdf2PasswordEncoder, or
|
||||
* SCryptPasswordEncoder. Even better use {@link DelegatingPasswordEncoder} which supports
|
||||
* password upgrades. There are no plans to remove this support. It is deprecated to indicate
|
||||
* that this is a legacy implementation and using it is considered insecure.
|
||||
*/
|
||||
@Deprecated
|
||||
public class MessageDigestPasswordEncoder implements PasswordEncoder {
|
||||
private static final String PREFIX = "{";
|
||||
private static final String SUFFIX = "}";
|
||||
private StringKeyGenerator saltGenerator = new Base64StringKeyGenerator();
|
||||
private boolean encodeHashAsBase64;
|
||||
|
||||
private Digester digester;
|
||||
|
||||
/**
|
||||
* The digest algorithm to use Supports the named
|
||||
* <a href="http://java.sun.com/j2se/1.4.2/docs/guide/security/CryptoSpec.html#AppA">
|
||||
* Message Digest Algorithms</a> in the Java environment.
|
||||
*
|
||||
* @param algorithm
|
||||
*/
|
||||
public MessageDigestPasswordEncoder(String algorithm) {
|
||||
this.digester = new Digester(algorithm, 1);
|
||||
}
|
||||
|
||||
public void setEncodeHashAsBase64(boolean encodeHashAsBase64) {
|
||||
this.encodeHashAsBase64 = encodeHashAsBase64;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the rawPass using a MessageDigest. If a salt is specified it will be merged
|
||||
* with the password before encoding.
|
||||
*
|
||||
* @param rawPassword The plain text password
|
||||
* @return Hex string of password digest (or base64 encoded string if
|
||||
* encodeHashAsBase64 is enabled.
|
||||
*/
|
||||
public String encode(CharSequence rawPassword) {
|
||||
String salt = PREFIX + this.saltGenerator.generateKey() + SUFFIX;
|
||||
return digest(salt, rawPassword);
|
||||
}
|
||||
|
||||
private String digest(String salt, CharSequence rawPassword) {
|
||||
String saltedPassword = rawPassword + salt;
|
||||
|
||||
byte[] digest = this.digester.digest(Utf8.encode(saltedPassword));
|
||||
String encoded = encode(digest);
|
||||
return salt + encoded;
|
||||
}
|
||||
|
||||
private String encode(byte[] digest) {
|
||||
if (this.encodeHashAsBase64) {
|
||||
return Utf8.decode(Base64.getEncoder().encode(digest));
|
||||
}
|
||||
else {
|
||||
return new String(Hex.encode(digest));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a previously encoded password and compares it with a rawpassword after mixing
|
||||
* in the salt and encoding that value
|
||||
*
|
||||
* @param rawPassword plain text password
|
||||
* @param encodedPassword previously encoded password
|
||||
* @return true or false
|
||||
*/
|
||||
public boolean matches(CharSequence rawPassword, String encodedPassword) {
|
||||
String salt = extractSalt(encodedPassword);
|
||||
String rawPasswordEncoded = digest(salt, rawPassword);
|
||||
return PasswordEncoderUtils.equals(encodedPassword.toString(), rawPasswordEncoded);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the number of iterations for which the calculated hash value should be
|
||||
* "stretched". If this is greater than one, the initial digest is calculated, the
|
||||
* digest function will be called repeatedly on the result for the additional number
|
||||
* of iterations.
|
||||
*
|
||||
* @param iterations the number of iterations which will be executed on the hashed
|
||||
* password/salt value. Defaults to 1.
|
||||
*/
|
||||
public void setIterations(int iterations) {
|
||||
this.digester.setIterations(iterations);
|
||||
}
|
||||
|
||||
private String extractSalt(String prefixEncodedPassword) {
|
||||
int start = prefixEncodedPassword.indexOf(PREFIX);
|
||||
if(start != 0) {
|
||||
return "";
|
||||
}
|
||||
int end = prefixEncodedPassword.indexOf(SUFFIX, start);
|
||||
if(end < 0) {
|
||||
return "";
|
||||
}
|
||||
return prefixEncodedPassword.substring(start, end + 1);
|
||||
}
|
||||
}
|
||||
+8
@@ -16,11 +16,19 @@
|
||||
package org.springframework.security.crypto.password;
|
||||
|
||||
/**
|
||||
* This {@link PasswordEncoder} is provided for legacy and testing purposes only and is
|
||||
* not considered secure.
|
||||
*
|
||||
* A password encoder that does nothing. Useful for testing where working with plain text
|
||||
* passwords may be preferred.
|
||||
*
|
||||
* @author Keith Donald
|
||||
* @deprecated This PasswordEncoder is not secure. Instead use an
|
||||
* adaptive one way function like BCryptPasswordEncoder, Pbkdf2PasswordEncoder, or
|
||||
* SCryptPasswordEncoder. Even better use {@link DelegatingPasswordEncoder} which supports
|
||||
* password upgrades.
|
||||
*/
|
||||
@Deprecated
|
||||
public final class NoOpPasswordEncoder implements PasswordEncoder {
|
||||
|
||||
public String encode(CharSequence rawPassword) {
|
||||
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.security.crypto.password;
|
||||
|
||||
import org.springframework.security.crypto.codec.Utf8;
|
||||
|
||||
/**
|
||||
* Utility for constant time comparison to prevent against timing attacks.
|
||||
*
|
||||
* @author Rob Winch
|
||||
*/
|
||||
class PasswordEncoderUtils {
|
||||
|
||||
/**
|
||||
* Constant time comparison to prevent against timing attacks.
|
||||
* @param expected
|
||||
* @param actual
|
||||
* @return
|
||||
*/
|
||||
static boolean equals(String expected, String actual) {
|
||||
byte[] expectedBytes = bytesUtf8(expected);
|
||||
byte[] actualBytes = bytesUtf8(actual);
|
||||
int expectedLength = expectedBytes == null ? -1 : expectedBytes.length;
|
||||
int actualLength = actualBytes == null ? -1 : actualBytes.length;
|
||||
|
||||
int result = expectedLength == actualLength ? 0 : 1;
|
||||
for (int i = 0; i < actualLength; i++) {
|
||||
byte expectedByte = expectedLength <= 0 ? 0 : expectedBytes[i % expectedLength];
|
||||
byte actualByte = actualBytes[i % actualLength];
|
||||
result |= expectedByte ^ actualByte;
|
||||
}
|
||||
return result == 0;
|
||||
}
|
||||
|
||||
private static byte[] bytesUtf8(String s) {
|
||||
if (s == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Utf8.encode(s); // need to check if Utf8.encode() runs in constant time (probably not). This may leak length of string.
|
||||
}
|
||||
|
||||
private PasswordEncoderUtils() {
|
||||
}
|
||||
}
|
||||
+67
-5
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,6 +16,8 @@
|
||||
package org.springframework.security.crypto.password;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Base64;
|
||||
|
||||
import javax.crypto.SecretKeyFactory;
|
||||
import javax.crypto.spec.PBEKeySpec;
|
||||
@@ -41,7 +43,7 @@ import static org.springframework.security.crypto.util.EncodingUtils.subArray;
|
||||
* @since 4.1
|
||||
*/
|
||||
public class Pbkdf2PasswordEncoder implements PasswordEncoder {
|
||||
private static final String PBKDF2_ALGORITHM = "PBKDF2WithHmacSHA1";
|
||||
|
||||
private static final int DEFAULT_HASH_WIDTH = 256;
|
||||
private static final int DEFAULT_ITERATIONS = 185000;
|
||||
|
||||
@@ -50,6 +52,8 @@ public class Pbkdf2PasswordEncoder implements PasswordEncoder {
|
||||
private final byte[] secret;
|
||||
private final int hashWidth;
|
||||
private final int iterations;
|
||||
private String algorithm = SecretKeyFactoryAlgorithm.PBKDF2WithHmacSHA1.name();
|
||||
private boolean encodeHashAsBase64;
|
||||
|
||||
/**
|
||||
* Constructs a PBKDF2 password encoder with no additional secret value. There will be
|
||||
@@ -86,16 +90,56 @@ public class Pbkdf2PasswordEncoder implements PasswordEncoder {
|
||||
this.hashWidth = hashWidth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the algorithm to use. See
|
||||
* <a href="http://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#SecretKeyFactory">SecretKeyFactory Algorithms</a>
|
||||
* @param secretKeyFactoryAlgorithm the algorithm to use (i.e.
|
||||
* {@code SecretKeyFactoryAlgorithm.PBKDF2WithHmacSHA1},
|
||||
* {@code SecretKeyFactoryAlgorithm.PBKDF2WithHmacSHA256},
|
||||
* {@code SecretKeyFactoryAlgorithm.PBKDF2WithHmacSHA512})
|
||||
* @since 5.0
|
||||
*/
|
||||
public void setAlgorithm(SecretKeyFactoryAlgorithm secretKeyFactoryAlgorithm) {
|
||||
if(secretKeyFactoryAlgorithm == null) {
|
||||
throw new IllegalArgumentException("secretKeyFactoryAlgorithm cannot be null");
|
||||
}
|
||||
String algorithmName = secretKeyFactoryAlgorithm.name();
|
||||
try {
|
||||
SecretKeyFactory.getInstance(algorithmName);
|
||||
}
|
||||
catch (NoSuchAlgorithmException e) {
|
||||
throw new IllegalArgumentException("Invalid algorithm '" + algorithmName + "'.", e);
|
||||
}
|
||||
this.algorithm = algorithmName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets if the resulting hash should be encoded as Base64. The default is false which
|
||||
* means it will be encoded in Hex.
|
||||
* @param encodeHashAsBase64 true if encode as Base64, false if should use Hex
|
||||
* (default)
|
||||
*/
|
||||
public void setEncodeHashAsBase64(boolean encodeHashAsBase64) {
|
||||
this.encodeHashAsBase64 = encodeHashAsBase64;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String encode(CharSequence rawPassword) {
|
||||
byte[] salt = this.saltGenerator.generateKey();
|
||||
byte[] encoded = encode(rawPassword, salt);
|
||||
return String.valueOf(Hex.encode(encoded));
|
||||
return encode(encoded);
|
||||
}
|
||||
|
||||
private String encode(byte[] bytes) {
|
||||
if(this.encodeHashAsBase64) {
|
||||
return Base64.getEncoder().encodeToString(bytes);
|
||||
}
|
||||
return String.valueOf(Hex.encode(bytes));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(CharSequence rawPassword, String encodedPassword) {
|
||||
byte[] digested = Hex.decode(encodedPassword);
|
||||
byte[] digested = decode(encodedPassword);
|
||||
byte[] salt = subArray(digested, 0, this.saltGenerator.getKeyLength());
|
||||
return matches(digested, encode(rawPassword, salt));
|
||||
}
|
||||
@@ -115,15 +159,33 @@ public class Pbkdf2PasswordEncoder implements PasswordEncoder {
|
||||
return result == 0;
|
||||
}
|
||||
|
||||
private byte[] decode(String encodedBytes) {
|
||||
if(this.encodeHashAsBase64) {
|
||||
return Base64.getDecoder().decode(encodedBytes);
|
||||
}
|
||||
return Hex.decode(encodedBytes);
|
||||
}
|
||||
|
||||
private byte[] encode(CharSequence rawPassword, byte[] salt) {
|
||||
try {
|
||||
PBEKeySpec spec = new PBEKeySpec(rawPassword.toString().toCharArray(),
|
||||
concatenate(salt, this.secret), this.iterations, this.hashWidth);
|
||||
SecretKeyFactory skf = SecretKeyFactory.getInstance(PBKDF2_ALGORITHM);
|
||||
SecretKeyFactory skf = SecretKeyFactory.getInstance(this.algorithm);
|
||||
return concatenate(salt, skf.generateSecret(spec).getEncoded());
|
||||
}
|
||||
catch (GeneralSecurityException e) {
|
||||
throw new IllegalStateException("Could not create hash", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The Algorithm used for creating the {@link SecretKeyFactory}
|
||||
*
|
||||
* @since 5.0
|
||||
*/
|
||||
public enum SecretKeyFactoryAlgorithm {
|
||||
PBKDF2WithHmacSHA1,
|
||||
PBKDF2WithHmacSHA256,
|
||||
PBKDF2WithHmacSHA512
|
||||
}
|
||||
}
|
||||
|
||||
+9
@@ -24,6 +24,9 @@ import org.springframework.security.crypto.keygen.BytesKeyGenerator;
|
||||
import org.springframework.security.crypto.keygen.KeyGenerators;
|
||||
|
||||
/**
|
||||
* This {@link PasswordEncoder} is provided for legacy purposes only and is not considered
|
||||
* secure.
|
||||
*
|
||||
* A standard {@code PasswordEncoder} implementation that uses SHA-256 hashing with 1024
|
||||
* iterations and a random 8-byte random salt value. It uses an additional system-wide
|
||||
* secret value to provide additional protection.
|
||||
@@ -37,7 +40,13 @@ import org.springframework.security.crypto.keygen.KeyGenerators;
|
||||
*
|
||||
* @author Keith Donald
|
||||
* @author Luke Taylor
|
||||
* @deprecated Digest based password encoding is not considered secure. Instead use an
|
||||
* adaptive one way function like BCryptPasswordEncoder, Pbkdf2PasswordEncoder, or
|
||||
* SCryptPasswordEncoder. Even better use {@link DelegatingPasswordEncoder} which supports
|
||||
* password upgrades. There are no plans to remove this support. It is deprecated to indicate
|
||||
* that this is a legacy implementation and using it is considered insecure.
|
||||
*/
|
||||
@Deprecated
|
||||
public final class StandardPasswordEncoder implements PasswordEncoder {
|
||||
|
||||
private final Digester digester;
|
||||
|
||||
@@ -34,7 +34,7 @@ public class HexTests {
|
||||
@Test
|
||||
public void encode() {
|
||||
assertThat(Hex.encode(new byte[] { (byte) 'A', (byte) 'B', (byte) 'C',
|
||||
(byte) 'D' })).isEqualTo(new char[] {'4','1','4','2','4','3','4','4'});
|
||||
(byte) 'D' })).isEqualTo(new char[] {'4', '1', '4', '2', '4', '3', '4', '4'});
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.crypto.factory;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
* @since 5.0
|
||||
*/
|
||||
public class PasswordEncoderFactoriesTests {
|
||||
private PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
|
||||
|
||||
private String rawPassword = "password";
|
||||
|
||||
@Test
|
||||
public void encodeWhenDefaultThenBCryptUsed() {
|
||||
String encodedPassword = this.encoder.encode(this.rawPassword);
|
||||
|
||||
assertThat(encodedPassword).startsWith("{bcrypt}");
|
||||
assertThat(this.encoder.matches(this.rawPassword, encodedPassword)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchesWhenBCryptThenWorks() {
|
||||
String encodedPassword = "{bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG";
|
||||
assertThat(this.encoder.matches(this.rawPassword, encodedPassword)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchesWhenLdapThenWorks() {
|
||||
String encodedPassword = "{ldap}{SSHA}igvD9lOiTXm16dmOw0YWRb9OjK2ThZvdQku2EQ==";
|
||||
assertThat(this.encoder.matches(this.rawPassword, encodedPassword)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchesWhenMd4ThenWorks() {
|
||||
String encodedPassword = "{MD4}{KYp8/QErWyQemYazZQ8UnWWfbGbkYkVC8qMi0duoA84=}152ce09d3261d2b53cac55b2ea4d1c7a";
|
||||
assertThat(this.encoder.matches(this.rawPassword, encodedPassword)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchesWhenMd5ThenWorks() {
|
||||
String encodedPassword = "{MD5}{aRYR+Yp2xSqtgF+vtjH6jNda6M083iEbP+zCFjLt9IA=}905e382a25eed53e22224223b3581092";
|
||||
assertThat(this.encoder.matches(this.rawPassword, encodedPassword)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchesWhenNoopThenWorks() {
|
||||
String encodedPassword = "{noop}password";
|
||||
assertThat(this.encoder.matches(this.rawPassword, encodedPassword)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchesWhenPbkdf2ThenWorks() {
|
||||
String encodedPassword = "{pbkdf2}5d923b44a6d129f3ddf3e3c8d29412723dcbde72445e8ef6bf3b508fbf17fa4ed4d6b99ca763d8dc";
|
||||
assertThat(this.encoder.matches(this.rawPassword, encodedPassword)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchesWhenSCryptThenWorks() {
|
||||
String encodedPassword = "{scrypt}$e0801$8bWJaSu2IKSn9Z9kM+TPXfOc/9bdYSrN1oD9qfVThWEwdRTnO7re7Ei+fUZRJ68k9lTyuTeUp4of4g24hHnazw==$OAOec05+bXxvuu/1qZ6NUR+xQYvYv7BeL1QxwRpY5Pc=";
|
||||
assertThat(this.encoder.matches(this.rawPassword, encodedPassword)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchesWhenSHA1ThenWorks() {
|
||||
String encodedPassword = "{SHA-1}{6581QepZz2qd8jVrT2QYPVtK8DuM2n45dVslmc3UTWc=}4f31573948ddbfb8ac9dd80107dfad13fd8f2454";
|
||||
assertThat(this.encoder.matches(this.rawPassword, encodedPassword)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchesWhenSHA256ThenWorks() {
|
||||
String encodedPassword = "{SHA-256}{UisHp3pFSMqcqrhQsrhR+hspIG0SyMDyDW/XtY+t6nA=}a98efbaf59277bfd1837c33fd4fde67de5bcfd2205bcba0992f6fc32b03a8f88";
|
||||
assertThat(this.encoder.matches(this.rawPassword, encodedPassword)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchesWhenSha256ThenWorks() {
|
||||
String encodedPassword = "{sha256}97cde38028ad898ebc02e690819fa220e88c62e0699403e94fff291cfffaf8410849f27605abcbc0";
|
||||
assertThat(this.encoder.matches(this.rawPassword, encodedPassword)).isTrue();
|
||||
}
|
||||
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.crypto.keygen;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Base64;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
* @since 5.0
|
||||
*/
|
||||
public class Base64StringKeyGeneratorTests {
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void constructorIntWhenLessThan32ThenIllegalArgumentException() {
|
||||
new Base64StringKeyGenerator(31);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void constructorEncoderWhenEncoderNullThenThrowsIllegalArgumentException() {
|
||||
Base64.Encoder encoder = null;
|
||||
new Base64StringKeyGenerator(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void generateKeyWhenDefaultConstructorThen32Bytes() {
|
||||
String result = new Base64StringKeyGenerator().generateKey();
|
||||
assertThat(Base64.getDecoder().decode(result.getBytes())).hasSize(32);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void generateKeyWhenCustomKeySizeThen32Bytes() {
|
||||
int size = 40;
|
||||
String result = new Base64StringKeyGenerator(size).generateKey();
|
||||
assertThat(Base64.getDecoder().decode(result.getBytes())).hasSize(size);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void generateKeyWhenBase64Then32Bytes() {
|
||||
String result = new Base64StringKeyGenerator(Base64.getUrlEncoder()).generateKey();
|
||||
assertThat(Base64.getUrlDecoder().decode(result.getBytes())).hasSize(32);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void generateKeyWhenBase64AndCustomKeySizeThen32Bytes() {
|
||||
int size = 40;
|
||||
String result = new Base64StringKeyGenerator(Base64.getUrlEncoder(), size).generateKey();
|
||||
assertThat(Base64.getUrlDecoder().decode(result.getBytes())).hasSize(size);
|
||||
}
|
||||
}
|
||||
+219
@@ -0,0 +1,219 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.crypto.password;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.fail;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyZeroInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Hashtable;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
* @author Michael Simons
|
||||
* @since 5.0
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class DelegatingPasswordEncoderTests {
|
||||
@Mock
|
||||
private PasswordEncoder bcrypt;
|
||||
|
||||
@Mock
|
||||
private PasswordEncoder noop;
|
||||
|
||||
@Mock
|
||||
private PasswordEncoder invalidId;
|
||||
|
||||
private String bcryptId = "bcrypt";
|
||||
|
||||
private String rawPassword = "password";
|
||||
|
||||
private String encodedPassword = "ENCODED-PASSWORD";
|
||||
|
||||
private String bcryptEncodedPassword = "{bcrypt}" + this.encodedPassword;
|
||||
|
||||
private String noopEncodedPassword = "{noop}" + this.encodedPassword;
|
||||
|
||||
private Map<String, PasswordEncoder> delegates;
|
||||
|
||||
private DelegatingPasswordEncoder passwordEncoder;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
this.delegates = new HashMap<String, PasswordEncoder>();
|
||||
this.delegates.put(this.bcryptId, this.bcrypt);
|
||||
this.delegates.put("noop", this.noop);
|
||||
|
||||
this.passwordEncoder = new DelegatingPasswordEncoder(this.bcryptId, this.delegates);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void constructorWhenIdForEncodeNullThenIllegalArgumentException() {
|
||||
new DelegatingPasswordEncoder(null, this.delegates);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void constructorWhenIdForEncodeDoesNotExistThenIllegalArgumentException() {
|
||||
new DelegatingPasswordEncoder(this.bcryptId + "INVALID", this.delegates);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void setDefaultPasswordEncoderForMatchesWhenNullThenIllegalArgumentException() {
|
||||
this.passwordEncoder.setDefaultPasswordEncoderForMatches(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchesWhenCustomDefaultPasswordEncoderForMatchesThenDelegates() {
|
||||
String encodedPassword = "{unmapped}" + this.rawPassword;
|
||||
this.passwordEncoder.setDefaultPasswordEncoderForMatches(this.invalidId);
|
||||
|
||||
assertThat(this.passwordEncoder.matches(this.rawPassword, encodedPassword)).isFalse();
|
||||
|
||||
verify(this.invalidId).matches(this.rawPassword, encodedPassword);
|
||||
verifyZeroInteractions(this.bcrypt, this.noop);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void encodeWhenValidThenUsesIdForEncode() {
|
||||
when(this.bcrypt.encode(this.rawPassword)).thenReturn(this.encodedPassword);
|
||||
|
||||
assertThat(this.passwordEncoder.encode(this.rawPassword)).isEqualTo(this.bcryptEncodedPassword);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchesWhenBCryptThenDelegatesToBCrypt() {
|
||||
when(this.bcrypt.matches(this.rawPassword, this.encodedPassword)).thenReturn(true);
|
||||
|
||||
assertThat(this.passwordEncoder.matches(this.rawPassword, this.bcryptEncodedPassword)).isTrue();
|
||||
|
||||
verify(this.bcrypt).matches(this.rawPassword, this.encodedPassword);
|
||||
verifyZeroInteractions(this.noop);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchesWhenNoopThenDelegatesToNoop() {
|
||||
when(this.noop.matches(this.rawPassword, this.encodedPassword)).thenReturn(true);
|
||||
|
||||
assertThat(this.passwordEncoder.matches(this.rawPassword, this.noopEncodedPassword)).isTrue();
|
||||
|
||||
verify(this.noop).matches(this.rawPassword, this.encodedPassword);
|
||||
verifyZeroInteractions(this.bcrypt);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchesWhenUnMappedThenIllegalArgumentException() {
|
||||
try {
|
||||
this.passwordEncoder.matches(this.rawPassword, "{unmapped}" + this.rawPassword);
|
||||
fail("Expected Exception");
|
||||
} catch(IllegalArgumentException e) {
|
||||
assertThat(e).hasMessage("There is no PasswordEncoder mapped for the id \"unmapped\"");
|
||||
}
|
||||
|
||||
verifyZeroInteractions(this.bcrypt, this.noop);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchesWhenNoClosingPrefixStringThenIllegalArgumentExcetion() {
|
||||
try {
|
||||
this.passwordEncoder.matches(this.rawPassword, "{bcrypt" + this.rawPassword);
|
||||
fail("Expected Exception");
|
||||
} catch(IllegalArgumentException e) {
|
||||
assertThat(e).hasMessage("There is no PasswordEncoder mapped for the id \"null\"");
|
||||
}
|
||||
|
||||
verifyZeroInteractions(this.bcrypt, this.noop);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchesWhenNoStartingPrefixStringThenFalse() {
|
||||
try {
|
||||
this.passwordEncoder.matches(this.rawPassword, "bcrypt}" + this.rawPassword);
|
||||
fail("Expected Exception");
|
||||
} catch(IllegalArgumentException e) {
|
||||
assertThat(e).hasMessage("There is no PasswordEncoder mapped for the id \"null\"");
|
||||
}
|
||||
|
||||
verifyZeroInteractions(this.bcrypt, this.noop);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchesWhenNoIdStringThenFalse() {
|
||||
try {
|
||||
this.passwordEncoder.matches(this.rawPassword, "{}" + this.rawPassword);
|
||||
fail("Expected Exception");
|
||||
} catch(IllegalArgumentException e) {
|
||||
assertThat(e).hasMessage("There is no PasswordEncoder mapped for the id \"\"");
|
||||
}
|
||||
|
||||
verifyZeroInteractions(this.bcrypt, this.noop);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchesWhenPrefixInMiddleThenFalse() {
|
||||
try {
|
||||
this.passwordEncoder.matches(this.rawPassword, "invalid" + this.bcryptEncodedPassword);
|
||||
fail("Expected Exception");
|
||||
} catch(IllegalArgumentException e) {
|
||||
assertThat(e).hasMessage("There is no PasswordEncoder mapped for the id \"null\"");
|
||||
}
|
||||
|
||||
verifyZeroInteractions(this.bcrypt, this.noop);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchesWhenIdIsNullThenFalse() {
|
||||
this.delegates = new Hashtable<String, PasswordEncoder>(this.delegates);
|
||||
|
||||
DelegatingPasswordEncoder passwordEncoder = new DelegatingPasswordEncoder(this.bcryptId, this.delegates);
|
||||
|
||||
try {
|
||||
passwordEncoder.matches(this.rawPassword, this.rawPassword);
|
||||
fail("Expected Exception");
|
||||
} catch(IllegalArgumentException e) {
|
||||
assertThat(e).hasMessage("There is no PasswordEncoder mapped for the id \"null\"");
|
||||
}
|
||||
|
||||
verifyZeroInteractions(this.bcrypt, this.noop);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchesWhenNullIdThenDelegatesToInvalidId() {
|
||||
this.delegates.put(null, this.invalidId);
|
||||
this.passwordEncoder = new DelegatingPasswordEncoder(this.bcryptId, this.delegates);
|
||||
when(this.invalidId.matches(this.rawPassword, this.encodedPassword)).thenReturn(true);
|
||||
|
||||
assertThat(this.passwordEncoder.matches(this.rawPassword, this.encodedPassword)).isTrue();
|
||||
|
||||
verify(this.invalidId).matches(this.rawPassword, this.encodedPassword);
|
||||
verifyZeroInteractions(this.bcrypt, this.noop);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void matchesWhenRawPasswordNotNullAndEncodedPasswordNullThenThrowsIllegalArgumentException() {
|
||||
this.passwordEncoder.matches(this.rawPassword, null);
|
||||
}
|
||||
}
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.crypto.password;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.security.crypto.keygen.KeyGenerators;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests {@link LdapShaPasswordEncoder}.
|
||||
*
|
||||
* @author Luke Taylor
|
||||
*/
|
||||
public class LdapShaPasswordEncoderTests {
|
||||
// ~ Instance fields
|
||||
// ================================================================================================
|
||||
|
||||
LdapShaPasswordEncoder sha = new LdapShaPasswordEncoder();
|
||||
|
||||
// ~ Methods
|
||||
// ========================================================================================================
|
||||
|
||||
@Test
|
||||
public void invalidPasswordFails() {
|
||||
assertThat(this.sha.matches("wrongpassword", "{SHA}ddSFGmjXYPbZC+NXR2kCzBRjqiE=")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invalidSaltedPasswordFails() {
|
||||
assertThat(this.sha.matches("wrongpassword", "{SSHA}25ro4PKC8jhQZ26jVsozhX/xaP0suHgX")).isFalse();
|
||||
assertThat(this.sha.matches("wrongpassword", "{SSHA}PQy2j+6n5ytA+YlAKkM8Fh4p6u2JxfVd")).isFalse();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test values generated by 'slappasswd -h {SHA} -s boabspasswurd'
|
||||
*/
|
||||
@Test
|
||||
public void validPasswordSucceeds() {
|
||||
this.sha.setForceLowerCasePrefix(false);
|
||||
assertThat(this.sha.matches("boabspasswurd", "{SHA}ddSFGmjXYPbZC+NXR2kCzBRjqiE=")).isTrue();
|
||||
assertThat(this.sha.matches("boabspasswurd", "{sha}ddSFGmjXYPbZC+NXR2kCzBRjqiE=")).isTrue();
|
||||
this.sha.setForceLowerCasePrefix(true);
|
||||
assertThat(this.sha.matches("boabspasswurd", "{SHA}ddSFGmjXYPbZC+NXR2kCzBRjqiE=")).isTrue();
|
||||
assertThat(this.sha.matches("boabspasswurd", "{sha}ddSFGmjXYPbZC+NXR2kCzBRjqiE=")).isTrue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test values generated by 'slappasswd -s boabspasswurd'
|
||||
*/
|
||||
@Test
|
||||
public void validSaltedPasswordSucceeds() {
|
||||
this.sha.setForceLowerCasePrefix(false);
|
||||
assertThat(this.sha.matches("boabspasswurd", "{SSHA}25ro4PKC8jhQZ26jVsozhX/xaP0suHgX")).isTrue();
|
||||
assertThat(this.sha.matches("boabspasswurd", "{ssha}PQy2j+6n5ytA+YlAKkM8Fh4p6u2JxfVd")).isTrue();
|
||||
this.sha.setForceLowerCasePrefix(true);
|
||||
assertThat(this.sha.matches("boabspasswurd", "{SSHA}25ro4PKC8jhQZ26jVsozhX/xaP0suHgX")).isTrue();
|
||||
assertThat(this.sha.matches("boabspasswurd", "{ssha}PQy2j+6n5ytA+YlAKkM8Fh4p6u2JxfVd")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
// SEC-1031
|
||||
public void fullLengthOfHashIsUsedInComparison() throws Exception {
|
||||
assertThat(this.sha.matches("boabspasswurd", "{SSHA}25ro4PKC8jhQZ26jVsozhX/xaP0suHgX")).isTrue();
|
||||
// Change the first hash character from '2' to '3'
|
||||
assertThat(this.sha.matches("boabspasswurd", "{SSHA}35ro4PKC8jhQZ26jVsozhX/xaP0suHgX")).isFalse();
|
||||
// Change the last hash character from 'X' to 'Y'
|
||||
assertThat(this.sha.matches("boabspasswurd", "{SSHA}25ro4PKC8jhQZ26jVsozhX/xaP0suHgY")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void correctPrefixCaseIsUsed() {
|
||||
this.sha.setForceLowerCasePrefix(false);
|
||||
assertThat(this.sha.encode("somepassword").startsWith("{SSHA}"));
|
||||
|
||||
this.sha.setForceLowerCasePrefix(true);
|
||||
assertThat(this.sha.encode("somepassword").startsWith("{ssha}"));
|
||||
|
||||
this.sha = new LdapShaPasswordEncoder(KeyGenerators.shared(0));
|
||||
this.sha.setForceLowerCasePrefix(false);
|
||||
assertThat(this.sha.encode("somepassword").startsWith("{SHA}"));
|
||||
|
||||
this.sha.setForceLowerCasePrefix(true);
|
||||
assertThat(this.sha.encode("somepassword").startsWith("{SSHA}"));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void invalidPrefixIsRejected() {
|
||||
this.sha.matches("somepassword", "{MD9}xxxxxxxxxx");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void malformedPrefixIsRejected() {
|
||||
// No right brace
|
||||
this.sha.matches("somepassword", "{SSHA25ro4PKC8jhQZ26jVsozhX/xaP0suHgX");
|
||||
}
|
||||
}
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.crypto.password;
|
||||
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
|
||||
public class Md4PasswordEncoderTests {
|
||||
|
||||
@Test
|
||||
public void testEncodeUnsaltedPassword() {
|
||||
Md4PasswordEncoder md4 = new Md4PasswordEncoder();
|
||||
md4.setEncodeHashAsBase64(true);
|
||||
assertThat(md4.matches("ww_uni123", "8zobtq72iAt0W6KNqavGwg==")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncodeSaltedPassword() {
|
||||
Md4PasswordEncoder md4 = new Md4PasswordEncoder();
|
||||
md4.setEncodeHashAsBase64(true);
|
||||
assertThat(md4.matches("ww_uni123", "{Alan K Stewart}ZplT6P5Kv6Rlu6W4FIoYNA==")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncodeNullPassword() {
|
||||
Md4PasswordEncoder md4 = new Md4PasswordEncoder();
|
||||
md4.setEncodeHashAsBase64(true);
|
||||
assertThat(md4.matches(null, "MdbP4NFq6TG3PFnX4MCJwA==")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncodeEmptyPassword() {
|
||||
Md4PasswordEncoder md4 = new Md4PasswordEncoder();
|
||||
md4.setEncodeHashAsBase64(true);
|
||||
assertThat(md4.matches(null, "MdbP4NFq6TG3PFnX4MCJwA==")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNonAsciiPasswordHasCorrectHash() {
|
||||
Md4PasswordEncoder md4 = new Md4PasswordEncoder();
|
||||
assertThat(md4.matches("\u4F60\u597d", "a7f1196539fd1f85f754ffd185b16e6e")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncodedMatches() {
|
||||
String rawPassword = "password";
|
||||
Md4PasswordEncoder md4 = new Md4PasswordEncoder();
|
||||
String encodedPassword = md4.encode(rawPassword);
|
||||
|
||||
assertThat(md4.matches(rawPassword, encodedPassword)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void javadocWhenHasSaltThenMatches() {
|
||||
Md4PasswordEncoder encoder = new Md4PasswordEncoder();
|
||||
assertThat(encoder.matches("password", "{thisissalt}6cc7924dad12ade79dfb99e424f25260"));
|
||||
}
|
||||
}
|
||||
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.crypto.password;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* TestCase for Md5PasswordEncoder.
|
||||
* </p>
|
||||
*
|
||||
* @author colin sampaleanu
|
||||
* @author Ben Alex
|
||||
* @author Ray Krueger
|
||||
* @author Luke Taylor
|
||||
*/
|
||||
public class MessageDigestPasswordEncoderTests {
|
||||
// ~ Methods
|
||||
// ========================================================================================================
|
||||
|
||||
@Test
|
||||
public void md5BasicFunctionality() {
|
||||
MessageDigestPasswordEncoder pe = new MessageDigestPasswordEncoder("MD5");
|
||||
String raw = "abc123";
|
||||
assertThat(pe.matches( raw, "{THIS_IS_A_SALT}a68aafd90299d0b137de28fb4bb68573")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void md5NonAsciiPasswordHasCorrectHash() throws Exception {
|
||||
MessageDigestPasswordEncoder pe = new MessageDigestPasswordEncoder("MD5");
|
||||
// $ echo -n "??" | md5
|
||||
// 7eca689f0d3389d9dea66ae112e5cfd7
|
||||
assertThat(pe.matches("\u4F60\u597d", "7eca689f0d3389d9dea66ae112e5cfd7")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void md5Base64() throws Exception {
|
||||
MessageDigestPasswordEncoder pe = new MessageDigestPasswordEncoder("MD5");
|
||||
pe.setEncodeHashAsBase64(true);
|
||||
assertThat(pe.matches("abc123", "{THIS_IS_A_SALT}poqv2QKZ0LE33ij7S7aFcw==")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void md5StretchFactorIsProcessedCorrectly() throws Exception {
|
||||
MessageDigestPasswordEncoder pe = new MessageDigestPasswordEncoder("MD5");
|
||||
pe.setIterations(2);
|
||||
// Calculate value using:
|
||||
// echo -n password{salt} | openssl md5 -binary | openssl md5
|
||||
assertThat(pe.matches("password", "{salt}eb753fb0c370582b4ee01b30f304b9fc")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void md5MatchesWhenNullSalt() {
|
||||
MessageDigestPasswordEncoder pe = new MessageDigestPasswordEncoder("MD5");
|
||||
assertThat(pe.matches("password", "5f4dcc3b5aa765d61d8327deb882cf99")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void md5MatchesWhenEmptySalt() {
|
||||
MessageDigestPasswordEncoder pe = new MessageDigestPasswordEncoder("MD5");
|
||||
assertThat(pe.matches("password", "{}f1026a66095fc2058c1f8771ed05d6da")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void md5MatchesWhenHasSalt() {
|
||||
MessageDigestPasswordEncoder pe = new MessageDigestPasswordEncoder("MD5");
|
||||
assertThat(pe.matches("password", "{salt}ce421738b1c5540836bdc8ff707f1572")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void md5EncodeThenMatches() {
|
||||
String rawPassword = "password";
|
||||
MessageDigestPasswordEncoder pe = new MessageDigestPasswordEncoder("MD5");
|
||||
String encode = pe.encode(rawPassword);
|
||||
assertThat(pe.matches(rawPassword, encode)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBasicFunctionality() {
|
||||
MessageDigestPasswordEncoder pe = new MessageDigestPasswordEncoder("SHA-1");
|
||||
String raw = "abc123";
|
||||
assertThat(pe.matches(raw, "{THIS_IS_A_SALT}b2f50ffcbd3407fe9415c062d55f54731f340d32"));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBase64() throws Exception {
|
||||
MessageDigestPasswordEncoder pe = new MessageDigestPasswordEncoder("SHA-1");
|
||||
pe.setEncodeHashAsBase64(true);
|
||||
String raw = "abc123";
|
||||
assertThat(pe.matches(raw, "{THIS_IS_A_SALT}b2f50ffcbd3407fe9415c062d55f54731f340d32"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test256() throws Exception {
|
||||
MessageDigestPasswordEncoder pe = new MessageDigestPasswordEncoder("SHA-1");
|
||||
String raw = "abc123";
|
||||
assertThat(pe.matches(raw, "{THIS_IS_A_SALT}4b79b7de23eb23b78cc5ede227d532b8a51f89b2ec166f808af76b0dbedc47d7"));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void testInvalidStrength() throws Exception {
|
||||
new MessageDigestPasswordEncoder("SHA-666");
|
||||
}
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.security.crypto.password;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
*/
|
||||
public class PasswordEncoderUtilsTests {
|
||||
|
||||
@Test
|
||||
public void equalsWhenDifferentLengthThenFalse() {
|
||||
assertThat(PasswordEncoderUtils.equals("abc", "a")).isFalse();
|
||||
assertThat(PasswordEncoderUtils.equals("a", "abc")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void equalsWhenNullAndNotEmtpyThenFalse() {
|
||||
assertThat(PasswordEncoderUtils.equals(null, "a")).isFalse();
|
||||
assertThat(PasswordEncoderUtils.equals("a", null)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void equalsWhenNullAndNullThenTrue() {
|
||||
assertThat(PasswordEncoderUtils.equals(null, null)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void equalsWhenNullAndEmptyThenFalse() {
|
||||
assertThat(PasswordEncoderUtils.equals(null, "")).isFalse();
|
||||
assertThat(PasswordEncoderUtils.equals("", null)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void equalsWhenNotEmptyAndEmptyThenFalse() {
|
||||
assertThat(PasswordEncoderUtils.equals("abc", "")).isFalse();
|
||||
assertThat(PasswordEncoderUtils.equals("", "abc")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void equalsWhenEmtpyAndEmptyThenTrue() {
|
||||
assertThat(PasswordEncoderUtils.equals("", "")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void equalsWhenDifferentCaseThenFalse() {
|
||||
assertThat(PasswordEncoderUtils.equals("aBc", "abc")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void equalsWhenSameThenTrue() {
|
||||
assertThat(PasswordEncoderUtils.equals("abcdef", "abcdef")).isTrue();
|
||||
}
|
||||
}
|
||||
+37
-1
@@ -75,6 +75,42 @@ public class Pbkdf2PasswordEncoderTests {
|
||||
assertThat(fixedHex).isEqualTo(encodedPassword);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void encodeAndMatchWhenBase64ThenSuccess() {
|
||||
this.encoder.setEncodeHashAsBase64(true);
|
||||
|
||||
String rawPassword = "password";
|
||||
String encodedPassword = this.encoder.encode(rawPassword);
|
||||
assertThat(this.encoder.matches(rawPassword, encodedPassword)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchWhenBase64ThenSuccess() {
|
||||
this.encoder.setEncodeHashAsBase64(true);
|
||||
String rawPassword = "password";
|
||||
String encodedPassword = "3FOwOMcDgxP+z1x/sv184LFY2WVD+ZGMgYP3LPOSmCcDmk1XPYvcCQ==";
|
||||
|
||||
assertThat(this.encoder.matches(rawPassword, encodedPassword)).isTrue();
|
||||
java.util.Base64.getDecoder().decode(encodedPassword); // validate can decode as Base64
|
||||
}
|
||||
|
||||
@Test
|
||||
public void encodeAndMatchWhenSha256ThenSuccess() {
|
||||
this.encoder.setAlgorithm(Pbkdf2PasswordEncoder.SecretKeyFactoryAlgorithm.PBKDF2WithHmacSHA256);
|
||||
|
||||
String rawPassword = "password";
|
||||
String encodedPassword = this.encoder.encode(rawPassword);
|
||||
assertThat(this.encoder.matches(rawPassword, encodedPassword)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchWhenSha256ThenSuccess() {
|
||||
this.encoder.setAlgorithm(Pbkdf2PasswordEncoder.SecretKeyFactoryAlgorithm.PBKDF2WithHmacSHA256);
|
||||
|
||||
String rawPassword = "password";
|
||||
String encodedPassword = "821447f994e2b04c5014e31fa9fca4ae1cc9f2188c4ed53d3ddb5ba7980982b51a0ecebfc0b81a79";
|
||||
assertThat(this.encoder.matches(rawPassword, encodedPassword)).isTrue();
|
||||
}
|
||||
/**
|
||||
* Used to find the iteration count that takes .5 seconds.
|
||||
*/
|
||||
@@ -105,4 +141,4 @@ public class Pbkdf2PasswordEncoderTests {
|
||||
}
|
||||
System.out.println("Iterations " + iterations);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
<configuration>
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<logger name="org.springframework.security" level="${sec.log.level:-WARN}"/>
|
||||
|
||||
|
||||
<root level="${root.level:-WARN}">
|
||||
<appender-ref ref="STDOUT" />
|
||||
</root>
|
||||
|
||||
</configuration>
|
||||
Reference in New Issue
Block a user