From e470eaa41db4a5c6793ac64d2822fc096c299ab7 Mon Sep 17 00:00:00 2001 From: Luke Taylor Date: Thu, 17 Mar 2011 00:48:53 +0000 Subject: [PATCH] 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. --- .../encoding/LdapShaPasswordEncoder.java | 2 +- .../encoding/Md4PasswordEncoder.java | 4 +- .../MessageDigestPasswordEncoder.java | 4 +- .../KeyBasedPersistenceTokenService.java | 4 +- .../core/token/Sha512DigestUtils.java | 2 +- .../{core => crypto}/codec/Base64.java | 14 ++-- .../security/{core => crypto}/codec/Hex.java | 26 +++++-- .../security/crypto/codec/Utf8.java | 39 +++++++++++ .../{core => crypto}/codec/package-info.java | 2 +- .../crypto/encrypt/AesBytesEncryptor.java | 17 ++--- .../crypto/{util => encrypt}/CipherUtils.java | 4 +- .../security/crypto/encrypt/Encryptors.java | 20 +++--- .../encrypt/HexEncodingTextEncryptor.java | 10 ++- .../crypto/encrypt/TextEncryptor.java | 1 + .../keygen/HexEncodingStringKeyGenerator.java | 6 +- .../crypto/{util => password}/Digester.java | 6 +- .../crypto/password/NoOpPasswordEncoder.java | 10 +-- .../crypto/password/PasswordEncoder.java | 8 ++- .../password/StandardPasswordEncoder.java | 39 ++++++----- .../security/crypto/util/EncodingUtils.java | 68 +------------------ ...ationSimpleHttpInvokerRequestExecutor.java | 2 +- .../crypto/encrypt/EncryptorsTests.java | 4 +- .../crypto/keygen/KeyGeneratorsTests.java | 4 +- .../{util => password}/DigesterTests.java | 3 +- .../StandardPasswordEncoderTests.java | 2 +- .../crypto/util/EncodingUtilsTests.java | 5 +- .../AbstractRememberMeServices.java | 2 +- ...ersistentTokenBasedRememberMeServices.java | 2 +- .../TokenBasedRememberMeServices.java | 2 +- .../www/BasicAuthenticationFilter.java | 2 +- .../authentication/www/DigestAuthUtils.java | 2 +- .../www/DigestAuthenticationEntryPoint.java | 2 +- .../www/DigestAuthenticationFilter.java | 2 +- web/template.mf | 1 + 34 files changed, 162 insertions(+), 159 deletions(-) rename core/src/main/java/org/springframework/security/{core => crypto}/codec/Base64.java (98%) rename core/src/main/java/org/springframework/security/{core => crypto}/codec/Hex.java (51%) create mode 100644 core/src/main/java/org/springframework/security/crypto/codec/Utf8.java rename core/src/main/java/org/springframework/security/{core => crypto}/codec/package-info.java (60%) rename core/src/main/java/org/springframework/security/crypto/{util => encrypt}/CipherUtils.java (98%) rename core/src/main/java/org/springframework/security/crypto/{util => password}/Digester.java (96%) rename core/src/test/java/org/springframework/security/crypto/{util => password}/DigesterTests.java (89%) diff --git a/core/src/main/java/org/springframework/security/authentication/encoding/LdapShaPasswordEncoder.java b/core/src/main/java/org/springframework/security/authentication/encoding/LdapShaPasswordEncoder.java index f349df9c96..85553d56c8 100644 --- a/core/src/main/java/org/springframework/security/authentication/encoding/LdapShaPasswordEncoder.java +++ b/core/src/main/java/org/springframework/security/authentication/encoding/LdapShaPasswordEncoder.java @@ -19,7 +19,7 @@ package org.springframework.security.authentication.encoding; import java.io.UnsupportedEncodingException; import java.security.MessageDigest; -import org.springframework.security.core.codec.Base64; +import org.springframework.security.crypto.codec.Base64; import org.springframework.util.Assert; diff --git a/core/src/main/java/org/springframework/security/authentication/encoding/Md4PasswordEncoder.java b/core/src/main/java/org/springframework/security/authentication/encoding/Md4PasswordEncoder.java index bb2751c7dc..e7479b8cd9 100644 --- a/core/src/main/java/org/springframework/security/authentication/encoding/Md4PasswordEncoder.java +++ b/core/src/main/java/org/springframework/security/authentication/encoding/Md4PasswordEncoder.java @@ -16,8 +16,8 @@ package org.springframework.security.authentication.encoding; import java.io.UnsupportedEncodingException; -import org.springframework.security.core.codec.Base64; -import org.springframework.security.core.codec.Hex; +import org.springframework.security.crypto.codec.Base64; +import org.springframework.security.crypto.codec.Hex; /** * MD4 implementation of PasswordEncoder. diff --git a/core/src/main/java/org/springframework/security/authentication/encoding/MessageDigestPasswordEncoder.java b/core/src/main/java/org/springframework/security/authentication/encoding/MessageDigestPasswordEncoder.java index 86f08d08ff..9ecd7a7dc8 100644 --- a/core/src/main/java/org/springframework/security/authentication/encoding/MessageDigestPasswordEncoder.java +++ b/core/src/main/java/org/springframework/security/authentication/encoding/MessageDigestPasswordEncoder.java @@ -4,8 +4,8 @@ import java.io.UnsupportedEncodingException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import org.springframework.security.core.codec.Base64; -import org.springframework.security.core.codec.Hex; +import org.springframework.security.crypto.codec.Base64; +import org.springframework.security.crypto.codec.Hex; import org.springframework.util.Assert; /** diff --git a/core/src/main/java/org/springframework/security/core/token/KeyBasedPersistenceTokenService.java b/core/src/main/java/org/springframework/security/core/token/KeyBasedPersistenceTokenService.java index 4d22539e02..56f7c4fe79 100644 --- a/core/src/main/java/org/springframework/security/core/token/KeyBasedPersistenceTokenService.java +++ b/core/src/main/java/org/springframework/security/core/token/KeyBasedPersistenceTokenService.java @@ -5,8 +5,8 @@ import java.security.SecureRandom; import java.util.Date; import org.springframework.beans.factory.InitializingBean; -import org.springframework.security.core.codec.Base64; -import org.springframework.security.core.codec.Hex; +import org.springframework.security.crypto.codec.Base64; +import org.springframework.security.crypto.codec.Hex; import org.springframework.util.Assert; import org.springframework.util.StringUtils; diff --git a/core/src/main/java/org/springframework/security/core/token/Sha512DigestUtils.java b/core/src/main/java/org/springframework/security/core/token/Sha512DigestUtils.java index 05925f328d..f7fce2686c 100644 --- a/core/src/main/java/org/springframework/security/core/token/Sha512DigestUtils.java +++ b/core/src/main/java/org/springframework/security/core/token/Sha512DigestUtils.java @@ -3,7 +3,7 @@ package org.springframework.security.core.token; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import org.springframework.security.core.codec.Hex; +import org.springframework.security.crypto.codec.Hex; /** * Provides SHA512 digest methods. diff --git a/core/src/main/java/org/springframework/security/core/codec/Base64.java b/core/src/main/java/org/springframework/security/crypto/codec/Base64.java similarity index 98% rename from core/src/main/java/org/springframework/security/core/codec/Base64.java rename to core/src/main/java/org/springframework/security/crypto/codec/Base64.java index 31ea9008ea..f064a92e46 100644 --- a/core/src/main/java/org/springframework/security/core/codec/Base64.java +++ b/core/src/main/java/org/springframework/security/crypto/codec/Base64.java @@ -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 * 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: - * http://www.faqs.org/qa/rfcc-1940.html. - */ - public final static int ORDERED = 32; + /** + * Encode using the special "ordered" dialect of Base64 described here: + * http://www.faqs.org/qa/rfcc-1940.html. + */ + public final static int ORDERED = 32; /** Maximum line length (76) of Base64 output. */ diff --git a/core/src/main/java/org/springframework/security/core/codec/Hex.java b/core/src/main/java/org/springframework/security/crypto/codec/Hex.java similarity index 51% rename from core/src/main/java/org/springframework/security/core/codec/Hex.java rename to core/src/main/java/org/springframework/security/crypto/codec/Hex.java index d1d3105eec..a89b4560f6 100644 --- a/core/src/main/java/org/springframework/security/core/codec/Hex.java +++ b/core/src/main/java/org/springframework/security/crypto/codec/Hex.java @@ -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) @@ -30,7 +30,25 @@ public final class Hex { 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; + } + } diff --git a/core/src/main/java/org/springframework/security/crypto/codec/Utf8.java b/core/src/main/java/org/springframework/security/crypto/codec/Utf8.java new file mode 100644 index 0000000000..94b80f77a9 --- /dev/null +++ b/core/src/main/java/org/springframework/security/crypto/codec/Utf8.java @@ -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. + *

+ * 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); + } + } +} diff --git a/core/src/main/java/org/springframework/security/core/codec/package-info.java b/core/src/main/java/org/springframework/security/crypto/codec/package-info.java similarity index 60% rename from core/src/main/java/org/springframework/security/core/codec/package-info.java rename to core/src/main/java/org/springframework/security/crypto/codec/package-info.java index b1cfa6aea7..791bb54632 100644 --- a/core/src/main/java/org/springframework/security/core/codec/package-info.java +++ b/core/src/main/java/org/springframework/security/crypto/codec/package-info.java @@ -1,4 +1,4 @@ /** * Internal codec classes. Only intended for use within the framework. */ -package org.springframework.security.core.codec; +package org.springframework.security.crypto.codec; diff --git a/core/src/main/java/org/springframework/security/crypto/encrypt/AesBytesEncryptor.java b/core/src/main/java/org/springframework/security/crypto/encrypt/AesBytesEncryptor.java index cb9102927c..86cdbcee6d 100644 --- a/core/src/main/java/org/springframework/security/crypto/encrypt/AesBytesEncryptor.java +++ b/core/src/main/java/org/springframework/security/crypto/encrypt/AesBytesEncryptor.java @@ -15,10 +15,10 @@ */ package org.springframework.security.crypto.encrypt; -import static org.springframework.security.crypto.util.CipherUtils.doFinal; -import static org.springframework.security.crypto.util.CipherUtils.initCipher; -import static org.springframework.security.crypto.util.CipherUtils.newCipher; -import static org.springframework.security.crypto.util.CipherUtils.newSecretKey; +import static org.springframework.security.crypto.encrypt.CipherUtils.doFinal; +import static org.springframework.security.crypto.encrypt.CipherUtils.initCipher; +import static org.springframework.security.crypto.encrypt.CipherUtils.newCipher; +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.subArray; @@ -28,11 +28,12 @@ import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.PBEKeySpec; import javax.crypto.spec.SecretKeySpec; +import org.springframework.security.crypto.codec.Hex; import org.springframework.security.crypto.keygen.BytesKeyGenerator; -import org.springframework.security.crypto.util.EncodingUtils; /** * Encryptor that uses 256-bit AES encryption. + * * @author Keith Donald */ final class AesBytesEncryptor implements BytesEncryptor { @@ -45,8 +46,8 @@ final class AesBytesEncryptor implements BytesEncryptor { private final BytesKeyGenerator ivGenerator; - public AesBytesEncryptor(String password, String salt, BytesKeyGenerator ivGenerator) { - PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), EncodingUtils.hexDecode(salt), 1024, 256); + public AesBytesEncryptor(String password, CharSequence salt, BytesKeyGenerator ivGenerator) { + PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray(), Hex.decode(salt), 1024, 256); SecretKey secretKey = newSecretKey("PBKDF2WithHmacSHA1", keySpec); this.secretKey = new SecretKeySpec(secretKey.getEncoded(), "AES"); encryptor = newCipher(AES_ALGORITHM); @@ -82,4 +83,4 @@ final class AesBytesEncryptor implements BytesEncryptor { } private static final String AES_ALGORITHM = "AES/CBC/PKCS5Padding"; -} \ No newline at end of file +} diff --git a/core/src/main/java/org/springframework/security/crypto/util/CipherUtils.java b/core/src/main/java/org/springframework/security/crypto/encrypt/CipherUtils.java similarity index 98% rename from core/src/main/java/org/springframework/security/crypto/util/CipherUtils.java rename to core/src/main/java/org/springframework/security/crypto/encrypt/CipherUtils.java index 978fa45208..97dd0489ea 100644 --- a/core/src/main/java/org/springframework/security/crypto/util/CipherUtils.java +++ b/core/src/main/java/org/springframework/security/crypto/encrypt/CipherUtils.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.security.crypto.util; +package org.springframework.security.crypto.encrypt; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; @@ -35,7 +35,7 @@ import javax.crypto.spec.PBEParameterSpec; * Static helper for working with the Cipher API. * @author Keith Donald */ -public class CipherUtils { +class CipherUtils { /** * Generates a SecretKey. diff --git a/core/src/main/java/org/springframework/security/crypto/encrypt/Encryptors.java b/core/src/main/java/org/springframework/security/crypto/encrypt/Encryptors.java index fc4222e2d0..af4f056b56 100644 --- a/core/src/main/java/org/springframework/security/crypto/encrypt/Encryptors.java +++ b/core/src/main/java/org/springframework/security/crypto/encrypt/Encryptors.java @@ -20,6 +20,7 @@ import org.springframework.security.crypto.keygen.KeyGenerators; /** * Factory for commonly used encryptors. * Defines the public API for constructing {@link BytesEncryptor} and {@link TextEncryptor} implementations. + * * @author Keith Donald */ 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. * Also applies a random 16 byte initialization vector to ensure each encrypted message will be unique. * Requires Java 6. + * * @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) { - return new AesBytesEncryptor(password, password, KeyGenerators.secureRandom(16)); + public static BytesEncryptor standard(CharSequence password, CharSequence salt) { + return new AesBytesEncryptor(password.toString(), salt, KeyGenerators.secureRandom(16)); } /** * Creates a text encryptor that uses standard password-based encryption. * Encrypted text is hex-encoded. + * * @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)); } @@ -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. * This is done to allow encrypted data to be queried against. * Encrypted text is hex-encoded. + * * @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) { - return new HexEncodingTextEncryptor(new AesBytesEncryptor(password, salt, KeyGenerators.shared(16))); + public static TextEncryptor queryableText(CharSequence password, CharSequence salt) { + return new HexEncodingTextEncryptor(new AesBytesEncryptor(password.toString(), salt, KeyGenerators.shared(16))); } /** @@ -84,4 +88,4 @@ public class Encryptors { } -} \ No newline at end of file +} diff --git a/core/src/main/java/org/springframework/security/crypto/encrypt/HexEncodingTextEncryptor.java b/core/src/main/java/org/springframework/security/crypto/encrypt/HexEncodingTextEncryptor.java index a06e1f042a..16bcede36b 100644 --- a/core/src/main/java/org/springframework/security/crypto/encrypt/HexEncodingTextEncryptor.java +++ b/core/src/main/java/org/springframework/security/crypto/encrypt/HexEncodingTextEncryptor.java @@ -15,10 +15,8 @@ */ package org.springframework.security.crypto.encrypt; -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.utf8Decode; -import static org.springframework.security.crypto.util.EncodingUtils.utf8Encode; +import org.springframework.security.crypto.codec.Hex; +import org.springframework.security.crypto.codec.Utf8; /** * Delegates to an {@link BytesEncryptor} to encrypt text strings. @@ -35,11 +33,11 @@ final class HexEncodingTextEncryptor implements TextEncryptor { } 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) { - return utf8Decode(encryptor.decrypt(hexDecode(encryptedText))); + return Utf8.decode(encryptor.decrypt(Hex.decode(encryptedText))); } } diff --git a/core/src/main/java/org/springframework/security/crypto/encrypt/TextEncryptor.java b/core/src/main/java/org/springframework/security/crypto/encrypt/TextEncryptor.java index c62748751c..7e2ac9459d 100644 --- a/core/src/main/java/org/springframework/security/crypto/encrypt/TextEncryptor.java +++ b/core/src/main/java/org/springframework/security/crypto/encrypt/TextEncryptor.java @@ -17,6 +17,7 @@ package org.springframework.security.crypto.encrypt; /** * Service interface for symmetric encryption of text strings. + * * @author Keith Donald */ public interface TextEncryptor { diff --git a/core/src/main/java/org/springframework/security/crypto/keygen/HexEncodingStringKeyGenerator.java b/core/src/main/java/org/springframework/security/crypto/keygen/HexEncodingStringKeyGenerator.java index b371525154..0c0b92d5be 100644 --- a/core/src/main/java/org/springframework/security/crypto/keygen/HexEncodingStringKeyGenerator.java +++ b/core/src/main/java/org/springframework/security/crypto/keygen/HexEncodingStringKeyGenerator.java @@ -15,7 +15,7 @@ */ 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. @@ -31,7 +31,7 @@ final class HexEncodingStringKeyGenerator implements StringKeyGenerator { } public String generateKey() { - return hexEncode(keyGenerator.generateKey()); + return new String(Hex.encode(keyGenerator.generateKey())); } -} \ No newline at end of file +} diff --git a/core/src/main/java/org/springframework/security/crypto/util/Digester.java b/core/src/main/java/org/springframework/security/crypto/password/Digester.java similarity index 96% rename from core/src/main/java/org/springframework/security/crypto/util/Digester.java rename to core/src/main/java/org/springframework/security/crypto/password/Digester.java index 6ff2134e16..01935f8a4b 100644 --- a/core/src/main/java/org/springframework/security/crypto/util/Digester.java +++ b/core/src/main/java/org/springframework/security/crypto/password/Digester.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.security.crypto.util; +package org.springframework.security.crypto.password; import java.security.MessageDigest; 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. * @author Keith Donald */ -public class Digester { +class Digester { private final MessageDigest messageDigest; @@ -59,4 +59,4 @@ public class Digester { return messageDigest.digest(value); } -} \ No newline at end of file +} diff --git a/core/src/main/java/org/springframework/security/crypto/password/NoOpPasswordEncoder.java b/core/src/main/java/org/springframework/security/crypto/password/NoOpPasswordEncoder.java index 8ec5f3d563..d9b79d455e 100644 --- a/core/src/main/java/org/springframework/security/crypto/password/NoOpPasswordEncoder.java +++ b/core/src/main/java/org/springframework/security/crypto/password/NoOpPasswordEncoder.java @@ -18,16 +18,17 @@ package org.springframework.security.crypto.password; /** * A password encoder that does nothing. * Useful for testing where working with plain text passwords may be preferred. + * * @author Keith Donald */ public final class NoOpPasswordEncoder implements PasswordEncoder { - public String encode(String rawPassword) { - return rawPassword; + public String encode(CharSequence rawPassword) { + return rawPassword.toString(); } - public boolean matches(String rawPassword, String encodedPassword) { - return rawPassword.equals(encodedPassword); + public boolean matches(CharSequence rawPassword, String encodedPassword) { + return rawPassword.toString().equals(encodedPassword); } /** @@ -40,7 +41,6 @@ public final class NoOpPasswordEncoder implements PasswordEncoder { private static final PasswordEncoder INSTANCE = new NoOpPasswordEncoder(); private NoOpPasswordEncoder() { - } } diff --git a/core/src/main/java/org/springframework/security/crypto/password/PasswordEncoder.java b/core/src/main/java/org/springframework/security/crypto/password/PasswordEncoder.java index 981f7f12f3..c4320f31a6 100644 --- a/core/src/main/java/org/springframework/security/crypto/password/PasswordEncoder.java +++ b/core/src/main/java/org/springframework/security/crypto/password/PasswordEncoder.java @@ -23,18 +23,20 @@ public interface PasswordEncoder { /** * 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. * Returns true if the passwords match, false if they do not. * The stored password itself is never decoded. + * * @param rawPassword the raw password to encode and match * @param encodedPassword the encoded password from storage to compare with * @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); } diff --git a/core/src/main/java/org/springframework/security/crypto/password/StandardPasswordEncoder.java b/core/src/main/java/org/springframework/security/crypto/password/StandardPasswordEncoder.java index 420a2c6772..72e6b7cad7 100644 --- a/core/src/main/java/org/springframework/security/crypto/password/StandardPasswordEncoder.java +++ b/core/src/main/java/org/springframework/security/crypto/password/StandardPasswordEncoder.java @@ -16,17 +16,19 @@ package org.springframework.security.crypto.password; 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.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.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. + *

+ * The digest algorithm is invoked on the concatenated bytes of the salt, secret and password. + * * @author Keith Donald */ public final class StandardPasswordEncoder implements PasswordEncoder { @@ -41,15 +43,15 @@ public final class StandardPasswordEncoder implements PasswordEncoder { * Constructs a standard password encoder. * @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); } - public String encode(String rawPassword) { + public String encode(CharSequence rawPassword) { return encode(rawPassword, saltGenerator.generateKey()); } - public boolean matches(String rawPassword, String encodedPassword) { + public boolean matches(CharSequence rawPassword, String encodedPassword) { byte[] digested = decode(encodedPassword); byte[] salt = subArray(digested, 0, saltGenerator.getKeyLength()); return matches(digested, digest(rawPassword, salt)); @@ -57,31 +59,28 @@ public final class StandardPasswordEncoder implements PasswordEncoder { // 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.secret = utf8Encode(secret); + this.secret = Utf8.encode(secret); this.saltGenerator = KeyGenerators.secureRandom(); } - private String encode(String rawPassword, byte[] salt) { + private String encode(CharSequence rawPassword, byte[] salt) { byte[] digest = digest(rawPassword, salt); - return hexEncode(digest); + return new String(Hex.encode(digest)); } - private byte[] digest(String rawPassword, byte[] salt) { - byte[] digest = digester.digest(concatenate(salt, secret, utf8Encode(rawPassword))); + private byte[] digest(CharSequence rawPassword, byte[] salt) { + byte[] digest = digester.digest(concatenate(salt, secret, Utf8.encode(rawPassword))); return concatenate(salt, digest); } - private byte[] decode(String encodedPassword) { - return hexDecode(encodedPassword); + private byte[] decode(CharSequence encodedPassword) { + return Hex.decode(encodedPassword); } /** * Constant time comparison to prevent against timing attacks. - * @param expected - * @param actual - * @return */ private boolean matches(byte[] expected, byte[] actual) { if (expected.length != actual.length) { @@ -94,4 +93,4 @@ public final class StandardPasswordEncoder implements PasswordEncoder { } return result == 0; } -} \ No newline at end of file +} diff --git a/core/src/main/java/org/springframework/security/crypto/util/EncodingUtils.java b/core/src/main/java/org/springframework/security/crypto/util/EncodingUtils.java index 32425f3999..fbe83b9fdd 100644 --- a/core/src/main/java/org/springframework/security/crypto/util/EncodingUtils.java +++ b/core/src/main/java/org/springframework/security/crypto/util/EncodingUtils.java @@ -15,73 +15,15 @@ */ package org.springframework.security.crypto.util; -import java.io.UnsupportedEncodingException; - /** * Static helper for encoding data. + *

+ * For internal use only. + * * @author Keith Donald */ 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. */ @@ -115,8 +57,4 @@ public class EncodingUtils { private EncodingUtils() { } - private static RuntimeException encodingException(UnsupportedEncodingException e) { - return new IllegalStateException("UTF-8 is not an available char set", e); - } - } diff --git a/core/src/main/java/org/springframework/security/remoting/httpinvoker/AuthenticationSimpleHttpInvokerRequestExecutor.java b/core/src/main/java/org/springframework/security/remoting/httpinvoker/AuthenticationSimpleHttpInvokerRequestExecutor.java index 6bde4a9742..ab32218d63 100644 --- a/core/src/main/java/org/springframework/security/remoting/httpinvoker/AuthenticationSimpleHttpInvokerRequestExecutor.java +++ b/core/src/main/java/org/springframework/security/remoting/httpinvoker/AuthenticationSimpleHttpInvokerRequestExecutor.java @@ -22,7 +22,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.remoting.httpinvoker.SimpleHttpInvokerRequestExecutor; 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; diff --git a/core/src/test/java/org/springframework/security/crypto/encrypt/EncryptorsTests.java b/core/src/test/java/org/springframework/security/crypto/encrypt/EncryptorsTests.java index 395b1a93a7..4683c468d0 100644 --- a/core/src/test/java/org/springframework/security/crypto/encrypt/EncryptorsTests.java +++ b/core/src/test/java/org/springframework/security/crypto/encrypt/EncryptorsTests.java @@ -10,9 +10,9 @@ import org.junit.Test; public class EncryptorsTests { @Test - public void standard() { + public void standard() throws Exception { BytesEncryptor encryptor = Encryptors.standard("password", "5c0744940b5c369b"); - byte[] result = encryptor.encrypt("text".getBytes()); + byte[] result = encryptor.encrypt("text".getBytes("UTF-8")); assertNotNull(result); assertFalse(new String(result).equals("text")); assertEquals("text", new String(encryptor.decrypt(result))); diff --git a/core/src/test/java/org/springframework/security/crypto/keygen/KeyGeneratorsTests.java b/core/src/test/java/org/springframework/security/crypto/keygen/KeyGeneratorsTests.java index fc4a113c6b..ae01e03628 100644 --- a/core/src/test/java/org/springframework/security/crypto/keygen/KeyGeneratorsTests.java +++ b/core/src/test/java/org/springframework/security/crypto/keygen/KeyGeneratorsTests.java @@ -6,7 +6,7 @@ import static org.junit.Assert.assertFalse; import java.util.Arrays; import org.junit.Test; -import org.springframework.security.crypto.util.EncodingUtils; +import org.springframework.security.crypto.codec.Hex; public class KeyGeneratorsTests { @@ -35,7 +35,7 @@ public class KeyGeneratorsTests { StringKeyGenerator keyGenerator = KeyGenerators.string(); String hexStringKey = keyGenerator.generateKey(); assertEquals(16, hexStringKey.length()); - assertEquals(8, EncodingUtils.hexDecode(hexStringKey).length); + assertEquals(8, Hex.decode(hexStringKey).length); String hexStringKey2 = keyGenerator.generateKey(); assertFalse(hexStringKey.equals(hexStringKey2)); } diff --git a/core/src/test/java/org/springframework/security/crypto/util/DigesterTests.java b/core/src/test/java/org/springframework/security/crypto/password/DigesterTests.java similarity index 89% rename from core/src/test/java/org/springframework/security/crypto/util/DigesterTests.java rename to core/src/test/java/org/springframework/security/crypto/password/DigesterTests.java index 839466261c..97f14f06bb 100644 --- a/core/src/test/java/org/springframework/security/crypto/util/DigesterTests.java +++ b/core/src/test/java/org/springframework/security/crypto/password/DigesterTests.java @@ -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.assertFalse; @@ -7,6 +7,7 @@ import java.security.MessageDigest; import java.util.Arrays; import org.junit.Test; +import org.springframework.security.crypto.password.Digester; public class DigesterTests { diff --git a/core/src/test/java/org/springframework/security/crypto/password/StandardPasswordEncoderTests.java b/core/src/test/java/org/springframework/security/crypto/password/StandardPasswordEncoderTests.java index 044ac9a9fc..e4523ec8ba 100644 --- a/core/src/test/java/org/springframework/security/crypto/password/StandardPasswordEncoderTests.java +++ b/core/src/test/java/org/springframework/security/crypto/password/StandardPasswordEncoderTests.java @@ -19,7 +19,7 @@ public class StandardPasswordEncoderTests { @Test public void matchesLengthChecked() { 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 diff --git a/core/src/test/java/org/springframework/security/crypto/util/EncodingUtilsTests.java b/core/src/test/java/org/springframework/security/crypto/util/EncodingUtilsTests.java index baa85ed0f2..46638d0a35 100644 --- a/core/src/test/java/org/springframework/security/crypto/util/EncodingUtilsTests.java +++ b/core/src/test/java/org/springframework/security/crypto/util/EncodingUtilsTests.java @@ -6,20 +6,21 @@ import static org.junit.Assert.assertTrue; import java.util.Arrays; import org.junit.Test; +import org.springframework.security.crypto.codec.Hex; public class EncodingUtilsTests { @Test public void hexEncode() { 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); } @Test public void hexDecode() { 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)); } diff --git a/web/src/main/java/org/springframework/security/web/authentication/rememberme/AbstractRememberMeServices.java b/web/src/main/java/org/springframework/security/web/authentication/rememberme/AbstractRememberMeServices.java index 67416152a6..ca4c6d0ae0 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/rememberme/AbstractRememberMeServices.java +++ b/web/src/main/java/org/springframework/security/web/authentication/rememberme/AbstractRememberMeServices.java @@ -16,7 +16,7 @@ import org.springframework.security.core.Authentication; import org.springframework.security.core.SpringSecurityMessageSource; import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper; 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.UserDetailsChecker; import org.springframework.security.core.userdetails.UserDetailsService; diff --git a/web/src/main/java/org/springframework/security/web/authentication/rememberme/PersistentTokenBasedRememberMeServices.java b/web/src/main/java/org/springframework/security/web/authentication/rememberme/PersistentTokenBasedRememberMeServices.java index b48a5076d6..9cdff2b3f1 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/rememberme/PersistentTokenBasedRememberMeServices.java +++ b/web/src/main/java/org/springframework/security/web/authentication/rememberme/PersistentTokenBasedRememberMeServices.java @@ -9,7 +9,7 @@ import javax.servlet.http.HttpServletResponse; import org.springframework.dao.DataAccessException; 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.web.authentication.RememberMeServices; import org.springframework.util.Assert; diff --git a/web/src/main/java/org/springframework/security/web/authentication/rememberme/TokenBasedRememberMeServices.java b/web/src/main/java/org/springframework/security/web/authentication/rememberme/TokenBasedRememberMeServices.java index 3d1a1db5a5..4e650bb973 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/rememberme/TokenBasedRememberMeServices.java +++ b/web/src/main/java/org/springframework/security/web/authentication/rememberme/TokenBasedRememberMeServices.java @@ -16,7 +16,7 @@ package org.springframework.security.web.authentication.rememberme; 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.util.StringUtils; diff --git a/web/src/main/java/org/springframework/security/web/authentication/www/BasicAuthenticationFilter.java b/web/src/main/java/org/springframework/security/web/authentication/www/BasicAuthenticationFilter.java index c32451e873..4267210471 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/www/BasicAuthenticationFilter.java +++ b/web/src/main/java/org/springframework/security/web/authentication/www/BasicAuthenticationFilter.java @@ -31,7 +31,7 @@ import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; 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.web.AuthenticationEntryPoint; import org.springframework.security.web.authentication.NullRememberMeServices; diff --git a/web/src/main/java/org/springframework/security/web/authentication/www/DigestAuthUtils.java b/web/src/main/java/org/springframework/security/web/authentication/www/DigestAuthUtils.java index db34b4cb4b..1c6cac55c3 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/www/DigestAuthUtils.java +++ b/web/src/main/java/org/springframework/security/web/authentication/www/DigestAuthUtils.java @@ -7,7 +7,7 @@ import java.util.HashMap; import java.util.List; 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.StringUtils; diff --git a/web/src/main/java/org/springframework/security/web/authentication/www/DigestAuthenticationEntryPoint.java b/web/src/main/java/org/springframework/security/web/authentication/www/DigestAuthenticationEntryPoint.java index 0b5d169fae..c6c8417edd 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/www/DigestAuthenticationEntryPoint.java +++ b/web/src/main/java/org/springframework/security/web/authentication/www/DigestAuthenticationEntryPoint.java @@ -26,7 +26,7 @@ import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.InitializingBean; import org.springframework.core.Ordered; 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; diff --git a/web/src/main/java/org/springframework/security/web/authentication/www/DigestAuthenticationFilter.java b/web/src/main/java/org/springframework/security/web/authentication/www/DigestAuthenticationFilter.java index b37297ce4a..ced37900d2 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/www/DigestAuthenticationFilter.java +++ b/web/src/main/java/org/springframework/security/web/authentication/www/DigestAuthenticationFilter.java @@ -37,7 +37,7 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticatio import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; 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.userdetails.UserCache; import org.springframework.security.core.userdetails.UserDetails; diff --git a/web/template.mf b/web/template.mf index fbf3842de0..3cec203cea 100644 --- a/web/template.mf +++ b/web/template.mf @@ -19,6 +19,7 @@ Ignored-Existing-Headers: Import-Template: org.apache.commons.logging.*;version="${cloggingRange}", org.springframework.security.core.*;version="${secRange}", + org.springframework.security.crypto.*;version="${secRange}", org.springframework.security.authentication.*;version="${secRange}", org.springframework.security.access.*;version="${secRange}", org.springframework.security.util;version="${secRange}",