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

This commit is contained in:
Keith Donald 2011-01-13 22:40:47 -05:00 committed by Luke Taylor
parent afd586c96e
commit ffa7301e7f
29 changed files with 1254 additions and 1 deletions

5
crypto/crypto.gradle Normal file
View File

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

View File

@ -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);
}

View File

@ -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;
}
}
}

View File

@ -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)));
}
}

View File

@ -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);
}
}

View File

@ -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)));
}
}

View File

@ -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);
}

View File

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

View File

@ -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();
}

View File

@ -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());
}
}

View File

@ -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() {
}
}

View File

@ -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;
}

View File

@ -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();
}

View File

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

View File

@ -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() {
}
}

View File

@ -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);
}

View File

@ -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);
}
}

View File

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

View File

@ -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() {
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

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

View File

@ -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"));
}
}

View File

@ -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));
}
}

View File

@ -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));
}
}

View File

@ -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"));
}
}

View File

@ -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
crypto/template.mf Normal file
View File

@ -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:

View File

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