Browse Source

SEC-1569: initial commit of spring-security-crypto module, consisting of encrypt, keygen, password, and util packages

Keith Donald 14 năm trước cách đây
mục cha
commit
ffa7301e7f
29 tập tin đã thay đổi với 1254 bổ sung1 xóa
  1. 5 0
      crypto/crypto.gradle
  2. 34 0
      crypto/src/main/java/org/springframework/security/crypto/encrypt/BytesEncryptor.java
  3. 86 0
      crypto/src/main/java/org/springframework/security/crypto/encrypt/Encryptors.java
  4. 45 0
      crypto/src/main/java/org/springframework/security/crypto/encrypt/HexEncodingTextEncryptor.java
  5. 84 0
      crypto/src/main/java/org/springframework/security/crypto/encrypt/PasswordBasedBytesEncryptor.java
  6. 60 0
      crypto/src/main/java/org/springframework/security/crypto/encrypt/QueryableTextEncryptor.java
  7. 34 0
      crypto/src/main/java/org/springframework/security/crypto/encrypt/TextEncryptor.java
  8. 5 0
      crypto/src/main/java/org/springframework/security/crypto/encrypt/package-info.java
  9. 35 0
      crypto/src/main/java/org/springframework/security/crypto/keygen/BytesKeyGenerator.java
  10. 37 0
      crypto/src/main/java/org/springframework/security/crypto/keygen/HexEncodingStringKeyGenerator.java
  11. 54 0
      crypto/src/main/java/org/springframework/security/crypto/keygen/KeyGenerators.java
  12. 85 0
      crypto/src/main/java/org/springframework/security/crypto/keygen/SecureRandomBytesKeyGenerator.java
  13. 26 0
      crypto/src/main/java/org/springframework/security/crypto/keygen/StringKeyGenerator.java
  14. 6 0
      crypto/src/main/java/org/springframework/security/crypto/keygen/package-info.java
  15. 46 0
      crypto/src/main/java/org/springframework/security/crypto/password/NoOpPasswordEncoder.java
  16. 40 0
      crypto/src/main/java/org/springframework/security/crypto/password/PasswordEncoder.java
  17. 86 0
      crypto/src/main/java/org/springframework/security/crypto/password/StandardPasswordEncoder.java
  18. 5 0
      crypto/src/main/java/org/springframework/security/crypto/password/package-info.java
  19. 95 0
      crypto/src/main/java/org/springframework/security/crypto/util/CipherUtils.java
  20. 62 0
      crypto/src/main/java/org/springframework/security/crypto/util/Digester.java
  21. 122 0
      crypto/src/main/java/org/springframework/security/crypto/util/EncodingUtils.java
  22. 4 0
      crypto/src/main/java/org/springframework/security/crypto/util/package-info.java
  23. 49 0
      crypto/src/test/java/org/springframework/security/crypto/encrypt/EncryptorsTests.java
  24. 43 0
      crypto/src/test/java/org/springframework/security/crypto/keygen/KeyGeneratorsTests.java
  25. 25 0
      crypto/src/test/java/org/springframework/security/crypto/password/StandardPasswordEncoderTests.java
  26. 19 0
      crypto/src/test/java/org/springframework/security/crypto/util/DigesterTests.java
  27. 44 0
      crypto/src/test/java/org/springframework/security/crypto/util/EncodingUtilsTests.java
  28. 16 0
      crypto/template.mf
  29. 2 1
      settings.gradle

+ 5 - 0
crypto/crypto.gradle

@@ -0,0 +1,5 @@
+// crypto module build file
+
+dependencies {
+
+}

+ 34 - 0
crypto/src/main/java/org/springframework/security/crypto/encrypt/BytesEncryptor.java

@@ -0,0 +1,34 @@
+/*
+ * Copyright 2011 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.encrypt;
+
+/**
+ * Service interface for symmetric data encryption.
+ * @author Keith Donald
+ */
+public interface BytesEncryptor {
+
+    /**
+     * Encrypt the byte array.
+     */
+    byte[] encrypt(byte[] byteArray);
+
+    /**
+     * Decrypt the byte array.
+     */
+    byte[] decrypt(byte[] encryptedByteArray);
+
+}

+ 86 - 0
crypto/src/main/java/org/springframework/security/crypto/encrypt/Encryptors.java

@@ -0,0 +1,86 @@
+/*
+ * Copyright 2011 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.encrypt;
+
+/**
+ * Factory for commonly used encryptors.
+ * Defines the public API for constructing {@link BytesEncryptor} and {@link TextEncryptor} implementations.
+ * @author Keith Donald
+ */
+public class Encryptors {
+
+    /**
+     * Creates a standard password-based bytes encryptor.
+     * Uses MD5 PRF hashing with 1024 iterations and DES-based encryption.
+     * Salts each encrypted value to ensure it will be unique.
+     * TODO - switch standard algorithm from DES to AES.  Switch hashing to SHA-1 from MD5.
+     * @param password the password used to generate the encryptor's secret key; should not be shared
+     */
+    public static BytesEncryptor standard(String password) {
+        return new PasswordBasedBytesEncryptor(PBE_MD5_DES_ALGORITHM, password);
+    }
+
+    /**
+     * 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) {
+        return new HexEncodingTextEncryptor(standard(password));
+    }
+
+    /**
+     * Creates an encryptor for queryable text strings that uses standard password-based encryption.
+     * The hex-encoded salt string provided should be random and is used to protect against password dictionary attacks.
+     * Does not salt each encrypted value so an encrypted value may 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 initialize the cipher
+     */
+    public static TextEncryptor queryableText(String password, String salt) {
+        return new QueryableTextEncryptor(PBE_MD5_DES_ALGORITHM, password, salt);
+    }
+
+    /**
+     * Creates a text encrypter that performs no encryption.
+     * Useful for test environments where working with plain text strings is desired for simplicity.
+     */
+    public static TextEncryptor noOpText() {
+        return NO_OP_TEXT_INSTANCE;
+    }
+
+    // internal helpers
+
+    private Encryptors() {
+    }
+
+    private static final String PBE_MD5_DES_ALGORITHM = "PBEWithMD5AndDES";
+
+    private static final TextEncryptor NO_OP_TEXT_INSTANCE = new NoOpTextEncryptor();
+
+    private static final class NoOpTextEncryptor implements TextEncryptor {
+
+        public String encrypt(String text) {
+            return text;
+        }
+
+        public String decrypt(String encryptedText) {
+            return encryptedText;
+        }
+
+    }
+
+}

+ 45 - 0
crypto/src/main/java/org/springframework/security/crypto/encrypt/HexEncodingTextEncryptor.java

@@ -0,0 +1,45 @@
+/*
+ * Copyright 2011 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.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;
+
+/**
+ * Delegates to an {@link BytesEncryptor} to encrypt text strings.
+ * Raw text strings are UTF-8 encoded before being passed to the encryptor.
+ * Encrypted strings are returned hex-encoded.
+ * @author Keith Donald
+ */
+final class HexEncodingTextEncryptor implements TextEncryptor {
+
+    private final BytesEncryptor encryptor;
+
+    public HexEncodingTextEncryptor(BytesEncryptor encryptor) {
+        this.encryptor = encryptor;
+    }
+
+    public String encrypt(String text) {
+        return hexEncode(encryptor.encrypt(utf8Encode(text)));
+    }
+
+    public String decrypt(String encryptedText) {
+        return utf8Decode(encryptor.decrypt(hexDecode(encryptedText)));
+    }
+
+}

+ 84 - 0
crypto/src/main/java/org/springframework/security/crypto/encrypt/PasswordBasedBytesEncryptor.java

@@ -0,0 +1,84 @@
+/*
+ * Copyright 2011 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.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.util.EncodingUtils.concatenate;
+import static org.springframework.security.crypto.util.EncodingUtils.subArray;
+
+import javax.crypto.Cipher;
+import javax.crypto.SecretKey;
+
+import org.springframework.security.crypto.keygen.BytesKeyGenerator;
+import org.springframework.security.crypto.keygen.KeyGenerators;
+
+/**
+ * A general purpose encryptor for password-based encryption (PBEwith{prf}and{encryption} algorithms).
+ * Prepends a random salt to each encrypted value to aid in the prevention of password compromise with the aid of a dictionary/rainbow table.
+ * The salt allows the same secret key to be used for multiple encryption operations.
+ * The password should be not be shared.
+ * Note: {prf} = Pseudo random function e.g. MD5; {encryption} = Encryption method e.g. DES or AES.
+ * @author Keith Donald
+ */
+final class PasswordBasedBytesEncryptor implements BytesEncryptor {
+
+    private final SecretKey secretKey;
+
+    private final BytesKeyGenerator saltGenerator;
+
+    private final Cipher encryptor;
+
+    private final Cipher decryptor;
+
+    public PasswordBasedBytesEncryptor(String algorithm, String password) {
+        secretKey = newSecretKey(algorithm, password);
+        saltGenerator = KeyGenerators.secureRandom();
+        encryptor = newCipher(algorithm);
+        decryptor = newCipher(algorithm);
+    }
+
+    public byte[] encrypt(byte[] bytes) {
+        byte[] salt = saltGenerator.generateKey();
+        byte[] encrypted;
+        synchronized (encryptor) {
+            initCipher(encryptor, Cipher.ENCRYPT_MODE, secretKey, salt, 1024);
+            encrypted = doFinal(encryptor, bytes);
+        }
+        return concatenate(salt, encrypted);
+    }
+
+    public byte[] decrypt(byte[] encryptedBytes) {
+        byte[] salt = saltPart(encryptedBytes);
+        byte[] decrypted;
+        synchronized (decryptor) {
+            initCipher(decryptor, Cipher.DECRYPT_MODE, secretKey, salt, 1024);
+            decrypted = doFinal(decryptor, cipherPart(encryptedBytes, salt));
+        }
+        return decrypted;
+    }
+
+    private byte[] saltPart(byte[] encrypted) {
+        return subArray(encrypted, 0, saltGenerator.getKeyLength());
+    }
+
+    private byte[] cipherPart(byte[] encrypted, byte[] salt) {
+        return subArray(encrypted, salt.length, encrypted.length);
+    }
+
+}

+ 60 - 0
crypto/src/main/java/org/springframework/security/crypto/encrypt/QueryableTextEncryptor.java

@@ -0,0 +1,60 @@
+/*
+ * Copyright 2011 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.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.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 javax.crypto.Cipher;
+import javax.crypto.SecretKey;
+
+/**
+ * A text encryptor that applies password-based MD5 plus DES symmetric key encryption.
+ * Designed to be used to encrypt fields that are queryable; for example, an indexed field such as an OAuth apiKey.
+ * Requires a random site-global salt to protect against password dictionary attacks.
+ * Does not salt on each {@link #encrypt(String)} operation to allow the encrypted field to be queried.
+ * @author Keith Donald
+ */
+final class QueryableTextEncryptor implements TextEncryptor {
+
+    private final Cipher encryptor;
+
+    private final Cipher decryptor;
+
+    public QueryableTextEncryptor(String algorithm, String password, String salt) {
+        byte[] saltBytes = hexDecode(salt);
+        SecretKey secretKey = newSecretKey(algorithm, password);
+        encryptor = newCipher(algorithm);
+        initCipher(encryptor, Cipher.ENCRYPT_MODE, secretKey, saltBytes, 1000);
+        decryptor = newCipher(algorithm);
+        initCipher(decryptor, Cipher.DECRYPT_MODE, secretKey, saltBytes, 1000);
+    }
+
+    public String encrypt(String text) {
+        return hexEncode(doFinal(encryptor, utf8Encode(text)));
+    }
+
+    public String decrypt(String encryptedText) {
+        return utf8Decode(doFinal(decryptor, hexDecode(encryptedText)));
+    }
+
+}

+ 34 - 0
crypto/src/main/java/org/springframework/security/crypto/encrypt/TextEncryptor.java

@@ -0,0 +1,34 @@
+/*
+ * Copyright 2011 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.encrypt;
+
+/**
+ * Service interface for symmetric encryption of text strings.
+ * @author Keith Donald
+ */
+public interface TextEncryptor {
+
+    /**
+     * Encrypt the raw text string.
+     */
+    String encrypt(String text);
+
+    /**
+     * Decrypt the encrypted text string.
+     */
+    String decrypt(String encryptedText);
+
+}

+ 5 - 0
crypto/src/main/java/org/springframework/security/crypto/encrypt/package-info.java

@@ -0,0 +1,5 @@
+/**
+ * Symmetric-key data encryption.
+ */
+package org.springframework.security.crypto.encrypt;
+

+ 35 - 0
crypto/src/main/java/org/springframework/security/crypto/keygen/BytesKeyGenerator.java

@@ -0,0 +1,35 @@
+/*
+ * Copyright 2011 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;
+
+/**
+ * A generator for unique byte array-based keys.
+ * @author Keith Donald
+ */
+public interface BytesKeyGenerator {
+
+    /**
+     * Get the length, in bytes, of keys created by this generator.
+     * Most unique keys are at least 8 bytes in length.
+     */
+    int getKeyLength();
+
+    /**
+     * Generate a new key.
+     */
+    byte[] generateKey();
+
+}

+ 37 - 0
crypto/src/main/java/org/springframework/security/crypto/keygen/HexEncodingStringKeyGenerator.java

@@ -0,0 +1,37 @@
+/*
+ * Copyright 2011 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 static org.springframework.security.crypto.util.EncodingUtils.hexEncode;
+
+/**
+ * A StringKeyGenerator that generates hex-encoded String keys.
+ * Delegates to a {@link BytesKeyGenerator} for the actual key generation.
+ * @author Keith Donald
+ */
+final class HexEncodingStringKeyGenerator implements StringKeyGenerator {
+
+    private final BytesKeyGenerator keyGenerator;
+
+    public HexEncodingStringKeyGenerator(BytesKeyGenerator keyGenerator) {
+        this.keyGenerator = keyGenerator;
+    }
+
+    public String generateKey() {
+        return hexEncode(keyGenerator.generateKey());
+    }
+
+}

+ 54 - 0
crypto/src/main/java/org/springframework/security/crypto/keygen/KeyGenerators.java

@@ -0,0 +1,54 @@
+/*
+ * Copyright 2011 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.security.SecureRandom;
+
+/**
+ * Factory for commonly used key generators.
+ * Public API for constructing a {@link BytesKeyGenerator} or {@link StringKeyGenerator}.
+ * @author Keith Donald
+ */
+public class KeyGenerators {
+
+    /**
+     * Create a {@link BytesKeyGenerator} that uses a {@link SecureRandom} to generate keys of 8 bytes in length.
+     */
+    public static BytesKeyGenerator secureRandom() {
+        return new SecureRandomBytesKeyGenerator();
+    }
+
+    /**
+     * Create a {@link BytesKeyGenerator} that uses a {@link SecureRandom} to generate keys of a custom length.
+     * @param keyLength the key length in bytes, e.g. 16, for a 16 byte key.
+     */
+    public static BytesKeyGenerator secureRandom(int keyLength) {
+        return new SecureRandomBytesKeyGenerator(keyLength);
+    }
+
+    /**
+     * Creates a {@link StringKeyGenerator} that hex-encodes {@link SecureRandom} keys of 8 bytes in length.
+     * The hex-encoded string is keyLength * 2 characters in length.
+     */
+    public static StringKeyGenerator string() {
+        return new HexEncodingStringKeyGenerator(secureRandom());
+    }
+
+    // internal helpers
+
+    private KeyGenerators() {
+    }
+}

+ 85 - 0
crypto/src/main/java/org/springframework/security/crypto/keygen/SecureRandomBytesKeyGenerator.java

@@ -0,0 +1,85 @@
+/*
+ * Copyright 2011 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.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.SecureRandom;
+
+/**
+ * A KeyGenerator that uses SecureRandom to generate byte array-based keys.
+ * Defaults to 8 byte keys produced by the SHA1PRNG algorithm developed by the Sun Provider.
+ * @author Keith Donald
+ */
+final class SecureRandomBytesKeyGenerator implements BytesKeyGenerator {
+
+    private final SecureRandom random;
+
+    private final int keyLength;
+
+    /**
+     * Creates a secure random key generator using the defaults.
+     */
+    public SecureRandomBytesKeyGenerator() {
+        this(DEFAULT_ALGORITHM, DEFAULT_PROVIDER, DEFAULT_KEY_LENGTH);
+    }
+
+    /**
+     * Creates a secure random key generator with a custom key length.
+     */
+    public SecureRandomBytesKeyGenerator(int keyLength) {
+        this(DEFAULT_ALGORITHM, DEFAULT_PROVIDER, keyLength);
+    }
+
+    public int getKeyLength() {
+        return keyLength;
+    }
+
+    public byte[] generateKey() {
+        byte[] bytes = new byte[keyLength];
+        random.nextBytes(bytes);
+        return bytes;
+    }
+
+    // internal helpers
+
+    /**
+     * Creates a secure random key generator that is fully customized.
+     */
+    private SecureRandomBytesKeyGenerator(String algorithm, String provider, int keyLength) {
+        this.random = createSecureRandom(algorithm, provider, keyLength);
+        this.keyLength = keyLength;
+    }
+
+    private SecureRandom createSecureRandom(String algorithm, String provider, int keyLength) {
+        try {
+            SecureRandom random = SecureRandom.getInstance(algorithm, provider);
+            random.setSeed(random.generateSeed(keyLength));
+            return random;
+        } catch (NoSuchAlgorithmException e) {
+            throw new IllegalArgumentException("Not a supported SecureRandom key generation algorithm", e);
+        } catch (NoSuchProviderException e) {
+            throw new IllegalArgumentException("Not a supported SecureRandom key provider", e);
+        }
+    }
+
+    private static final String DEFAULT_ALGORITHM = "SHA1PRNG";
+
+    private static final String DEFAULT_PROVIDER = "SUN";
+
+    private static final int DEFAULT_KEY_LENGTH = 8;
+
+}

+ 26 - 0
crypto/src/main/java/org/springframework/security/crypto/keygen/StringKeyGenerator.java

@@ -0,0 +1,26 @@
+/*
+ * Copyright 2011 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;
+
+/**
+ * A generator for unique string keys.
+ * @author Keith Donald
+ */
+public interface StringKeyGenerator {
+
+    String generateKey();
+
+}

+ 6 - 0
crypto/src/main/java/org/springframework/security/crypto/keygen/package-info.java

@@ -0,0 +1,6 @@
+/**
+ * Secure random key generators.
+ */
+package org.springframework.security.crypto.keygen;
+
+

+ 46 - 0
crypto/src/main/java/org/springframework/security/crypto/password/NoOpPasswordEncoder.java

@@ -0,0 +1,46 @@
+/*
+ * Copyright 2011 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;
+
+/**
+ * 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 boolean matches(String rawPassword, String encodedPassword) {
+        return rawPassword.equals(encodedPassword);
+    }
+
+    /**
+     * Get the singleton {@link NoOpPasswordEncoder}.
+     */
+    public static PasswordEncoder getInstance() {
+        return INSTANCE;
+    }
+
+    private static final PasswordEncoder INSTANCE = new NoOpPasswordEncoder();
+
+    private NoOpPasswordEncoder() {
+
+    }
+
+}

+ 40 - 0
crypto/src/main/java/org/springframework/security/crypto/password/PasswordEncoder.java

@@ -0,0 +1,40 @@
+/*
+ * Copyright 2011 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;
+
+/**
+ * Service interface for encoding passwords.
+ * @author Keith Donald
+ */
+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.
+     */
+    String encode(String 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);
+
+}

+ 86 - 0
crypto/src/main/java/org/springframework/security/crypto/password/StandardPasswordEncoder.java

@@ -0,0 +1,86 @@
+/*
+ * Copyright 2011 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.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 java.util.Arrays;
+
+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.
+ * @author Keith Donald
+ */
+public final class StandardPasswordEncoder implements PasswordEncoder {
+
+    private final Digester digester;
+
+    private final byte[] secret;
+
+    private final BytesKeyGenerator saltGenerator;
+
+    /**
+     * Constructs a standard password encoder.
+     * @param secret the secret key used in the encoding process (should not be shared)
+     */
+    public StandardPasswordEncoder(String secret) {
+        this("SHA-256", "SUN", secret);
+    }
+
+    public String encode(String rawPassword) {
+        return encode(rawPassword, saltGenerator.generateKey());
+    }
+
+    public boolean matches(String rawPassword, String encodedPassword) {
+        byte[] digested = decode(encodedPassword);
+        byte[] salt = subArray(digested, 0, saltGenerator.getKeyLength());
+        return matches(digested, digest(rawPassword, salt));
+    }
+
+    // internal helpers
+
+    private StandardPasswordEncoder(String algorithm, String provider, String secret) {
+        this.digester = new Digester(algorithm, provider);
+        this.secret = utf8Encode(secret);
+        this.saltGenerator = KeyGenerators.secureRandom();
+    }
+
+    private String encode(String rawPassword, byte[] salt) {
+        byte[] digest = digest(rawPassword, salt);
+        return hexEncode(digest);
+    }
+
+    private byte[] digest(String rawPassword, byte[] salt) {
+        byte[] digest = digester.digest(concatenate(salt, secret, utf8Encode(rawPassword)));
+        return concatenate(salt, digest);
+    }
+
+    private byte[] decode(String encodedPassword) {
+        return hexDecode(encodedPassword);
+    }
+
+    private boolean matches(byte[] expected, byte[] actual) {
+        return Arrays.equals(expected, actual);
+    }
+
+}

+ 5 - 0
crypto/src/main/java/org/springframework/security/crypto/password/package-info.java

@@ -0,0 +1,5 @@
+/**
+ * Password encoders.
+ */
+package org.springframework.security.crypto.password;
+

+ 95 - 0
crypto/src/main/java/org/springframework/security/crypto/util/CipherUtils.java

@@ -0,0 +1,95 @@
+/*
+ * Copyright 2011 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.util;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.spec.InvalidKeySpecException;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.PBEKeySpec;
+import javax.crypto.spec.PBEParameterSpec;
+
+/**
+ * Static helper for working with the Cipher API.
+ * @author Keith Donald
+ */
+public class CipherUtils {
+
+    /**
+     * Generates a SecretKey.
+     */
+    public static SecretKey newSecretKey(String algorithm, String secret) {
+        try {
+            PBEKeySpec pbeKeySpec = new PBEKeySpec(secret.toCharArray());
+            SecretKeyFactory factory = SecretKeyFactory.getInstance(algorithm);
+            return factory.generateSecret(pbeKeySpec);
+        } catch (NoSuchAlgorithmException e) {
+            throw new IllegalArgumentException("Not a valid encryption algorithm", e);
+        } catch (InvalidKeySpecException e) {
+            throw new IllegalArgumentException("Not a valid secert key", e);
+        }
+    }
+
+    /**
+     * Constructs a new Cipher.
+     */
+    public static Cipher newCipher(String algorithm) {
+        try {
+            return Cipher.getInstance(algorithm);
+        } catch (NoSuchAlgorithmException e) {
+            throw new IllegalArgumentException("Not a valid encryption algorithm", e);
+        } catch (NoSuchPaddingException e) {
+            throw new IllegalStateException("Should not happen", e);
+        }
+    }
+
+    /**
+     * Initializes the Cipher for use.
+     */
+    public static void initCipher(Cipher cipher, int mode, SecretKey secretKey, byte[] salt, int iterationCount) {
+        try {
+            cipher.init(mode, secretKey, new PBEParameterSpec(salt, iterationCount));
+        } catch (InvalidKeyException e) {
+            throw new IllegalArgumentException("Unable to initialize due to invalid secret key", e);
+        } catch (InvalidAlgorithmParameterException e) {
+            throw new IllegalStateException("Unable to initialize due to invalid decryption parameter spec", e);
+        }
+    }
+
+    /**
+     * Invokes the Cipher to perform encryption or decryption (depending on the initialized mode).
+     */
+    public static byte[] doFinal(Cipher cipher, byte[] input) {
+        try {
+            return cipher.doFinal(input);
+        } catch (IllegalBlockSizeException e) {
+            throw new IllegalStateException("Unable to invoke Cipher due to illegal block size", e);
+        } catch (BadPaddingException e) {
+            throw new IllegalStateException("Unable to invoke Cipher due to bad padding", e);
+        }
+    }
+
+    private CipherUtils() {
+    }
+
+}

+ 62 - 0
crypto/src/main/java/org/springframework/security/crypto/util/Digester.java

@@ -0,0 +1,62 @@
+/*
+ * Copyright 2011 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.util;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+
+/**
+ * Helper for working with the MessageDigest API.
+ * Performs 1024 iterations of the hashing algorithm per digest to aid in protecting against brute force attacks.
+ * @author Keith Donald
+ */
+public class Digester {
+
+    private final MessageDigest messageDigest;
+
+    private final int iterations = 1024;
+
+    /**
+     * Create a new Digester.
+     * @param algorithm the digest algorithm; for example, "SHA-1" or "SHA-256".
+     * @param provider the provider of the digest algorithm, for example "SUN".
+     */
+    public Digester(String algorithm, String provider) {
+        try {
+            messageDigest = MessageDigest.getInstance(algorithm, provider);
+        } catch (NoSuchAlgorithmException e) {
+            throw new IllegalStateException("No such hashing algorithm", e);
+        } catch (NoSuchProviderException e) {
+            throw new IllegalStateException("No such provider for hashing algorithm", e);
+        }
+    }
+
+    public byte[] digest(byte[] value) {
+        synchronized (messageDigest) {
+            for (int i = 0; i < (iterations - 1); i++) {
+                invokeDigest(value);
+            }
+            return messageDigest.digest(value);
+        }
+    }
+
+    private byte[] invokeDigest(byte[] value) {
+        messageDigest.reset();
+        return messageDigest.digest(value);
+    }
+
+}

+ 122 - 0
crypto/src/main/java/org/springframework/security/crypto/util/EncodingUtils.java

@@ -0,0 +1,122 @@
+/*
+ * Copyright 2011 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.util;
+
+import java.io.UnsupportedEncodingException;
+
+/**
+ * Static helper for encoding data.
+ * @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.
+     */
+    public static byte[] concatenate(byte[]... arrays) {
+        int length = 0;
+        for (byte[] array : arrays) {
+            length += array.length;
+        }
+        byte[] newArray = new byte[length];
+        int destPos = 0;
+        for (byte[] array : arrays) {
+            System.arraycopy(array, 0, newArray, destPos, array.length);
+            destPos += array.length;
+        }
+        return newArray;
+    }
+
+    /**
+     * Extract a sub array of bytes out of the byte array.
+     * @param array the byte array to extract from
+     * @param beginIndex the beginning index of the sub array, inclusive
+     * @param endIndex the ending index of the sub array, exclusive
+     */
+    public static byte[] subArray(byte[] array, int beginIndex, int endIndex) {
+        int length = endIndex - beginIndex;
+        byte[] subarray = new byte[length];
+        System.arraycopy(array, beginIndex, subarray, 0, length);
+        return subarray;
+    }
+
+    private EncodingUtils() {
+    }
+
+    private static RuntimeException encodingException(UnsupportedEncodingException e) {
+        return new IllegalStateException("UTF-8 is not an available char set", e);
+    }
+
+}

+ 4 - 0
crypto/src/main/java/org/springframework/security/crypto/util/package-info.java

@@ -0,0 +1,4 @@
+/**
+ * Shared crypto utilities.
+ */
+package org.springframework.security.crypto.util;

+ 49 - 0
crypto/src/test/java/org/springframework/security/crypto/encrypt/EncryptorsTests.java

@@ -0,0 +1,49 @@
+package org.springframework.security.crypto.encrypt;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+public class EncryptorsTests {
+
+    @Test
+    public void standard() {
+        BytesEncryptor encryptor = Encryptors.standard("password");
+        byte[] result = encryptor.encrypt("text".getBytes());
+        assertNotNull(result);
+        assertFalse(new String(result).equals("text"));
+        assertEquals("text", new String(encryptor.decrypt(result)));
+        assertFalse(new String(result).equals(new String(encryptor.encrypt("text".getBytes()))));
+    }
+
+    @Test
+    public void text() {
+        TextEncryptor encryptor = Encryptors.text("password");
+        String result = encryptor.encrypt("text");
+        assertNotNull(result);
+        assertFalse(result.equals("text"));
+        assertEquals("text", encryptor.decrypt(result));
+        assertFalse(result.equals(encryptor.encrypt("text")));
+    }
+
+    @Test
+    public void queryableText() {
+        TextEncryptor encryptor = Encryptors.queryableText("password", "5c0744940b5c369b");
+        String result = encryptor.encrypt("text");
+        assertNotNull(result);
+        assertFalse(result.equals("text"));
+        assertEquals("text", encryptor.decrypt(result));
+        assertTrue(result.equals(encryptor.encrypt("text")));
+    }
+
+    @Test
+    public void noOpText() {
+        TextEncryptor encryptor = Encryptors.noOpText();
+        assertEquals("text", encryptor.encrypt("text"));
+        assertEquals("text", encryptor.decrypt("text"));
+    }
+
+}

+ 43 - 0
crypto/src/test/java/org/springframework/security/crypto/keygen/KeyGeneratorsTests.java

@@ -0,0 +1,43 @@
+package org.springframework.security.crypto.keygen;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+import java.util.Arrays;
+
+import org.junit.Test;
+import org.springframework.security.crypto.util.EncodingUtils;
+
+public class KeyGeneratorsTests {
+
+    @Test
+    public void secureRandom() {
+        BytesKeyGenerator keyGenerator = KeyGenerators.secureRandom();
+        assertEquals(8, keyGenerator.getKeyLength());
+        byte[] key = keyGenerator.generateKey();
+        assertEquals(8, key.length);
+        byte[] key2 = keyGenerator.generateKey();
+        assertFalse(Arrays.equals(key, key2));
+    }
+
+    @Test
+    public void secureRandomCustomLength() {
+        BytesKeyGenerator keyGenerator = KeyGenerators.secureRandom(16);
+        assertEquals(16, keyGenerator.getKeyLength());
+        byte[] key = keyGenerator.generateKey();
+        assertEquals(16, key.length);
+        byte[] key2 = keyGenerator.generateKey();
+        assertFalse(Arrays.equals(key, key2));
+    }
+
+    @Test
+    public void string() {
+        StringKeyGenerator keyGenerator = KeyGenerators.string();
+        String hexStringKey = keyGenerator.generateKey();
+        assertEquals(16, hexStringKey.length());
+        assertEquals(8, EncodingUtils.hexDecode(hexStringKey).length);
+        String hexStringKey2 = keyGenerator.generateKey();
+        assertFalse(hexStringKey.equals(hexStringKey2));
+    }
+
+}

+ 25 - 0
crypto/src/test/java/org/springframework/security/crypto/password/StandardPasswordEncoderTests.java

@@ -0,0 +1,25 @@
+package org.springframework.security.crypto.password;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+public class StandardPasswordEncoderTests {
+
+    private StandardPasswordEncoder encoder = new StandardPasswordEncoder("secret");
+
+    @Test
+    public void matches() {
+        String result = encoder.encode("password");
+        assertFalse(result.equals("password"));
+        assertTrue(encoder.matches("password", result));
+    }
+
+    @Test
+    public void notMatches() {
+        String result = encoder.encode("password");
+        assertFalse(encoder.matches("bogus", result));
+    }
+
+}

+ 19 - 0
crypto/src/test/java/org/springframework/security/crypto/util/DigesterTests.java

@@ -0,0 +1,19 @@
+package org.springframework.security.crypto.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+import org.junit.Test;
+
+public class DigesterTests {
+
+    private Digester digester = new Digester("SHA-1", "SUN");
+
+    @Test
+    public void digest() {
+        byte[] result = digester.digest("text".getBytes());
+        assertEquals(20, result.length);
+        assertFalse(new String(result).equals("text"));
+    }
+
+}

+ 44 - 0
crypto/src/test/java/org/springframework/security/crypto/util/EncodingUtilsTests.java

@@ -0,0 +1,44 @@
+package org.springframework.security.crypto.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Arrays;
+
+import org.junit.Test;
+
+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);
+        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");
+        assertTrue(Arrays.equals(bytes, result));
+    }
+
+    @Test
+    public void concatenate() {
+        byte[] bytes = new byte[] { (byte)0x01, (byte)0xFF, (byte)65, (byte)66, (byte)67, (byte)0xC0, (byte)0xC1, (byte)0xC2 };
+        byte[] one = new byte[] { (byte)0x01 };
+        byte[] two = new byte[] { (byte)0xFF, (byte)65, (byte)66 };
+        byte[] three = new byte[] { (byte)67, (byte)0xC0, (byte)0xC1, (byte)0xC2 };
+        assertTrue(Arrays.equals(bytes, EncodingUtils.concatenate(one, two, three)));
+    }
+
+    @Test
+    public void subArray() {
+        byte[] bytes = new byte[] { (byte)0x01, (byte)0xFF, (byte)65, (byte)66, (byte)67, (byte)0xC0, (byte)0xC1, (byte)0xC2 };
+        byte[] two = new byte[] { (byte)0xFF, (byte)65, (byte)66 };
+        byte[] subArray = EncodingUtils.subArray(bytes, 1, 4);
+        assertEquals(3, subArray.length);
+        assertTrue(Arrays.equals(two, subArray));
+    }
+
+}

+ 16 - 0
crypto/template.mf

@@ -0,0 +1,16 @@
+Implementation-Title: org.springframework.security.crypto
+Implementation-Version: ${version}
+Bundle-SymbolicName: org.springframework.security.crypto
+Bundle-Name: Spring Security Web
+Bundle-Vendor: SpringSource
+Bundle-Version: ${version}
+Bundle-ManifestVersion: 2
+Excluded-Imports:
+ javax.naming.*,
+ javax.rmi.*,
+ javax.sql.*,
+ javax.security.auth.*
+Ignored-Existing-Headers:
+ Import-Package,
+ Export-Package
+Import-Template:

+ 2 - 1
settings.gradle

@@ -7,7 +7,8 @@ def String[] modules = [
     'cas',
     'openid',
     'taglibs',
-    'aspects'
+    'aspects',
+    'crypto'
 ]
 
 def String[] samples = [