org.jasypt.encryption.pbe.StandardPBEByteEncryptor Maven / Gradle / Ivy
/*
* =============================================================================
*
* Copyright (c) 2007-2010, The JASYPT team (http://www.jasypt.org)
*
* 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.jasypt.encryption.pbe;
import java.lang.reflect.Constructor;
import java.security.InvalidKeyException;
import java.security.Provider;
import java.security.spec.AlgorithmParameterSpec;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.PBEParameterSpec;
import org.jasypt.commons.CommonUtils;
import org.jasypt.encryption.pbe.config.PBECleanablePasswordConfig;
import org.jasypt.encryption.pbe.config.PBEConfig;
import org.jasypt.exceptions.AlreadyInitializedException;
import org.jasypt.exceptions.EncryptionInitializationException;
import org.jasypt.exceptions.EncryptionOperationNotPossibleException;
import org.jasypt.iv.IvGenerator;
import org.jasypt.iv.NoIvGenerator;
import org.jasypt.normalization.Normalizer;
import org.jasypt.salt.FixedSaltGenerator;
import org.jasypt.salt.RandomSaltGenerator;
import org.jasypt.salt.SaltGenerator;
/**
*
* Standard implementation of the {@link PBEByteEncryptor} interface.
* This class lets the user specify the algorithm, provider and
* the initialization vector (IV) generator to be used for
* encryption, the password to use,
* the number of hashing iterations and the salt generator
* that will be applied for obtaining the encryption key.
*
*
* This class is thread-safe.
*
*
*
Configuration
*
*
* The algorithm, provider, IV generator, password, key-obtention iterations
* and salt generator can take values in any of these ways:
*
* - Using its default values (except for password).
* - Setting a {@link org.jasypt.encryption.pbe.config.PBEConfig}
* object which provides new
* configuration values.
* - Calling the corresponding setAlgorithm(...),
* setProvider(...), setProviderName(...),
* setIvGenerator(...),
* setPassword(...), setKeyObtentionIterations(...) or
* setSaltGenerator(...) methods.
*
* And the actual values to be used for initialization will be established
* by applying the following priorities:
*
* - First, the default values are considered (except for password).
* - Then, if a {@link org.jasypt.encryption.pbe.config.PBEConfig}
* object has been set with
* setConfig(...), the non-null values returned by its
* getX() methods override the default values.
* - Finally, if the corresponding setX(...) method has been called
* on the encryptor itself for any of the configuration parameters, the
* values set by these calls override all of the above.
*
*
*
*
*
Initialization
*
*
* Before it is ready to encrypt, an object of this class has to be
* initialized. Initialization happens:
*
* - When initialize() is called.
* - When encrypt(...) or decrypt(...) are called for the
* first time, if initialize() has not been called before.
*
* Once an encryptor has been initialized, trying to
* change its configuration will
* result in an AlreadyInitializedException being thrown.
*
*
*
*
Usage
*
*
* An encryptor may be used for:
*
* - Encrypting messages, by calling the encrypt(...) method.
* - Decrypting messages, by calling the decrypt(...) method.
*
* If a random salt generator is used, two encryption results for
* the same message will always be different
* (except in the case of random salt coincidence). This may enforce
* security by difficulting brute force attacks on sets of data at a time
* and forcing attackers to perform a brute force attack on each separate
* piece of encrypted data.
* The same applies when a random IV generator is used.
*
*
* To learn more about the mechanisms involved in encryption, read
* PKCS #5: Password-Based Cryptography Standard.
*
*
* @since 1.0
*
* @author Daniel Fernández
*
*/
public final class StandardPBEByteEncryptor implements PBEByteCleanablePasswordEncryptor {
/**
* The default algorithm to be used if none specified: PBEWithMD5AndDES.
*/
public static final String DEFAULT_ALGORITHM = "PBEWithMD5AndDES";
/**
* The default number of hashing iterations applied for obtaining the
* encryption key from the specified password, set to 1000.
*/
public static final int DEFAULT_KEY_OBTENTION_ITERATIONS = 1000;
/**
* The default salt size, only used if the chosen encryption algorithm
* is not a block algorithm and thus block size cannot be used as salt size.
*/
public static final int DEFAULT_SALT_SIZE_BYTES = 8;
/**
* The default IV size, only used if the chosen encryption algorithm
* is not a block algorithm and thus block size cannot be used as IV size.
*/
public static final int DEFAULT_IV_SIZE_BYTES = 16;
// Algorithm (and provider-related info) for Password Based Encoding.
private String algorithm = DEFAULT_ALGORITHM;
private String providerName = null;
private Provider provider = null;
// Password to be applied. This will NOT have a default value. If none
// is set during configuration, an exception will be thrown.
private char[] password = null;
// Number of hashing iterations to be applied for obtaining the encryption
// key from the specified password.
private int keyObtentionIterations = DEFAULT_KEY_OBTENTION_ITERATIONS;
// SaltGenerator to be used. Initialization of a salt generator is costly,
// and so default value will be applied only in initialize(), if it finally
// becomes necessary.
private SaltGenerator saltGenerator = null;
// Size in bytes of the salt to be used for obtaining the
// encryption key. This size will depend on the PBE algorithm being used,
// and it will be set to the size of the block for the specific
// chosen algorithm (if the algorithm is not a block algorithm, the
// default value will be used).
private int saltSizeBytes = DEFAULT_SALT_SIZE_BYTES;
// IvGenerator to be used. Initialization of a IV generator is costly,
// and so default value will be applied only in initialize(), if it finally
// becomes necessary.
private IvGenerator ivGenerator = null;
// Size in bytes of the IV. This size will depend on the PBE algorithm
// being used, and it will be set to the size of the block for the specific
// chosen algorithm (if the algorithm is not a block algorithm, the
// default value will be used).
private int ivSizeBytes = DEFAULT_IV_SIZE_BYTES;
// Config object set (optionally).
private PBEConfig config = null;
/*
* Set of booleans which indicate whether the config or default values
* have to be overriden because of the setX methods having been
* called.
*/
private boolean algorithmSet = false;
private boolean passwordSet = false;
private boolean iterationsSet = false;
private boolean saltGeneratorSet = false;
private boolean ivGeneratorSet = false;
private boolean providerNameSet = false;
private boolean providerSet = false;
/*
* Flag which indicates whether the encryptor has been initialized or not.
*
* Once initialized, no further modifications to its configuration will
* be allowed.
*/
private boolean initialized = false;
// Encryption key generated.
private SecretKey key = null;
// Ciphers to be used for encryption and decryption.
private Cipher encryptCipher = null;
private Cipher decryptCipher = null;
// Flag which indicates whether the salt generator being used is a
// FixedSaltGenerator implementation (in which case some optimizations can
// be applied).
private boolean optimizingDueFixedSalt = false;
private byte[] fixedSaltInUse = null;
/**
* Creates a new instance of StandardPBEByteEncryptor.
*/
public StandardPBEByteEncryptor() {
super();
}
/**
*
* Sets a {@link org.jasypt.encryption.pbe.config.PBEConfig} object
* for the encryptor. If this config
* object is set, it will be asked values for:
*
*
*
* - Algorithm
* - Security Provider (or provider name)
* - Password
* - Hashing iterations for obtaining the encryption key
* - Salt generator
* - IV generator
*
*
*
* The non-null values it returns will override the default ones,
* and will be overriden by any values specified with a setX
* method.
*
*
* @param config the PBEConfig object to be used as the
* source for configuration parameters.
*/
public synchronized void setConfig(PBEConfig config) {
CommonUtils.validateNotNull(config, "Config cannot be set null");
if (isInitialized()) {
throw new AlreadyInitializedException();
}
this.config = config;
}
/**
*
* Sets the algorithm to be used for encryption, like
* PBEWithMD5AndDES.
*
*
* This algorithm has to be supported by your JCE provider (if you specify
* one, or the default JVM provider if you don't) and, if it is supported,
* you can also specify mode and padding for
* it, like ALGORITHM/MODE/PADDING.
*
*
* @param algorithm the name of the algorithm to be used.
*/
public synchronized void setAlgorithm(String algorithm) {
CommonUtils.validateNotEmpty(algorithm, "Algorithm cannot be set empty");
if (isInitialized()) {
throw new AlreadyInitializedException();
}
this.algorithm = algorithm;
this.algorithmSet = true;
}
/**
*
* Sets the password to be used.
*
*
* There is no default value for password, so not setting
* this parameter either from a
* {@link org.jasypt.encryption.pbe.config.PBEConfig} object or from
* a call to setPassword will result in an
* EncryptionInitializationException being thrown during initialization.
*
*
* @param password the password to be used.
*/
public synchronized void setPassword(String password) {
CommonUtils.validateNotEmpty(password, "Password cannot be set empty");
if (isInitialized()) {
throw new AlreadyInitializedException();
}
if (this.password != null) {
// We clean the old password, if there is one.
cleanPassword(this.password);
}
this.password = password.toCharArray();
this.passwordSet = true;
}
/**
*
* Sets the password to be used, as a char[].
*
*
* This allows the password to be specified as a cleanable
* char[] instead of a String, in extreme security conscious environments
* in which no copy of the password as an immutable String should
* be kept in memory.
*
*
* Important: the array specified as a parameter WILL BE COPIED
* in order to be stored as encryptor configuration. The caller of
* this method will therefore be responsible for its cleaning (jasypt
* will only clean the internally stored copy).
*
*
* There is no default value for password, so not setting
* this parameter either from a
* {@link org.jasypt.encryption.pbe.config.PBEConfig} object or from
* a call to setPassword will result in an
* EncryptionInitializationException being thrown during initialization.
*
*
* @since 1.8
*
* @param password the password to be used.
*/
public synchronized void setPasswordCharArray(char[] password) {
CommonUtils.validateNotNull(password, "Password cannot be set null");
CommonUtils.validateIsTrue(password.length > 0, "Password cannot be set empty");
if (isInitialized()) {
throw new AlreadyInitializedException();
}
if (this.password != null) {
// We clean the old password, if there is one.
cleanPassword(this.password);
}
this.password = new char[password.length];
System.arraycopy(password, 0, this.password, 0, password.length);
this.passwordSet = true;
}
/**
*
* Set the number of hashing iterations applied to obtain the
* encryption key.
*
*
* This mechanism is explained in
* PKCS #5: Password-Based Cryptography Standard.
*
*
* @param keyObtentionIterations the number of iterations
*/
public synchronized void setKeyObtentionIterations(
int keyObtentionIterations) {
CommonUtils.validateIsTrue(keyObtentionIterations > 0,
"Number of iterations for key obtention must be " +
"greater than zero");
if (isInitialized()) {
throw new AlreadyInitializedException();
}
this.keyObtentionIterations = keyObtentionIterations;
this.iterationsSet = true;
}
/**
*
* Sets the salt generator to be used. If no salt generator is specified,
* an instance of {@link org.jasypt.salt.RandomSaltGenerator} will be used.
*
*
* @param saltGenerator the salt generator to be used.
*/
public synchronized void setSaltGenerator(SaltGenerator saltGenerator) {
CommonUtils.validateNotNull(saltGenerator, "Salt generator cannot be set null");
if (isInitialized()) {
throw new AlreadyInitializedException();
}
this.saltGenerator = saltGenerator;
this.saltGeneratorSet = true;
}
/**
*
* Sets the IV generator to be used. If no IV generator is specified,
* an instance of {@link org.jasypt.iv.NoIvGenerator} will be used.
*
*
* @param ivGenerator the IV generator to be used.
*/
public synchronized void setIvGenerator(IvGenerator ivGenerator) { ;
if (isInitialized()) {
throw new AlreadyInitializedException();
}
this.ivGenerator = ivGenerator;
this.ivGeneratorSet = true;
}
/**
*
* Sets the name of the security provider to be asked for the
* encryption algorithm. This security provider has to be registered
* beforehand at the JVM security framework.
*
*
* The provider can also be set with the {@link #setProvider(Provider)}
* method, in which case it will not be necessary neither registering
* the provider beforehand,
* nor calling this {@link #setProviderName(String)} method to specify
* a provider name.
*
*
* Note that a call to {@link #setProvider(Provider)} overrides any value
* set by this method.
*
*
* If no provider name / provider is explicitly set, the default JVM
* provider will be used.
*
*
* @since 1.3
*
* @param providerName the name of the security provider to be asked
* for the encryption algorithm.
*/
public synchronized void setProviderName(String providerName) {
CommonUtils.validateNotNull(providerName, "Provider name cannot be set null");
if (isInitialized()) {
throw new AlreadyInitializedException();
}
this.providerName = providerName;
this.providerNameSet = true;
}
/**
*
* Sets the security provider to be asked for the encryption algorithm.
* The provider does not have to be registered at the security
* infrastructure beforehand, and its being used here will not result in
* its being registered.
*
*
* If this method is called, calling {@link #setProviderName(String)}
* becomes unnecessary.
*
*
* If no provider name / provider is explicitly set, the default JVM
* provider will be used.
*
*
* @since 1.3
*
* @param provider the provider to be asked for the chosen algorithm
*/
public synchronized void setProvider(Provider provider) {
CommonUtils.validateNotNull(provider, "Provider cannot be set null");
if (isInitialized()) {
throw new AlreadyInitializedException();
}
this.provider = provider;
this.providerSet = true;
}
/*
* Clone this encryptor 'size' times and initialize it.
* This encryptor will be at position 0 itself.
* Clones will NOT be initialized.
*/
synchronized StandardPBEByteEncryptor[] cloneAndInitializeEncryptor(final int size) {
if (isInitialized()) {
throw new EncryptionInitializationException(
"Cannot clone encryptor if it has been already initialized");
}
// If there is a config object, this forces the password configured value
// (if any) into the this.password property.
resolveConfigurationPassword();
final char[] copiedPassword = new char[this.password.length];
System.arraycopy(this.password, 0, copiedPassword, 0, this.password.length);
// Initialize the encryptor - note that this will clean the
// password (that's why copied it before)
initialize();
final StandardPBEByteEncryptor[] clones = new StandardPBEByteEncryptor[size];
clones[0] = this;
for (int i = 1; i < size; i++) {
final StandardPBEByteEncryptor clone = new StandardPBEByteEncryptor();
clone.setPasswordCharArray(copiedPassword);
if (CommonUtils.isNotEmpty(this.algorithm)) {
clone.setAlgorithm(this.algorithm);
}
clone.setKeyObtentionIterations(this.keyObtentionIterations);
if (this.provider != null) {
clone.setProvider(this.provider);
}
if (this.providerName != null) {
clone.setProviderName(this.providerName);
}
if (this.saltGenerator != null) {
clone.setSaltGenerator(this.saltGenerator);
}
if (this.ivGenerator != null) {
clone.setIvGenerator(this.ivGenerator);
}
clones[i] = clone;
}
cleanPassword(copiedPassword);
return clones;
}
/**
*
* Returns true if the encryptor has already been initialized, false if
* not.
* Initialization happens:
*
*
* - When initialize is called.
* - When encrypt or decrypt are called for the
* first time, if initialize has not been called before.
*
*
* Once an encryptor has been initialized, trying to
* change its configuration will
* result in an AlreadyInitializedException being thrown.
*
*
* @return true if the encryptor has already been initialized, false if
* not.
*/
public boolean isInitialized() {
return this.initialized;
}
/**
*
* Initialize the encryptor.
*
*
* This operation will consist in determining the actual configuration
* values to be used, and then initializing the encryptor with them.
*
* These values are decided by applying the following priorities:
*
*
* - First, the default values are considered (except for password).
*
* - Then, if a
* {@link org.jasypt.encryption.pbe.config.PBEConfig}
* object has been set with
* setConfig, the non-null values returned by its
* getX methods override the default values.
* - Finally, if the corresponding setX method has been called
* on the encryptor itself for any of the configuration parameters,
* the values set by these calls override all of the above.
*
*
* Once an encryptor has been initialized, trying to
* change its configuration will
* result in an AlreadyInitializedException being thrown.
*
*
* @throws EncryptionInitializationException if initialization could not
* be correctly done (for example, no password has been set).
*/
public synchronized void initialize() {
// Double-check to avoid synchronization issues
if (!this.initialized) {
/*
* If a PBEConfig object has been set, we need to
* consider the values it returns (if, for each value, the
* corresponding "setX" method has not been called).
*/
if (this.config != null) {
resolveConfigurationPassword();
final String configAlgorithm = this.config.getAlgorithm();
if (configAlgorithm != null) {
CommonUtils.validateNotEmpty(configAlgorithm,
"Algorithm cannot be set empty");
}
final Integer configKeyObtentionIterations =
this.config.getKeyObtentionIterations();
if (configKeyObtentionIterations != null) {
CommonUtils.validateIsTrue(configKeyObtentionIterations.intValue() > 0,
"Number of iterations for key obtention must be " +
"greater than zero");
}
final SaltGenerator configSaltGenerator = this.config.getSaltGenerator();
final IvGenerator configIvGenerator = this.config.getIvGenerator();
final String configProviderName = this.config.getProviderName();
if (configProviderName != null) {
CommonUtils.validateNotEmpty(configProviderName,
"Provider name cannot be empty");
}
final Provider configProvider = this.config.getProvider();
this.algorithm =
((this.algorithmSet) || (configAlgorithm == null))?
this.algorithm : configAlgorithm;
this.keyObtentionIterations =
((this.iterationsSet) ||
(configKeyObtentionIterations == null))?
this.keyObtentionIterations :
configKeyObtentionIterations.intValue();
this.saltGenerator =
((this.saltGeneratorSet) || (configSaltGenerator == null))?
this.saltGenerator : configSaltGenerator;
this.ivGenerator =
((this.ivGeneratorSet) || (configIvGenerator == null))?
this.ivGenerator : configIvGenerator;
this.providerName =
((this.providerNameSet) || (configProviderName == null))?
this.providerName : configProviderName;
this.provider =
((this.providerSet) || (configProvider == null))?
this.provider : configProvider;
}
/*
* If the encryptor was not set a salt generator in any way,
* it is time to apply its default value.
*/
if (this.saltGenerator == null) {
this.saltGenerator = new RandomSaltGenerator();
}
/*
* Default value is a no-op IV generator to maintain backwards compatibility
*/
if (this.ivGenerator == null) {
this.ivGenerator = new NoIvGenerator();
}
try {
// Password cannot be null.
if (this.password == null) {
throw new EncryptionInitializationException(
"Password not set for Password Based Encryptor");
}
// Normalize password to NFC form
final char[] normalizedPassword = Normalizer.normalizeToNfc(this.password);
/*
* Encryption and decryption Ciphers are created the usual way.
*/
final PBEKeySpec pbeKeySpec = new PBEKeySpec(normalizedPassword);
// We don't need the char[] passwords anymore -> clean!
cleanPassword(this.password);
cleanPassword(normalizedPassword);
if (this.provider != null) {
final SecretKeyFactory factory =
SecretKeyFactory.getInstance(
this.algorithm,
this.provider);
this.key = factory.generateSecret(pbeKeySpec);
this.encryptCipher =
Cipher.getInstance(this.algorithm, this.provider);
this.decryptCipher =
Cipher.getInstance(this.algorithm, this.provider);
} else if (this.providerName != null) {
final SecretKeyFactory factory =
SecretKeyFactory.getInstance(
this.algorithm,
this.providerName);
this.key = factory.generateSecret(pbeKeySpec);
this.encryptCipher =
Cipher.getInstance(this.algorithm, this.providerName);
this.decryptCipher =
Cipher.getInstance(this.algorithm, this.providerName);
} else {
final SecretKeyFactory factory =
SecretKeyFactory.getInstance(this.algorithm);
this.key = factory.generateSecret(pbeKeySpec);
this.encryptCipher = Cipher.getInstance(this.algorithm);
this.decryptCipher = Cipher.getInstance(this.algorithm);
}
} catch (EncryptionInitializationException e) {
throw e;
} catch (Throwable t) {
throw new EncryptionInitializationException(t);
}
// The salt size and the IV size for the chosen algorithm are set to be equal
// to the algorithm's block size (if it is a block algorithm).
final int algorithmBlockSize = this.encryptCipher.getBlockSize();
if (algorithmBlockSize > 0) {
this.saltSizeBytes = algorithmBlockSize;
this.ivSizeBytes = algorithmBlockSize;
}
this.optimizingDueFixedSalt = (this.saltGenerator instanceof FixedSaltGenerator)
&& (this.ivGenerator instanceof NoIvGenerator);
if (this.optimizingDueFixedSalt) {
// Create salt
this.fixedSaltInUse =
this.saltGenerator.generateSalt(this.saltSizeBytes);
/*
* Initialize the Cipher objects themselves. Due to the fact that
* we will be using a fixed salt, this can be done just once, which
* means a better performance at the encrypt/decrypt methods.
*/
final PBEParameterSpec parameterSpec =
new PBEParameterSpec(this.fixedSaltInUse, this.keyObtentionIterations);
try {
this.encryptCipher.init(
Cipher.ENCRYPT_MODE, this.key, parameterSpec);
this.decryptCipher.init(
Cipher.DECRYPT_MODE, this.key, parameterSpec);
} catch (final Exception e) {
// If encryption fails, it is more secure not to return any
// information about the cause in nested exceptions. Simply fail.
throw new EncryptionOperationNotPossibleException();
}
}
this.initialized = true;
}
}
private synchronized void resolveConfigurationPassword() {
// Double-check to avoid synchronization issues
if (!this.initialized) {
if (this.config != null && !this.passwordSet) {
// Get the configured password. If the config object implements
// CleanablePassword, we get password directly as a char array
// in order to avoid unnecessary creation of immutable Strings
// containing such password.
char[] configPassword = null;
if (this.config instanceof PBECleanablePasswordConfig) {
configPassword = ((PBECleanablePasswordConfig)this.config).getPasswordCharArray();
} else {
final String configPwd = this.config.getPassword();
if (configPwd != null) {
configPassword = configPwd.toCharArray();
}
}
if (configPassword != null) {
CommonUtils.validateIsTrue(configPassword.length > 0,
"Password cannot be set empty");
}
if (configPassword != null) {
this.password = new char[configPassword.length];
System.arraycopy(configPassword, 0, this.password, 0, configPassword.length);
this.passwordSet = true;
cleanPassword(configPassword);
}
// Finally, clean the password at the configuration object
if (this.config instanceof PBECleanablePasswordConfig) {
((PBECleanablePasswordConfig)this.config).cleanPassword();
}
}
}
}
private static void cleanPassword(final char[] password) {
if (password != null) {
synchronized (password) {
final int pwdLength = password.length;
for (int i = 0; i < pwdLength; i++) {
password[i] = (char)0;
}
}
}
}
/**
*
* Encrypts a message using the specified configuration.
*
*
* The mechanisms applied to perform the encryption operation are described
* in PKCS #5: Password-Based Cryptography Standard.
*
*
* This encryptor uses a salt and IV for each encryption
* operation. Sizes of the salt and IV depends on the algorithm
* being used. The salt and the IV, if generated by a random generator,
* they are also appended unencrypted at the beginning
* of the results so that a decryption operation can be performed.
*
*
* If a random salt generator is used, two encryption results for
* the same message will always be different
* (except in the case of random salt coincidence). This may enforce
* security by difficulting brute force attacks on sets of data at a time
* and forcing attackers to perform a brute force attack on each separate
* piece of encrypted data.
* The same is applied if a random IV generator is used.
*
*
* @param message the byte array message to be encrypted
* @return the result of encryption
* @throws EncryptionOperationNotPossibleException if the encryption
* operation fails, ommitting any further information about the
* cause for security reasons.
* @throws EncryptionInitializationException if initialization could not
* be correctly done (for example, no password has been set).
*/
public byte[] encrypt(final byte[] message)
throws EncryptionOperationNotPossibleException {
if (message == null) {
return null;
}
// Check initialization
if (!isInitialized()) {
initialize();
}
try {
final byte[] salt;
byte[] iv = null;
byte[] encryptedMessage;
if (this.optimizingDueFixedSalt) {
salt = this.fixedSaltInUse;
synchronized (this.encryptCipher) {
encryptedMessage = this.encryptCipher.doFinal(message);
}
} else {
// Create salt
salt = this.saltGenerator.generateSalt(this.saltSizeBytes);
// Create IV
iv = this.ivGenerator.generateIv(this.ivSizeBytes);
/*
* Perform encryption using the Cipher
*/
final PBEParameterSpec parameterSpec = buildPBEParameterSpec(salt, iv);
synchronized (this.encryptCipher) {
this.encryptCipher.init(
Cipher.ENCRYPT_MODE, this.key, parameterSpec);
encryptedMessage = this.encryptCipher.doFinal(message);
}
}
// We build an array containing both the unencrypted IV
// and the result of the encryption. This is done only
// if the IV generator we are using specifies to do so.
if (this.ivGenerator.includePlainIvInEncryptionResults()) {
// Insert plain IV before the encryption result
encryptedMessage = CommonUtils.appendArrays(iv, encryptedMessage);
}
// Finally we build an array containing both the unencrypted salt
// and the result of the encryption. This is done only
// if the salt generator we are using specifies to do so.
if (this.saltGenerator.includePlainSaltInEncryptionResults()) {
// Insert unhashed salt before the encryption result
encryptedMessage = CommonUtils.appendArrays(salt, encryptedMessage);
}
return encryptedMessage;
} catch (final InvalidKeyException e) {
// The problem could be not having the unlimited strength policies
// installed, so better give a usefull error message.
handleInvalidKeyException(e);
throw new EncryptionOperationNotPossibleException();
} catch (final Exception e) {
// If encryption fails, it is more secure not to return any
// information about the cause in nested exceptions. Simply fail.
throw new EncryptionOperationNotPossibleException();
}
}
/**
*
* Decrypts a message using the specified configuration.
*
*
* The mechanisms applied to perform the decryption operation are described
* in PKCS #5: Password-Based Cryptography Standard.
*
*
* If a random salt generator is used, this decryption operation will
* expect to find an unencrypted salt at the
* beginning of the encrypted input, so that the decryption operation can be
* correctly performed (there is no other way of knowing it).
*
*
* If a random IV generator is used, this decryption operation will
* expect to find an unencrypted IV at the
* beginning of the encrypted input, so that the decryption operation can be
* correctly performed (there is no other way of knowing it).
*
*
* @param encryptedMessage the byte array message to be decrypted
* @return the result of decryption
* @throws EncryptionOperationNotPossibleException if the decryption
* operation fails, ommitting any further information about the
* cause for security reasons.
* @throws EncryptionInitializationException if initialization could not
* be correctly done (for example, no password has been set).
*/
public byte[] decrypt(final byte[] encryptedMessage)
throws EncryptionOperationNotPossibleException {
if (encryptedMessage == null) {
return null;
}
// Check initialization
if (!isInitialized()) {
initialize();
}
if (this.saltGenerator.includePlainSaltInEncryptionResults()
&& this.ivGenerator.includePlainIvInEncryptionResults()) {
// Check that the received message is bigger than the salt + IV
if (encryptedMessage.length <= this.saltSizeBytes + this.ivSizeBytes) {
throw new EncryptionOperationNotPossibleException();
}
} else if (this.saltGenerator.includePlainSaltInEncryptionResults()) {
// Check that the received message is bigger than the salt
if (encryptedMessage.length <= this.saltSizeBytes) {
throw new EncryptionOperationNotPossibleException();
}
} else if (this.ivGenerator.includePlainIvInEncryptionResults()) {
// Check that the received message is bigger than the IV
if (encryptedMessage.length <= this.ivSizeBytes) {
throw new EncryptionOperationNotPossibleException();
}
}
try {
// If we are using a salt generator which specifies the salt
// to be included into the encrypted message itself, get it from
// there. If not, the salt is supposed to be fixed and thus the
// salt generator can be safely asked for it again.
byte[] salt = null;
byte[] encryptedMessageKernel = null;
if (this.saltGenerator.includePlainSaltInEncryptionResults()) {
final int saltStart = 0;
final int saltSize =
(this.saltSizeBytes < encryptedMessage.length? this.saltSizeBytes : encryptedMessage.length);
final int encMesKernelStart =
(this.saltSizeBytes < encryptedMessage.length? this.saltSizeBytes : encryptedMessage.length);
final int encMesKernelSize =
(this.saltSizeBytes < encryptedMessage.length? (encryptedMessage.length - this.saltSizeBytes) : 0);
salt = new byte[saltSize];
encryptedMessageKernel = new byte[encMesKernelSize];
System.arraycopy(encryptedMessage, saltStart, salt, 0, saltSize);
System.arraycopy(encryptedMessage, encMesKernelStart, encryptedMessageKernel, 0, encMesKernelSize);
} else if (!this.optimizingDueFixedSalt){
salt = this.saltGenerator.generateSalt(this.saltSizeBytes);
encryptedMessageKernel = encryptedMessage;
} else {
// this.optimizingDueFixedSalt == true
salt = this.fixedSaltInUse;
encryptedMessageKernel = encryptedMessage;
}
byte[] iv = null;
byte[] finalEncryptedMessageKernel = null;
if (this.ivGenerator.includePlainIvInEncryptionResults()) {
final int ivStart = 0;
final int ivSize =
(this.ivSizeBytes < encryptedMessageKernel.length? this.ivSizeBytes : encryptedMessageKernel.length);
final int encMesKernelStart =
(this.ivSizeBytes < encryptedMessageKernel.length? this.ivSizeBytes : encryptedMessageKernel.length);
final int encMesKernelSize =
(this.ivSizeBytes < encryptedMessageKernel.length? (encryptedMessageKernel.length - this.ivSizeBytes) : 0);
iv = new byte[ivSize];
finalEncryptedMessageKernel = new byte[encMesKernelSize];
System.arraycopy(encryptedMessageKernel, ivStart, iv, 0, ivSize);
System.arraycopy(encryptedMessageKernel, encMesKernelStart, finalEncryptedMessageKernel, 0, encMesKernelSize);
} else {
iv = ivGenerator.generateIv(ivSizeBytes);
finalEncryptedMessageKernel = encryptedMessageKernel;
}
final byte[] decryptedMessage;
if (this.optimizingDueFixedSalt) {
/*
* Fixed salt is being used, therefore no initialization supposedly needed
*/
synchronized (this.decryptCipher) {
decryptedMessage =
this.decryptCipher.doFinal(finalEncryptedMessageKernel);
}
} else {
/*
* Perform decryption using the Cipher
*/
final PBEParameterSpec parameterSpec = buildPBEParameterSpec(salt, iv);
synchronized (this.decryptCipher) {
this.decryptCipher.init(
Cipher.DECRYPT_MODE, this.key, parameterSpec);
decryptedMessage =
this.decryptCipher.doFinal(finalEncryptedMessageKernel);
}
}
// Return the results
return decryptedMessage;
} catch (final InvalidKeyException e) {
// The problem could be not having the unlimited strength policies
// installed, so better give a usefull error message.
handleInvalidKeyException(e);
throw new EncryptionOperationNotPossibleException();
} catch (final Exception e) {
// If decryption fails, it is more secure not to return any
// information about the cause in nested exceptions. Simply fail.
throw new EncryptionOperationNotPossibleException();
}
}
private PBEParameterSpec buildPBEParameterSpec(final byte[] salt, final byte[] iv) {
PBEParameterSpec parameterSpec;
try {
Class[] parameters = {byte[].class, int.class, AlgorithmParameterSpec.class};
Constructor java8Constructor = PBEParameterSpec.class.getConstructor(parameters);
Object[] parameterValues = {salt, this.keyObtentionIterations, new IvParameterSpec(iv)};
parameterSpec = java8Constructor.newInstance(parameterValues);
} catch (Exception e) {
parameterSpec = new PBEParameterSpec(salt, this.keyObtentionIterations);
}
return parameterSpec;
}
/*
* Method used to provide an useful error message in the case that the
* user tried to use a strong PBE algorithm like TripleDES and he/she
* has not installed the Unlimited Strength Policy files (the default
* message for this is simply "invalid key size", which does not provide
* enough clues for the user to know what is really going on).
*/
private void handleInvalidKeyException(final InvalidKeyException e) {
if ((e.getMessage() != null) &&
((e.getMessage().toUpperCase().indexOf("KEY SIZE") != -1))) {
throw new EncryptionOperationNotPossibleException(
"Encryption raised an exception. A possible cause is " +
"you are using strong encryption algorithms and " +
"you have not installed the Java Cryptography " +
"Extension (JCE) Unlimited Strength Jurisdiction " +
"Policy Files in this Java Virtual Machine");
}
}
}