diff --git a/crypto/crypto.gradle b/crypto/crypto.gradle new file mode 100644 index 0000000000..fa05236191 --- /dev/null +++ b/crypto/crypto.gradle @@ -0,0 +1,5 @@ +// crypto module build file + +dependencies { + +} diff --git a/crypto/src/main/java/org/springframework/security/crypto/encrypt/BytesEncryptor.java b/crypto/src/main/java/org/springframework/security/crypto/encrypt/BytesEncryptor.java new file mode 100644 index 0000000000..3c820caba0 --- /dev/null +++ b/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); + +} diff --git a/crypto/src/main/java/org/springframework/security/crypto/encrypt/Encryptors.java b/crypto/src/main/java/org/springframework/security/crypto/encrypt/Encryptors.java new file mode 100644 index 0000000000..b3d9a7b7c9 --- /dev/null +++ b/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; + } + + } + +} \ No newline at end of file diff --git a/crypto/src/main/java/org/springframework/security/crypto/encrypt/HexEncodingTextEncryptor.java b/crypto/src/main/java/org/springframework/security/crypto/encrypt/HexEncodingTextEncryptor.java new file mode 100644 index 0000000000..a06e1f042a --- /dev/null +++ b/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))); + } + +} diff --git a/crypto/src/main/java/org/springframework/security/crypto/encrypt/PasswordBasedBytesEncryptor.java b/crypto/src/main/java/org/springframework/security/crypto/encrypt/PasswordBasedBytesEncryptor.java new file mode 100644 index 0000000000..a0b6d2c297 --- /dev/null +++ b/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); + } + +} \ No newline at end of file diff --git a/crypto/src/main/java/org/springframework/security/crypto/encrypt/QueryableTextEncryptor.java b/crypto/src/main/java/org/springframework/security/crypto/encrypt/QueryableTextEncryptor.java new file mode 100644 index 0000000000..f789463808 --- /dev/null +++ b/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))); + } + +} diff --git a/crypto/src/main/java/org/springframework/security/crypto/encrypt/TextEncryptor.java b/crypto/src/main/java/org/springframework/security/crypto/encrypt/TextEncryptor.java new file mode 100644 index 0000000000..c62748751c --- /dev/null +++ b/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); + +} diff --git a/crypto/src/main/java/org/springframework/security/crypto/encrypt/package-info.java b/crypto/src/main/java/org/springframework/security/crypto/encrypt/package-info.java new file mode 100644 index 0000000000..f1ed951c6b --- /dev/null +++ b/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; + diff --git a/crypto/src/main/java/org/springframework/security/crypto/keygen/BytesKeyGenerator.java b/crypto/src/main/java/org/springframework/security/crypto/keygen/BytesKeyGenerator.java new file mode 100644 index 0000000000..48bae02762 --- /dev/null +++ b/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(); + +} diff --git a/crypto/src/main/java/org/springframework/security/crypto/keygen/HexEncodingStringKeyGenerator.java b/crypto/src/main/java/org/springframework/security/crypto/keygen/HexEncodingStringKeyGenerator.java new file mode 100644 index 0000000000..b371525154 --- /dev/null +++ b/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()); + } + +} \ No newline at end of file diff --git a/crypto/src/main/java/org/springframework/security/crypto/keygen/KeyGenerators.java b/crypto/src/main/java/org/springframework/security/crypto/keygen/KeyGenerators.java new file mode 100644 index 0000000000..f12d7f7477 --- /dev/null +++ b/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() { + } +} diff --git a/crypto/src/main/java/org/springframework/security/crypto/keygen/SecureRandomBytesKeyGenerator.java b/crypto/src/main/java/org/springframework/security/crypto/keygen/SecureRandomBytesKeyGenerator.java new file mode 100644 index 0000000000..eebb73733e --- /dev/null +++ b/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; + +} \ No newline at end of file diff --git a/crypto/src/main/java/org/springframework/security/crypto/keygen/StringKeyGenerator.java b/crypto/src/main/java/org/springframework/security/crypto/keygen/StringKeyGenerator.java new file mode 100644 index 0000000000..65965c50cb --- /dev/null +++ b/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(); + +} \ No newline at end of file diff --git a/crypto/src/main/java/org/springframework/security/crypto/keygen/package-info.java b/crypto/src/main/java/org/springframework/security/crypto/keygen/package-info.java new file mode 100644 index 0000000000..7753ded001 --- /dev/null +++ b/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; + + diff --git a/crypto/src/main/java/org/springframework/security/crypto/password/NoOpPasswordEncoder.java b/crypto/src/main/java/org/springframework/security/crypto/password/NoOpPasswordEncoder.java new file mode 100644 index 0000000000..8ec5f3d563 --- /dev/null +++ b/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() { + + } + +} diff --git a/crypto/src/main/java/org/springframework/security/crypto/password/PasswordEncoder.java b/crypto/src/main/java/org/springframework/security/crypto/password/PasswordEncoder.java new file mode 100644 index 0000000000..981f7f12f3 --- /dev/null +++ b/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); + +} diff --git a/crypto/src/main/java/org/springframework/security/crypto/password/StandardPasswordEncoder.java b/crypto/src/main/java/org/springframework/security/crypto/password/StandardPasswordEncoder.java new file mode 100644 index 0000000000..71a8be7394 --- /dev/null +++ b/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); + } + +} \ No newline at end of file diff --git a/crypto/src/main/java/org/springframework/security/crypto/password/package-info.java b/crypto/src/main/java/org/springframework/security/crypto/password/package-info.java new file mode 100644 index 0000000000..b3ff0bc39e --- /dev/null +++ b/crypto/src/main/java/org/springframework/security/crypto/password/package-info.java @@ -0,0 +1,5 @@ +/** + * Password encoders. + */ +package org.springframework.security.crypto.password; + diff --git a/crypto/src/main/java/org/springframework/security/crypto/util/CipherUtils.java b/crypto/src/main/java/org/springframework/security/crypto/util/CipherUtils.java new file mode 100644 index 0000000000..b01e976c39 --- /dev/null +++ b/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() { + } + +} diff --git a/crypto/src/main/java/org/springframework/security/crypto/util/Digester.java b/crypto/src/main/java/org/springframework/security/crypto/util/Digester.java new file mode 100644 index 0000000000..7c8e51a10d --- /dev/null +++ b/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); + } + +} \ No newline at end of file diff --git a/crypto/src/main/java/org/springframework/security/crypto/util/EncodingUtils.java b/crypto/src/main/java/org/springframework/security/crypto/util/EncodingUtils.java new file mode 100644 index 0000000000..32425f3999 --- /dev/null +++ b/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); + } + +} diff --git a/crypto/src/main/java/org/springframework/security/crypto/util/package-info.java b/crypto/src/main/java/org/springframework/security/crypto/util/package-info.java new file mode 100644 index 0000000000..34dd212323 --- /dev/null +++ b/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; diff --git a/crypto/src/test/java/org/springframework/security/crypto/encrypt/EncryptorsTests.java b/crypto/src/test/java/org/springframework/security/crypto/encrypt/EncryptorsTests.java new file mode 100644 index 0000000000..c460bf410b --- /dev/null +++ b/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")); + } + +} diff --git a/crypto/src/test/java/org/springframework/security/crypto/keygen/KeyGeneratorsTests.java b/crypto/src/test/java/org/springframework/security/crypto/keygen/KeyGeneratorsTests.java new file mode 100644 index 0000000000..fc4a113c6b --- /dev/null +++ b/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)); + } + +} diff --git a/crypto/src/test/java/org/springframework/security/crypto/password/StandardPasswordEncoderTests.java b/crypto/src/test/java/org/springframework/security/crypto/password/StandardPasswordEncoderTests.java new file mode 100644 index 0000000000..1395034b8f --- /dev/null +++ b/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)); + } + +} diff --git a/crypto/src/test/java/org/springframework/security/crypto/util/DigesterTests.java b/crypto/src/test/java/org/springframework/security/crypto/util/DigesterTests.java new file mode 100644 index 0000000000..bf4b775f1b --- /dev/null +++ b/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")); + } + +} diff --git a/crypto/src/test/java/org/springframework/security/crypto/util/EncodingUtilsTests.java b/crypto/src/test/java/org/springframework/security/crypto/util/EncodingUtilsTests.java new file mode 100644 index 0000000000..baa85ed0f2 --- /dev/null +++ b/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)); + } + +} diff --git a/crypto/template.mf b/crypto/template.mf new file mode 100644 index 0000000000..7a74e9dd11 --- /dev/null +++ b/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: \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index 774a0af408..4d77946e9b 100644 --- a/settings.gradle +++ b/settings.gradle @@ -7,7 +7,8 @@ def String[] modules = [ 'cas', 'openid', 'taglibs', - 'aspects' + 'aspects', + 'crypto' ] def String[] samples = [