SEC-1689: Moved core codec code into crypto package and removed existing duplication (Hex encoding etc). Refactoring of crypto code to use CharSequence for where possible instead of String.
This commit is contained in:
parent
3a3b2df1c5
commit
e470eaa41d
|
@ -19,7 +19,7 @@ package org.springframework.security.authentication.encoding;
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
|
|
||||||
import org.springframework.security.core.codec.Base64;
|
import org.springframework.security.crypto.codec.Base64;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -16,8 +16,8 @@ package org.springframework.security.authentication.encoding;
|
||||||
|
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
|
|
||||||
import org.springframework.security.core.codec.Base64;
|
import org.springframework.security.crypto.codec.Base64;
|
||||||
import org.springframework.security.core.codec.Hex;
|
import org.springframework.security.crypto.codec.Hex;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* MD4 implementation of PasswordEncoder.
|
* MD4 implementation of PasswordEncoder.
|
||||||
|
|
|
@ -4,8 +4,8 @@ import java.io.UnsupportedEncodingException;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
|
||||||
import org.springframework.security.core.codec.Base64;
|
import org.springframework.security.crypto.codec.Base64;
|
||||||
import org.springframework.security.core.codec.Hex;
|
import org.springframework.security.crypto.codec.Hex;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -5,8 +5,8 @@ import java.security.SecureRandom;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
import org.springframework.beans.factory.InitializingBean;
|
import org.springframework.beans.factory.InitializingBean;
|
||||||
import org.springframework.security.core.codec.Base64;
|
import org.springframework.security.crypto.codec.Base64;
|
||||||
import org.springframework.security.core.codec.Hex;
|
import org.springframework.security.crypto.codec.Hex;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ package org.springframework.security.core.token;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
|
||||||
import org.springframework.security.core.codec.Hex;
|
import org.springframework.security.crypto.codec.Hex;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides SHA512 digest methods.
|
* Provides SHA512 digest methods.
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package org.springframework.security.core.codec;
|
package org.springframework.security.crypto.codec;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -33,14 +33,14 @@ public final class Base64 {
|
||||||
* or at the very least should not be called Base64 without also specifying that is
|
* or at the very least should not be called Base64 without also specifying that is
|
||||||
* was encoded using the URL- and Filename-safe dialect.
|
* was encoded using the URL- and Filename-safe dialect.
|
||||||
*/
|
*/
|
||||||
public final static int URL_SAFE = 16;
|
public final static int URL_SAFE = 16;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encode using the special "ordered" dialect of Base64 described here:
|
* Encode using the special "ordered" dialect of Base64 described here:
|
||||||
* <a href="http://www.faqs.org/qa/rfcc-1940.html">http://www.faqs.org/qa/rfcc-1940.html</a>.
|
* <a href="http://www.faqs.org/qa/rfcc-1940.html">http://www.faqs.org/qa/rfcc-1940.html</a>.
|
||||||
*/
|
*/
|
||||||
public final static int ORDERED = 32;
|
public final static int ORDERED = 32;
|
||||||
|
|
||||||
|
|
||||||
/** Maximum line length (76) of Base64 output. */
|
/** Maximum line length (76) of Base64 output. */
|
|
@ -1,4 +1,4 @@
|
||||||
package org.springframework.security.core.codec;
|
package org.springframework.security.crypto.codec;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hex data encoder. Converts byte arrays (such as those obtained from message digests)
|
* Hex data encoder. Converts byte arrays (such as those obtained from message digests)
|
||||||
|
@ -30,7 +30,25 @@ public final class Hex {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// public static byte[] decode(char[] hex) {
|
public static byte[] decode(CharSequence s) {
|
||||||
//
|
int nChars = s.length();
|
||||||
// }
|
|
||||||
|
if (nChars % 2 != 0) {
|
||||||
|
throw new IllegalArgumentException("Hex-encoded string must have an even number of characters");
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] result = new byte[nChars / 2];
|
||||||
|
|
||||||
|
for (int i = 0; i < nChars; i += 2) {
|
||||||
|
int msb = Character.digit(s.charAt(i), 16);
|
||||||
|
int lsb = Character.digit(s.charAt(i+1), 16);
|
||||||
|
|
||||||
|
if (msb < 0 || lsb < 0) {
|
||||||
|
throw new IllegalArgumentException("Non-hex character in input: " + s);
|
||||||
|
}
|
||||||
|
result[i / 2] = (byte) ((msb << 4) | lsb);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
package org.springframework.security.crypto.codec;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.CharBuffer;
|
||||||
|
import java.nio.charset.CharacterCodingException;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UTF-8 Charset encoder/decoder.
|
||||||
|
* <p>
|
||||||
|
* For internal use only.
|
||||||
|
*
|
||||||
|
* @author Luke Taylor
|
||||||
|
*/
|
||||||
|
public final class Utf8 {
|
||||||
|
private static final Charset CHARSET = Charset.forName("UTF-8");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the bytes of the String in UTF-8 encoded form.
|
||||||
|
*/
|
||||||
|
public static byte[] encode(CharSequence string) {
|
||||||
|
try {
|
||||||
|
return CHARSET.newEncoder().encode(CharBuffer.wrap(string)).array();
|
||||||
|
} catch (CharacterCodingException e) {
|
||||||
|
throw new IllegalArgumentException("Encoding failed", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode the bytes in UTF-8 form into a String.
|
||||||
|
*/
|
||||||
|
public static String decode(byte[] bytes) {
|
||||||
|
try {
|
||||||
|
return new String(CHARSET.newDecoder().decode(ByteBuffer.wrap(bytes)).array());
|
||||||
|
} catch (CharacterCodingException e) {
|
||||||
|
throw new IllegalArgumentException("Encoding failed", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
/**
|
/**
|
||||||
* Internal codec classes. Only intended for use within the framework.
|
* Internal codec classes. Only intended for use within the framework.
|
||||||
*/
|
*/
|
||||||
package org.springframework.security.core.codec;
|
package org.springframework.security.crypto.codec;
|
|
@ -15,10 +15,10 @@
|
||||||
*/
|
*/
|
||||||
package org.springframework.security.crypto.encrypt;
|
package org.springframework.security.crypto.encrypt;
|
||||||
|
|
||||||
import static org.springframework.security.crypto.util.CipherUtils.doFinal;
|
import static org.springframework.security.crypto.encrypt.CipherUtils.doFinal;
|
||||||
import static org.springframework.security.crypto.util.CipherUtils.initCipher;
|
import static org.springframework.security.crypto.encrypt.CipherUtils.initCipher;
|
||||||
import static org.springframework.security.crypto.util.CipherUtils.newCipher;
|
import static org.springframework.security.crypto.encrypt.CipherUtils.newCipher;
|
||||||
import static org.springframework.security.crypto.util.CipherUtils.newSecretKey;
|
import static org.springframework.security.crypto.encrypt.CipherUtils.newSecretKey;
|
||||||
import static org.springframework.security.crypto.util.EncodingUtils.concatenate;
|
import static org.springframework.security.crypto.util.EncodingUtils.concatenate;
|
||||||
import static org.springframework.security.crypto.util.EncodingUtils.subArray;
|
import static org.springframework.security.crypto.util.EncodingUtils.subArray;
|
||||||
|
|
||||||
|
@ -28,11 +28,12 @@ import javax.crypto.spec.IvParameterSpec;
|
||||||
import javax.crypto.spec.PBEKeySpec;
|
import javax.crypto.spec.PBEKeySpec;
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
|
||||||
|
import org.springframework.security.crypto.codec.Hex;
|
||||||
import org.springframework.security.crypto.keygen.BytesKeyGenerator;
|
import org.springframework.security.crypto.keygen.BytesKeyGenerator;
|
||||||
import org.springframework.security.crypto.util.EncodingUtils;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encryptor that uses 256-bit AES encryption.
|
* Encryptor that uses 256-bit AES encryption.
|
||||||
|
*
|
||||||
* @author Keith Donald
|
* @author Keith Donald
|
||||||
*/
|
*/
|
||||||
final class AesBytesEncryptor implements BytesEncryptor {
|
final class AesBytesEncryptor implements BytesEncryptor {
|
||||||
|
@ -45,8 +46,8 @@ final class AesBytesEncryptor implements BytesEncryptor {
|
||||||
|
|
||||||
private final BytesKeyGenerator ivGenerator;
|
private final BytesKeyGenerator ivGenerator;
|
||||||
|
|
||||||
public AesBytesEncryptor(String password, String salt, BytesKeyGenerator ivGenerator) {
|
public AesBytesEncryptor(String password, CharSequence salt, BytesKeyGenerator ivGenerator) {
|
||||||
PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), EncodingUtils.hexDecode(salt), 1024, 256);
|
PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), Hex.decode(salt), 1024, 256);
|
||||||
SecretKey secretKey = newSecretKey("PBKDF2WithHmacSHA1", keySpec);
|
SecretKey secretKey = newSecretKey("PBKDF2WithHmacSHA1", keySpec);
|
||||||
this.secretKey = new SecretKeySpec(secretKey.getEncoded(), "AES");
|
this.secretKey = new SecretKeySpec(secretKey.getEncoded(), "AES");
|
||||||
encryptor = newCipher(AES_ALGORITHM);
|
encryptor = newCipher(AES_ALGORITHM);
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package org.springframework.security.crypto.util;
|
package org.springframework.security.crypto.encrypt;
|
||||||
|
|
||||||
import java.security.InvalidAlgorithmParameterException;
|
import java.security.InvalidAlgorithmParameterException;
|
||||||
import java.security.InvalidKeyException;
|
import java.security.InvalidKeyException;
|
||||||
|
@ -35,7 +35,7 @@ import javax.crypto.spec.PBEParameterSpec;
|
||||||
* Static helper for working with the Cipher API.
|
* Static helper for working with the Cipher API.
|
||||||
* @author Keith Donald
|
* @author Keith Donald
|
||||||
*/
|
*/
|
||||||
public class CipherUtils {
|
class CipherUtils {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates a SecretKey.
|
* Generates a SecretKey.
|
|
@ -20,6 +20,7 @@ import org.springframework.security.crypto.keygen.KeyGenerators;
|
||||||
/**
|
/**
|
||||||
* Factory for commonly used encryptors.
|
* Factory for commonly used encryptors.
|
||||||
* Defines the public API for constructing {@link BytesEncryptor} and {@link TextEncryptor} implementations.
|
* Defines the public API for constructing {@link BytesEncryptor} and {@link TextEncryptor} implementations.
|
||||||
|
*
|
||||||
* @author Keith Donald
|
* @author Keith Donald
|
||||||
*/
|
*/
|
||||||
public class Encryptors {
|
public class Encryptors {
|
||||||
|
@ -31,19 +32,21 @@ public class Encryptors {
|
||||||
* The provided salt is expected to be hex-encoded; it should be random and at least 8 bytes in length.
|
* The provided salt is expected to be hex-encoded; it should be random and at least 8 bytes in length.
|
||||||
* Also applies a random 16 byte initialization vector to ensure each encrypted message will be unique.
|
* Also applies a random 16 byte initialization vector to ensure each encrypted message will be unique.
|
||||||
* Requires Java 6.
|
* Requires Java 6.
|
||||||
|
*
|
||||||
* @param password the password used to generate the encryptor's secret key; should not be shared
|
* @param password the password used to generate the encryptor's secret key; should not be shared
|
||||||
* @param salt an hex-encoded, random, site-global salt value to use to generate the key
|
* @param salt a hex-encoded, random, site-global salt value to use to generate the key
|
||||||
*/
|
*/
|
||||||
public static BytesEncryptor standard(String password, String salt) {
|
public static BytesEncryptor standard(CharSequence password, CharSequence salt) {
|
||||||
return new AesBytesEncryptor(password, password, KeyGenerators.secureRandom(16));
|
return new AesBytesEncryptor(password.toString(), salt, KeyGenerators.secureRandom(16));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a text encryptor that uses standard password-based encryption.
|
* Creates a text encryptor that uses standard password-based encryption.
|
||||||
* Encrypted text is hex-encoded.
|
* Encrypted text is hex-encoded.
|
||||||
|
*
|
||||||
* @param password the password used to generate the encryptor's secret key; should not be shared
|
* @param password the password used to generate the encryptor's secret key; should not be shared
|
||||||
*/
|
*/
|
||||||
public static TextEncryptor text(String password, String salt) {
|
public static TextEncryptor text(CharSequence password, CharSequence salt) {
|
||||||
return new HexEncodingTextEncryptor(standard(password, salt));
|
return new HexEncodingTextEncryptor(standard(password, salt));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,11 +55,12 @@ public class Encryptors {
|
||||||
* Uses a shared, or constant 16 byte initialization vector so encrypting the same data results in the same encryption result.
|
* Uses a shared, or constant 16 byte initialization vector so encrypting the same data results in the same encryption result.
|
||||||
* This is done to allow encrypted data to be queried against.
|
* This is done to allow encrypted data to be queried against.
|
||||||
* Encrypted text is hex-encoded.
|
* Encrypted text is hex-encoded.
|
||||||
|
*
|
||||||
* @param password the password used to generate the encryptor's secret key; should not be shared
|
* @param password the password used to generate the encryptor's secret key; should not be shared
|
||||||
* @param salt an hex-encoded, random, site-global salt value to use to generate the secret key
|
* @param salt a hex-encoded, random, site-global salt value to use to generate the secret key
|
||||||
*/
|
*/
|
||||||
public static TextEncryptor queryableText(String password, String salt) {
|
public static TextEncryptor queryableText(CharSequence password, CharSequence salt) {
|
||||||
return new HexEncodingTextEncryptor(new AesBytesEncryptor(password, salt, KeyGenerators.shared(16)));
|
return new HexEncodingTextEncryptor(new AesBytesEncryptor(password.toString(), salt, KeyGenerators.shared(16)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -15,10 +15,8 @@
|
||||||
*/
|
*/
|
||||||
package org.springframework.security.crypto.encrypt;
|
package org.springframework.security.crypto.encrypt;
|
||||||
|
|
||||||
import static org.springframework.security.crypto.util.EncodingUtils.hexDecode;
|
import org.springframework.security.crypto.codec.Hex;
|
||||||
import static org.springframework.security.crypto.util.EncodingUtils.hexEncode;
|
import org.springframework.security.crypto.codec.Utf8;
|
||||||
import static org.springframework.security.crypto.util.EncodingUtils.utf8Decode;
|
|
||||||
import static org.springframework.security.crypto.util.EncodingUtils.utf8Encode;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delegates to an {@link BytesEncryptor} to encrypt text strings.
|
* Delegates to an {@link BytesEncryptor} to encrypt text strings.
|
||||||
|
@ -35,11 +33,11 @@ final class HexEncodingTextEncryptor implements TextEncryptor {
|
||||||
}
|
}
|
||||||
|
|
||||||
public String encrypt(String text) {
|
public String encrypt(String text) {
|
||||||
return hexEncode(encryptor.encrypt(utf8Encode(text)));
|
return new String(Hex.encode(encryptor.encrypt(Utf8.encode(text))));
|
||||||
}
|
}
|
||||||
|
|
||||||
public String decrypt(String encryptedText) {
|
public String decrypt(String encryptedText) {
|
||||||
return utf8Decode(encryptor.decrypt(hexDecode(encryptedText)));
|
return Utf8.decode(encryptor.decrypt(Hex.decode(encryptedText)));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ package org.springframework.security.crypto.encrypt;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Service interface for symmetric encryption of text strings.
|
* Service interface for symmetric encryption of text strings.
|
||||||
|
*
|
||||||
* @author Keith Donald
|
* @author Keith Donald
|
||||||
*/
|
*/
|
||||||
public interface TextEncryptor {
|
public interface TextEncryptor {
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
*/
|
*/
|
||||||
package org.springframework.security.crypto.keygen;
|
package org.springframework.security.crypto.keygen;
|
||||||
|
|
||||||
import static org.springframework.security.crypto.util.EncodingUtils.hexEncode;
|
import org.springframework.security.crypto.codec.Hex;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A StringKeyGenerator that generates hex-encoded String keys.
|
* A StringKeyGenerator that generates hex-encoded String keys.
|
||||||
|
@ -31,7 +31,7 @@ final class HexEncodingStringKeyGenerator implements StringKeyGenerator {
|
||||||
}
|
}
|
||||||
|
|
||||||
public String generateKey() {
|
public String generateKey() {
|
||||||
return hexEncode(keyGenerator.generateKey());
|
return new String(Hex.encode(keyGenerator.generateKey()));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -13,7 +13,7 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package org.springframework.security.crypto.util;
|
package org.springframework.security.crypto.password;
|
||||||
|
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
@ -24,7 +24,7 @@ import java.security.NoSuchProviderException;
|
||||||
* Performs 1024 iterations of the hashing algorithm per digest to aid in protecting against brute force attacks.
|
* Performs 1024 iterations of the hashing algorithm per digest to aid in protecting against brute force attacks.
|
||||||
* @author Keith Donald
|
* @author Keith Donald
|
||||||
*/
|
*/
|
||||||
public class Digester {
|
class Digester {
|
||||||
|
|
||||||
private final MessageDigest messageDigest;
|
private final MessageDigest messageDigest;
|
||||||
|
|
|
@ -18,16 +18,17 @@ package org.springframework.security.crypto.password;
|
||||||
/**
|
/**
|
||||||
* A password encoder that does nothing.
|
* A password encoder that does nothing.
|
||||||
* Useful for testing where working with plain text passwords may be preferred.
|
* Useful for testing where working with plain text passwords may be preferred.
|
||||||
|
*
|
||||||
* @author Keith Donald
|
* @author Keith Donald
|
||||||
*/
|
*/
|
||||||
public final class NoOpPasswordEncoder implements PasswordEncoder {
|
public final class NoOpPasswordEncoder implements PasswordEncoder {
|
||||||
|
|
||||||
public String encode(String rawPassword) {
|
public String encode(CharSequence rawPassword) {
|
||||||
return rawPassword;
|
return rawPassword.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean matches(String rawPassword, String encodedPassword) {
|
public boolean matches(CharSequence rawPassword, String encodedPassword) {
|
||||||
return rawPassword.equals(encodedPassword);
|
return rawPassword.toString().equals(encodedPassword);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -40,7 +41,6 @@ public final class NoOpPasswordEncoder implements PasswordEncoder {
|
||||||
private static final PasswordEncoder INSTANCE = new NoOpPasswordEncoder();
|
private static final PasswordEncoder INSTANCE = new NoOpPasswordEncoder();
|
||||||
|
|
||||||
private NoOpPasswordEncoder() {
|
private NoOpPasswordEncoder() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,18 +23,20 @@ public interface PasswordEncoder {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encode the raw password.
|
* Encode the raw password.
|
||||||
* Generally, a good encoding algorithm applies a SHA-1 or greater hash combined with a 8-byte or greater randomly generated salt.
|
* Generally, a good encoding algorithm applies a SHA-1 or greater hash combined with an 8-byte or greater randomly
|
||||||
|
* generated salt.
|
||||||
*/
|
*/
|
||||||
String encode(String rawPassword);
|
String encode(CharSequence rawPassword);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Verify the encoded password obtained from storage matches the submitted raw password after it too is encoded.
|
* Verify the encoded password obtained from storage matches the submitted raw password after it too is encoded.
|
||||||
* Returns true if the passwords match, false if they do not.
|
* Returns true if the passwords match, false if they do not.
|
||||||
* The stored password itself is never decoded.
|
* The stored password itself is never decoded.
|
||||||
|
*
|
||||||
* @param rawPassword the raw password to encode and match
|
* @param rawPassword the raw password to encode and match
|
||||||
* @param encodedPassword the encoded password from storage to compare with
|
* @param encodedPassword the encoded password from storage to compare with
|
||||||
* @return true if the raw password, after encoding, matches the encoded password from storage
|
* @return true if the raw password, after encoding, matches the encoded password from storage
|
||||||
*/
|
*/
|
||||||
boolean matches(String rawPassword, String encodedPassword);
|
boolean matches(CharSequence rawPassword, String encodedPassword);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,17 +16,19 @@
|
||||||
package org.springframework.security.crypto.password;
|
package org.springframework.security.crypto.password;
|
||||||
|
|
||||||
import static org.springframework.security.crypto.util.EncodingUtils.concatenate;
|
import static org.springframework.security.crypto.util.EncodingUtils.concatenate;
|
||||||
import static org.springframework.security.crypto.util.EncodingUtils.hexDecode;
|
|
||||||
import static org.springframework.security.crypto.util.EncodingUtils.hexEncode;
|
|
||||||
import static org.springframework.security.crypto.util.EncodingUtils.subArray;
|
import static org.springframework.security.crypto.util.EncodingUtils.subArray;
|
||||||
import static org.springframework.security.crypto.util.EncodingUtils.utf8Encode;
|
|
||||||
|
|
||||||
|
import org.springframework.security.crypto.codec.Hex;
|
||||||
|
import org.springframework.security.crypto.codec.Utf8;
|
||||||
import org.springframework.security.crypto.keygen.BytesKeyGenerator;
|
import org.springframework.security.crypto.keygen.BytesKeyGenerator;
|
||||||
import org.springframework.security.crypto.keygen.KeyGenerators;
|
import org.springframework.security.crypto.keygen.KeyGenerators;
|
||||||
import org.springframework.security.crypto.util.Digester;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A standard PasswordEncoder implementation that uses SHA-256 1024 iteration hashing with 8-byte random salting.
|
* 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.
|
||||||
|
* <p>
|
||||||
|
* The digest algorithm is invoked on the concatenated bytes of the salt, secret and password.
|
||||||
|
*
|
||||||
* @author Keith Donald
|
* @author Keith Donald
|
||||||
*/
|
*/
|
||||||
public final class StandardPasswordEncoder implements PasswordEncoder {
|
public final class StandardPasswordEncoder implements PasswordEncoder {
|
||||||
|
@ -41,15 +43,15 @@ public final class StandardPasswordEncoder implements PasswordEncoder {
|
||||||
* Constructs a standard password encoder.
|
* Constructs a standard password encoder.
|
||||||
* @param secret the secret key used in the encoding process (should not be shared)
|
* @param secret the secret key used in the encoding process (should not be shared)
|
||||||
*/
|
*/
|
||||||
public StandardPasswordEncoder(String secret) {
|
public StandardPasswordEncoder(CharSequence secret) {
|
||||||
this("SHA-256", "SUN", secret);
|
this("SHA-256", "SUN", secret);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String encode(String rawPassword) {
|
public String encode(CharSequence rawPassword) {
|
||||||
return encode(rawPassword, saltGenerator.generateKey());
|
return encode(rawPassword, saltGenerator.generateKey());
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean matches(String rawPassword, String encodedPassword) {
|
public boolean matches(CharSequence rawPassword, String encodedPassword) {
|
||||||
byte[] digested = decode(encodedPassword);
|
byte[] digested = decode(encodedPassword);
|
||||||
byte[] salt = subArray(digested, 0, saltGenerator.getKeyLength());
|
byte[] salt = subArray(digested, 0, saltGenerator.getKeyLength());
|
||||||
return matches(digested, digest(rawPassword, salt));
|
return matches(digested, digest(rawPassword, salt));
|
||||||
|
@ -57,31 +59,28 @@ public final class StandardPasswordEncoder implements PasswordEncoder {
|
||||||
|
|
||||||
// internal helpers
|
// internal helpers
|
||||||
|
|
||||||
private StandardPasswordEncoder(String algorithm, String provider, String secret) {
|
private StandardPasswordEncoder(String algorithm, String provider, CharSequence secret) {
|
||||||
this.digester = new Digester(algorithm, provider);
|
this.digester = new Digester(algorithm, provider);
|
||||||
this.secret = utf8Encode(secret);
|
this.secret = Utf8.encode(secret);
|
||||||
this.saltGenerator = KeyGenerators.secureRandom();
|
this.saltGenerator = KeyGenerators.secureRandom();
|
||||||
}
|
}
|
||||||
|
|
||||||
private String encode(String rawPassword, byte[] salt) {
|
private String encode(CharSequence rawPassword, byte[] salt) {
|
||||||
byte[] digest = digest(rawPassword, salt);
|
byte[] digest = digest(rawPassword, salt);
|
||||||
return hexEncode(digest);
|
return new String(Hex.encode(digest));
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] digest(String rawPassword, byte[] salt) {
|
private byte[] digest(CharSequence rawPassword, byte[] salt) {
|
||||||
byte[] digest = digester.digest(concatenate(salt, secret, utf8Encode(rawPassword)));
|
byte[] digest = digester.digest(concatenate(salt, secret, Utf8.encode(rawPassword)));
|
||||||
return concatenate(salt, digest);
|
return concatenate(salt, digest);
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] decode(String encodedPassword) {
|
private byte[] decode(CharSequence encodedPassword) {
|
||||||
return hexDecode(encodedPassword);
|
return Hex.decode(encodedPassword);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constant time comparison to prevent against timing attacks.
|
* Constant time comparison to prevent against timing attacks.
|
||||||
* @param expected
|
|
||||||
* @param actual
|
|
||||||
* @return
|
|
||||||
*/
|
*/
|
||||||
private boolean matches(byte[] expected, byte[] actual) {
|
private boolean matches(byte[] expected, byte[] actual) {
|
||||||
if (expected.length != actual.length) {
|
if (expected.length != actual.length) {
|
||||||
|
|
|
@ -15,73 +15,15 @@
|
||||||
*/
|
*/
|
||||||
package org.springframework.security.crypto.util;
|
package org.springframework.security.crypto.util;
|
||||||
|
|
||||||
import java.io.UnsupportedEncodingException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Static helper for encoding data.
|
* Static helper for encoding data.
|
||||||
|
* <p>
|
||||||
|
* For internal use only.
|
||||||
|
*
|
||||||
* @author Keith Donald
|
* @author Keith Donald
|
||||||
*/
|
*/
|
||||||
public class EncodingUtils {
|
public class EncodingUtils {
|
||||||
|
|
||||||
/**
|
|
||||||
* Encode the byte array into a hex String.
|
|
||||||
*/
|
|
||||||
public static String hexEncode(byte[] bytes) {
|
|
||||||
StringBuilder result = new StringBuilder();
|
|
||||||
char[] digits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
|
|
||||||
for (int i = 0; i < bytes.length; ++i) {
|
|
||||||
byte b = bytes[i];
|
|
||||||
result.append(digits[(b & 0xf0) >> 4]);
|
|
||||||
result.append(digits[b & 0x0f]);
|
|
||||||
}
|
|
||||||
return result.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decode the hex String into a byte array.
|
|
||||||
*/
|
|
||||||
public static byte[] hexDecode(String s) {
|
|
||||||
int len = s.length();
|
|
||||||
byte[] r = new byte[len / 2];
|
|
||||||
for (int i = 0; i < r.length; i++) {
|
|
||||||
int digit1 = s.charAt(i * 2), digit2 = s.charAt(i * 2 + 1);
|
|
||||||
if ((digit1 >= '0') && (digit1 <= '9')) {
|
|
||||||
digit1 -= '0';
|
|
||||||
} else if ((digit1 >= 'a') && (digit1 <= 'f')) {
|
|
||||||
digit1 -= 'a' - 10;
|
|
||||||
}
|
|
||||||
if ((digit2 >= '0') && (digit2 <= '9')) {
|
|
||||||
digit2 -= '0';
|
|
||||||
} else if ((digit2 >= 'a') && (digit2 <= 'f')) {
|
|
||||||
digit2 -= 'a' - 10;
|
|
||||||
}
|
|
||||||
r[i] = (byte) ((digit1 << 4) + digit2);
|
|
||||||
}
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the bytes of the String in UTF-8 encoded form.
|
|
||||||
*/
|
|
||||||
public static byte[] utf8Encode(String string) {
|
|
||||||
try {
|
|
||||||
return string.getBytes("UTF-8");
|
|
||||||
} catch (UnsupportedEncodingException e) {
|
|
||||||
throw encodingException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decode the bytes in UTF-8 form into a String.
|
|
||||||
*/
|
|
||||||
public static String utf8Decode(byte[] bytes) {
|
|
||||||
try {
|
|
||||||
return new String(bytes, "UTF-8");
|
|
||||||
} catch (UnsupportedEncodingException e) {
|
|
||||||
throw encodingException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Combine the individual byte arrays into one array.
|
* Combine the individual byte arrays into one array.
|
||||||
*/
|
*/
|
||||||
|
@ -115,8 +57,4 @@ public class EncodingUtils {
|
||||||
private EncodingUtils() {
|
private EncodingUtils() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static RuntimeException encodingException(UnsupportedEncodingException e) {
|
|
||||||
return new IllegalStateException("UTF-8 is not an available char set", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
import org.springframework.remoting.httpinvoker.SimpleHttpInvokerRequestExecutor;
|
import org.springframework.remoting.httpinvoker.SimpleHttpInvokerRequestExecutor;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.codec.Base64;
|
import org.springframework.security.crypto.codec.Base64;
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -10,9 +10,9 @@ import org.junit.Test;
|
||||||
public class EncryptorsTests {
|
public class EncryptorsTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void standard() {
|
public void standard() throws Exception {
|
||||||
BytesEncryptor encryptor = Encryptors.standard("password", "5c0744940b5c369b");
|
BytesEncryptor encryptor = Encryptors.standard("password", "5c0744940b5c369b");
|
||||||
byte[] result = encryptor.encrypt("text".getBytes());
|
byte[] result = encryptor.encrypt("text".getBytes("UTF-8"));
|
||||||
assertNotNull(result);
|
assertNotNull(result);
|
||||||
assertFalse(new String(result).equals("text"));
|
assertFalse(new String(result).equals("text"));
|
||||||
assertEquals("text", new String(encryptor.decrypt(result)));
|
assertEquals("text", new String(encryptor.decrypt(result)));
|
||||||
|
|
|
@ -6,7 +6,7 @@ import static org.junit.Assert.assertFalse;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.springframework.security.crypto.util.EncodingUtils;
|
import org.springframework.security.crypto.codec.Hex;
|
||||||
|
|
||||||
public class KeyGeneratorsTests {
|
public class KeyGeneratorsTests {
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@ public class KeyGeneratorsTests {
|
||||||
StringKeyGenerator keyGenerator = KeyGenerators.string();
|
StringKeyGenerator keyGenerator = KeyGenerators.string();
|
||||||
String hexStringKey = keyGenerator.generateKey();
|
String hexStringKey = keyGenerator.generateKey();
|
||||||
assertEquals(16, hexStringKey.length());
|
assertEquals(16, hexStringKey.length());
|
||||||
assertEquals(8, EncodingUtils.hexDecode(hexStringKey).length);
|
assertEquals(8, Hex.decode(hexStringKey).length);
|
||||||
String hexStringKey2 = keyGenerator.generateKey();
|
String hexStringKey2 = keyGenerator.generateKey();
|
||||||
assertFalse(hexStringKey.equals(hexStringKey2));
|
assertFalse(hexStringKey.equals(hexStringKey2));
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package org.springframework.security.crypto.util;
|
package org.springframework.security.crypto.password;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
|
@ -7,6 +7,7 @@ import java.security.MessageDigest;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import org.springframework.security.crypto.password.Digester;
|
||||||
|
|
||||||
public class DigesterTests {
|
public class DigesterTests {
|
||||||
|
|
|
@ -19,7 +19,7 @@ public class StandardPasswordEncoderTests {
|
||||||
@Test
|
@Test
|
||||||
public void matchesLengthChecked() {
|
public void matchesLengthChecked() {
|
||||||
String result = encoder.encode("password");
|
String result = encoder.encode("password");
|
||||||
assertFalse(encoder.matches("password", result.substring(0,result.length()-1)));
|
assertFalse(encoder.matches("password", result.substring(0,result.length()-2)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -6,20 +6,21 @@ import static org.junit.Assert.assertTrue;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import org.springframework.security.crypto.codec.Hex;
|
||||||
|
|
||||||
public class EncodingUtilsTests {
|
public class EncodingUtilsTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void hexEncode() {
|
public void hexEncode() {
|
||||||
byte[] bytes = new byte[] { (byte)0x01, (byte)0xFF, (byte)65, (byte)66, (byte)67, (byte)0xC0, (byte)0xC1, (byte)0xC2 };
|
byte[] bytes = new byte[] { (byte)0x01, (byte)0xFF, (byte)65, (byte)66, (byte)67, (byte)0xC0, (byte)0xC1, (byte)0xC2 };
|
||||||
String result = EncodingUtils.hexEncode(bytes);
|
String result = new String(Hex.encode(bytes));
|
||||||
assertEquals("01ff414243c0c1c2", result);
|
assertEquals("01ff414243c0c1c2", result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void hexDecode() {
|
public void hexDecode() {
|
||||||
byte[] bytes = new byte[] { (byte)0x01, (byte)0xFF, (byte)65, (byte)66, (byte)67, (byte)0xC0, (byte)0xC1, (byte)0xC2 };
|
byte[] bytes = new byte[] { (byte)0x01, (byte)0xFF, (byte)65, (byte)66, (byte)67, (byte)0xC0, (byte)0xC1, (byte)0xC2 };
|
||||||
byte[] result = EncodingUtils.hexDecode("01ff414243c0c1c2");
|
byte[] result = Hex.decode("01ff414243c0c1c2");
|
||||||
assertTrue(Arrays.equals(bytes, result));
|
assertTrue(Arrays.equals(bytes, result));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.SpringSecurityMessageSource;
|
import org.springframework.security.core.SpringSecurityMessageSource;
|
||||||
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
|
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
|
||||||
import org.springframework.security.core.authority.mapping.NullAuthoritiesMapper;
|
import org.springframework.security.core.authority.mapping.NullAuthoritiesMapper;
|
||||||
import org.springframework.security.core.codec.Base64;
|
import org.springframework.security.crypto.codec.Base64;
|
||||||
import org.springframework.security.core.userdetails.UserDetails;
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
import org.springframework.security.core.userdetails.UserDetailsChecker;
|
import org.springframework.security.core.userdetails.UserDetailsChecker;
|
||||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||||
|
|
|
@ -9,7 +9,7 @@ import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
import org.springframework.dao.DataAccessException;
|
import org.springframework.dao.DataAccessException;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.codec.Base64;
|
import org.springframework.security.crypto.codec.Base64;
|
||||||
import org.springframework.security.core.userdetails.UserDetails;
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
import org.springframework.security.web.authentication.RememberMeServices;
|
import org.springframework.security.web.authentication.RememberMeServices;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
package org.springframework.security.web.authentication.rememberme;
|
package org.springframework.security.web.authentication.rememberme;
|
||||||
|
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.codec.Hex;
|
import org.springframework.security.crypto.codec.Hex;
|
||||||
import org.springframework.security.core.userdetails.UserDetails;
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,7 @@ import org.springframework.security.authentication.BadCredentialsException;
|
||||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.AuthenticationException;
|
import org.springframework.security.core.AuthenticationException;
|
||||||
import org.springframework.security.core.codec.Base64;
|
import org.springframework.security.crypto.codec.Base64;
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
import org.springframework.security.web.AuthenticationEntryPoint;
|
import org.springframework.security.web.AuthenticationEntryPoint;
|
||||||
import org.springframework.security.web.authentication.NullRememberMeServices;
|
import org.springframework.security.web.authentication.NullRememberMeServices;
|
||||||
|
|
|
@ -7,7 +7,7 @@ import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.springframework.security.core.codec.Hex;
|
import org.springframework.security.crypto.codec.Hex;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,7 @@ import org.apache.commons.logging.LogFactory;
|
||||||
import org.springframework.beans.factory.InitializingBean;
|
import org.springframework.beans.factory.InitializingBean;
|
||||||
import org.springframework.core.Ordered;
|
import org.springframework.core.Ordered;
|
||||||
import org.springframework.security.core.AuthenticationException;
|
import org.springframework.security.core.AuthenticationException;
|
||||||
import org.springframework.security.core.codec.Base64;
|
import org.springframework.security.crypto.codec.Base64;
|
||||||
import org.springframework.security.web.AuthenticationEntryPoint;
|
import org.springframework.security.web.AuthenticationEntryPoint;
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -37,7 +37,7 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticatio
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.AuthenticationException;
|
import org.springframework.security.core.AuthenticationException;
|
||||||
import org.springframework.security.core.SpringSecurityMessageSource;
|
import org.springframework.security.core.SpringSecurityMessageSource;
|
||||||
import org.springframework.security.core.codec.Base64;
|
import org.springframework.security.crypto.codec.Base64;
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
import org.springframework.security.core.userdetails.UserCache;
|
import org.springframework.security.core.userdetails.UserCache;
|
||||||
import org.springframework.security.core.userdetails.UserDetails;
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
|
|
@ -19,6 +19,7 @@ Ignored-Existing-Headers:
|
||||||
Import-Template:
|
Import-Template:
|
||||||
org.apache.commons.logging.*;version="${cloggingRange}",
|
org.apache.commons.logging.*;version="${cloggingRange}",
|
||||||
org.springframework.security.core.*;version="${secRange}",
|
org.springframework.security.core.*;version="${secRange}",
|
||||||
|
org.springframework.security.crypto.*;version="${secRange}",
|
||||||
org.springframework.security.authentication.*;version="${secRange}",
|
org.springframework.security.authentication.*;version="${secRange}",
|
||||||
org.springframework.security.access.*;version="${secRange}",
|
org.springframework.security.access.*;version="${secRange}",
|
||||||
org.springframework.security.util;version="${secRange}",
|
org.springframework.security.util;version="${secRange}",
|
||||||
|
|
Loading…
Reference in New Issue