org.jasypt.digest.PooledByteDigester 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.digest;
import java.security.Provider;
import org.jasypt.commons.CommonUtils;
import org.jasypt.digest.config.DigesterConfig;
import org.jasypt.exceptions.AlreadyInitializedException;
import org.jasypt.salt.SaltGenerator;
/**
*
* Pooled implementation of {@link ByteDigester} that in fact contains
* an array of {@link StandardByteDigester} objects which are used
* to attend digest and match requests in round-robin. This should
* result in higher performance in multiprocessor systems.
*
*
* Configuration of this class is equivalent to that of
* {@link StandardByteDigester}.
*
*
* This class is thread-safe.
*
*
*
* @since 1.7
*
* @author Daniel Fernández
*
*/
public class PooledByteDigester implements ByteDigester {
private final StandardByteDigester firstDigester;
private DigesterConfig config = null;
private int poolSize = 0;
private boolean poolSizeSet = false;
private StandardByteDigester[] pool;
private int roundRobin = 0;
/*
* Flag which indicates whether the digester has been initialized or not.
*
* Once initialized, no further modifications to its configuration will
* be allowed.
*/
private boolean initialized = false;
/**
* Creates a new instance of PooledStandardByteDigester.
*/
public PooledByteDigester() {
super();
this.firstDigester = new StandardByteDigester();
}
/**
*
* Sets a {@link org.jasypt.digest.config.DigesterConfig} object
* for the digester. If this config
* object is set, it will be asked values for:
*
*
*
* - Algorithm
* - Security Provider (or provider name)
* - Salt size
* - Hashing iterations
* - Salt generator
* - Location of the salt in relation to the encrypted message
* (default: before)
*
*
*
* 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 DigesterConfig object to be used as the
* source for configuration parameters.
*/
public synchronized void setConfig(final DigesterConfig config) {
this.firstDigester.setConfig(config);
this.config = config;
}
/**
*
* Sets the algorithm to be used for digesting, like MD5
* or SHA-1.
*
*
* This algorithm has to be supported by your security infrastructure, and
* it should be allowed as an algorithm for creating
* java.security.MessageDigest instances.
*
*
* If you are specifying a security provider with {@link #setProvider(Provider)} or
* {@link #setProviderName(String)}, this algorithm should be
* supported by your specified provider.
*
*
* If you are not specifying a provider, you will be able to use those
* algorithms provided by the default security provider of your JVM vendor.
* For valid names in the Sun JVM, see Java
* Cryptography Architecture API Specification &
* Reference.
*
*
* @param algorithm the name of the algorithm to be used.
*/
public synchronized void setAlgorithm(final String algorithm) {
this.firstDigester.setAlgorithm(algorithm);
}
/**
*
* Sets the size of the salt to be used to compute the digest.
* This mechanism is explained in
* PKCS #5: Password-Based Cryptography Standard.
*
*
*
* If salt size is set to zero, then no salt will be used.
*
*
* @param saltSizeBytes the size of the salt to be used, in bytes.
*/
public synchronized void setSaltSizeBytes(final int saltSizeBytes) {
this.firstDigester.setSaltSizeBytes(saltSizeBytes);
}
/**
*
* Set the number of times the hash function will be applied recursively.
*
* The hash function will be applied to its own results as many times as
* specified: h(h(...h(x)...))
*
*
* This mechanism is explained in
* PKCS #5: Password-Based Cryptography Standard.
*
*
* @param iterations the number of iterations.
*/
public synchronized void setIterations(final int iterations) {
this.firstDigester.setIterations(iterations);
}
/**
*
* 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(final SaltGenerator saltGenerator) {
this.firstDigester.setSaltGenerator(saltGenerator);
}
/**
*
* Sets the name of the security provider to be asked for the
* digest 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.
*
*
* @param providerName the name of the security provider to be asked
* for the digest algorithm.
*/
public synchronized void setProviderName(final String providerName) {
this.firstDigester.setProviderName(providerName);
}
/**
*
* Sets the security provider to be asked for the digest 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.
*
*
* @param provider the provider to be asked for the chosen algorithm
*/
public synchronized void setProvider(final Provider provider) {
this.firstDigester.setProvider(provider);
}
/**
*
* Whether the salt bytes are to be appended after the
* message ones before performing the digest operation on the whole. The
* default behaviour is to insert those bytes before the message bytes, but
* setting this configuration item to true allows compatibility
* with some external systems and specifications (e.g. LDAP {SSHA}).
*
*
* If this parameter is not explicitly set, the default behaviour
* (insertion of salt before message) will be applied.
*
*
* @param invertPositionOfSaltInMessageBeforeDigesting
* whether salt will be appended after the message before applying
* the digest operation on the whole, instead of inserted before it
* (which is the default).
*/
public synchronized void setInvertPositionOfSaltInMessageBeforeDigesting(
final boolean invertPositionOfSaltInMessageBeforeDigesting) {
this.firstDigester.setInvertPositionOfSaltInMessageBeforeDigesting(invertPositionOfSaltInMessageBeforeDigesting);
}
/**
*
* Whether the plain (not hashed) salt bytes are to
* be appended after the digest operation result bytes. The default behaviour is
* to insert them before the digest result, but setting this configuration
* item to true allows compatibility with some external systems
* and specifications (e.g. LDAP {SSHA}).
*
*
* If this parameter is not explicitly set, the default behaviour
* (insertion of plain salt before digest result) will be applied.
*
*
* @since 1.7
*
* @param invertPositionOfPlainSaltInEncryptionResults
* whether plain salt will be appended after the digest operation
* result instead of inserted before it (which is the
* default).
*/
public synchronized void setInvertPositionOfPlainSaltInEncryptionResults(
final boolean invertPositionOfPlainSaltInEncryptionResults) {
this.firstDigester.setInvertPositionOfPlainSaltInEncryptionResults(invertPositionOfPlainSaltInEncryptionResults);
}
/**
*
* Whether digest matching operations will allow matching
* digests with a salt size different to the one configured in the "saltSizeBytes"
* property. This is possible because digest algorithms will produce a fixed-size
* result, so the remaining bytes from the hashed input will be considered salt.
*
*
* This will allow the digester to match digests produced in environments which do not
* establish a fixed salt size as standard (for example, SSHA password encryption
* in LDAP systems).
*
*
* The value of this property will not affect the creation of digests,
* which will always have a salt of the size established by the "saltSizeBytes"
* property. It will only affect digest matching.
*
*
* Setting this property to true is not compatible with {@link SaltGenerator}
* implementations which return false for their
* {@link SaltGenerator#includePlainSaltInEncryptionResults()} property.
*
*
* Also, be aware that some algorithms or algorithm providers might not support
* knowing the size of the digests beforehand, which is also incompatible with
* a lenient behaviour.
*
*
* If this parameter is not explicitly set, the default behaviour
* (NOT lenient) will be applied.
*
*
* @param useLenientSaltSizeCheck whether the digester will allow matching of
* digests with different salt sizes than established or not (default
* is false).
*/
public synchronized void setUseLenientSaltSizeCheck(final boolean useLenientSaltSizeCheck) {
this.firstDigester.setUseLenientSaltSizeCheck(useLenientSaltSizeCheck);
}
/**
*
* Sets the size of the pool of digesters to be created.
*
*
* This parameter is required.
*
*
* @param poolSize size of the pool
*/
public synchronized void setPoolSize(final int poolSize) {
CommonUtils.validateIsTrue(poolSize > 0, "Pool size be > 0");
if (isInitialized()) {
throw new AlreadyInitializedException();
}
this.poolSize = poolSize;
this.poolSizeSet = true;
}
/**
*
* Returns true if the digester has already been initialized, false if
* not.
* Initialization happens:
*
*
* - When initialize is called.
* - When digest or matches are called for the
* first time, if initialize has not been called before.
*
*
* Once a digester has been initialized, trying to
* change its configuration (algorithm, provider, salt size, iterations
* or salt generator) will
* result in an AlreadyInitializedException being thrown.
*
*
* @return true if the digester has already been initialized, false if
* not.
*/
public boolean isInitialized() {
return this.initialized;
}
/**
*
* Initialize the digester.
*
*
* This operation will consist in determining the actual configuration
* values to be used, and then initializing the digester with them.
*
* These values are decided by applying the following priorities:
*
*
* - First, the default values are considered.
* - Then, if a
* {@link org.jasypt.digest.config.DigesterConfig}
* 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 digester itself for any of the configuration parameters, the
* values set by these calls override all of the above.
*
*
* Once a digester 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, if the digest algorithm chosen
* cannot be used).
*
*/
public synchronized void initialize() {
// Double-check to avoid synchronization issues
if (!this.initialized) {
if (this.config != null) {
final Integer configPoolSize = this.config.getPoolSize();
this.poolSize =
((this.poolSizeSet) || (configPoolSize == null))?
this.poolSize : configPoolSize.intValue();
}
if (this.poolSize <= 0) {
throw new IllegalArgumentException("Pool size must be set and > 0");
}
this.pool = new StandardByteDigester[this.poolSize];
this.pool[0] = this.firstDigester;
for (int i = 1; i < this.poolSize; i++) {
this.pool[i] = this.pool[i - 1].cloneDigester();
}
this.initialized = true;
}
}
/**
*
* Performs a digest operation on a byte array message.
*
*
* The steps taken for creating the digest are:
*
* - A salt of the specified size is generated (see
* {@link SaltGenerator}).
* - The salt bytes are added to the message.
* - The hash function is applied to the salt and message altogether,
* and then to the
* results of the function itself, as many times as specified
* (iterations).
* - If specified by the salt generator (see
* {@link org.jasypt.salt.SaltGenerator#includePlainSaltInEncryptionResults()}),
* the undigested salt and the final result of the hash
* function are concatenated and returned as a result.
*
* Put schematically in bytes:
*
* -
* DIGEST = |S|..(ssb)..|S|X|X|X|...|X|
*
* - S: salt bytes (plain, not digested). (OPTIONAL).
* - ssb: salt size in bytes.
* - X: bytes resulting from hashing (see below).
*
*
* -
* |X|X|X|...|X| =
* H(H(H(..(it)..H(Z|Z|Z|...|Z|))))
*
* - H: Hash function (algorithm).
* - it: Number of iterations.
* - Z: Input for hashing (see below).
*
*
* -
* |Z|Z|Z|...|Z| =
* |S|..(ssb)..|S|M|M|M...|M|
*
* - S: salt bytes (plain, not digested).
* - ssb: salt size in bytes.
* - M: message bytes.
*
*
*
*
*
* If a random salt generator is used, two digests created for the same
* message will always be different
* (except in the case of random salt coincidence).
* Because of this, in this case the result of the digest method
* will contain both the undigested salt and the digest of the
* (salt + message), so that another digest operation can be performed
* with the same salt on a different message to check if both messages
* match (all of which will be managed automatically by the
* matches method).
*
*
* @param message the byte array to be digested
* @return the digest result
* @throws EncryptionOperationNotPossibleException if the digest operation
* fails, ommitting any further information about the cause for
* security reasons.
* @throws EncryptionInitializationException if initialization could not
* be correctly done (for example, if the digest algorithm chosen
* cannot be used).
*
*/
public byte[] digest(byte[] message) {
// Check initialization
if (!isInitialized()) {
initialize();
}
int poolPosition;
synchronized(this) {
poolPosition = this.roundRobin;
this.roundRobin = (this.roundRobin + 1) % this.poolSize;
}
return this.pool[poolPosition].digest(message);
}
/**
*
* Checks a message against a given digest.
*
*
* This method tells whether a message corresponds to a specific digest
* or not by getting the salt with which the digest was created and
* applying it to a digest operation performed on the message. If
* new and existing digest match, the message is said to match the digest.
*
*
* This method will be used, for instance, for password checking in
* authentication processes.
*
*
* A null message will only match a null digest.
*
*
* @param message the message to be compared to the digest.
* @param digest the digest.
* @return true if the specified message matches the digest, false
* if not.
* @throws EncryptionOperationNotPossibleException if the digest matching
* operation fails, ommitting any further information about the
* cause for security reasons.
* @throws EncryptionInitializationException if initialization could not
* be correctly done (for example, if the digest algorithm chosen
* cannot be used).
*/
public boolean matches(final byte[] message, final byte[] digest) {
// Check initialization
if (!isInitialized()) {
initialize();
}
int poolPosition;
synchronized(this) {
poolPosition = this.roundRobin;
this.roundRobin = (this.roundRobin + 1) % this.poolSize;
}
return this.pool[poolPosition].matches(message, digest);
}
}