Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* Copyright 2020-2021 Ping Identity Corporation
* All Rights Reserved.
*/
/*
* Copyright 2020-2021 Ping Identity Corporation
*
* 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.
*/
/*
* Copyright (C) 2020-2021 Ping Identity Corporation
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License (GPLv2 only)
* or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see .
*/
package com.unboundid.ldap.sdk.unboundidds;
import java.io.Serializable;
import java.security.GeneralSecurityException;
import java.security.SecureRandom;
import java.text.ParseException;
import java.util.Arrays;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import com.unboundid.util.Base64;
import com.unboundid.util.ByteStringBuffer;
import com.unboundid.util.CryptoHelper;
import com.unboundid.util.Debug;
import com.unboundid.util.NotMutable;
import com.unboundid.util.NotNull;
import com.unboundid.util.StaticUtils;
import com.unboundid.util.ThreadLocalSecureRandom;
import com.unboundid.util.ThreadSafety;
import com.unboundid.util.ThreadSafetyLevel;
import com.unboundid.util.Validator;
import static com.unboundid.ldap.sdk.unboundidds.UnboundIDDSMessages.*;
/**
* This class provides a mechanism that can be used to encrypt and decrypt
* passwords using the same mechanism that the Ping Identity Directory Server
* uses for the AES256 password storage scheme (for clients that know the
* passphrase used to generate the encryption key).
*
*
* NOTE: This class, and other classes within the
* {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only
* supported for use against Ping Identity, UnboundID, and
* Nokia/Alcatel-Lucent 8661 server products. These classes provide support
* for proprietary functionality or for external specifications that are not
* considered stable or mature enough to be guaranteed to work in an
* interoperable way with other types of LDAP servers.
*
*
* Note that this class requires strong encryption support in the underlying
* JVM. For Java 7 JVMs, and for Java 8 JVMs prior to update 161, this requires
* installing unlimited strength jurisdiction policy files in the JVM. For Java
* 8 JVMs starting with update 161, and for all later Java versions, strong
* encryption should be available by default.
*
* The raw representation for encoded passwords is constructed as follows:
*
*
* A single byte that combines the encoding version and the padding length.
* The least significant four bits represent a two's complement integer that
* indicate the number of zero bytes that will be appended to the provided
* password to make it a multiple of sixteen bytes. The most significant
* four bits represent the encoding version. At present, we only support a
* single encoding version in which all of those bits must be set to zero.
* With this encoding version, the following properties will be used:
*
* Sixteen bytes of random data generated by a secure random number
* generator. This represents the salt provided to the key factory for the
* purpose of generating the secret key.
*
*
* Sixteen bytes of random data generated by a secure random number
* generator. This represents the initialization vector that will be used
* to randomize the cipher output.
*
*
* One byte that represents a two's complement integer that indicates the
* number of bytes in the ID of the encryption settings definition whose
* passphrase is used to generate the encryption key. The value must be
* less than or equal to 255. For current versions of the Ping Identity
* Directory Server, it will typically be 32 bytes.
*
*
* The bytes that comprise the raw ID of the encryption settings definition
* whose passphrase will be used to generate the encryption key.
*
*
* The bytes that comprise the encrypted password using the above settings.
*
*
*
* The string representation of the encoded password is generated by appending
* the base64-encoded representation of the raw encoded bytes to the prefix
* "{AES256}".
*
* When encrypting a password using this algorithm, the first step is to
* generate the encryption key. This is done using a key factory, which
* combines a passphrase (obtained from an encryption settings definition), an
* iteration count, and a salt.
*
* The second step is to apply any necessary padding to the password. Because
* AES used in Galois Counter mode (GCM) behaves as a stream cipher, the size of
* the encrypted data can be used to determine the size of the plaintext that
* was encrypted. This is undesirable when encrypting passwords because it can
* let an attacker know how long the user's password is. Padding the password
* (by appending enough zero bytes to make its length a multiple of sixteen
* bytes) makes it impossible for an attacker to determine the size of the
* clear-text password.
*
* The final step is to perform the encryption. A cipher is created using the
* generated secret key and initialization vector, and it is used to encrypt the
* padded password.
*
* Because this encoding uses reversible encryption rather than a one-way
* algorithm, there are two possible ways to verify that a provided plain-text
* password matches an encoded representation. Both involve decoding the
* encoded representation of the password to extract the padding length, salt,
* initialization vector, and encryption settings definition ID. Then, you can
* either encrypt the provided plaintext password with the same settings to
* verify that it yields the same encoded representation, or you can decrypt
* the encoded password and remove any padding to verify that it yields the same
* plaintext representation.
*/
@NotMutable()
@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
public final class AES256EncodedPassword
implements Serializable
{
/**
* The bitmask that will be used to indicate an encoding version of zero.
* Only the most significant four bits of this byte will be used. The least
* significant four bits of the first byte will be used to indicate the number
* of padding bytes that must be appended to the clear-text password to make
* its length a multiple of sixteen bytes.
*/
public static final byte ENCODING_VERSION_0_MASK = (byte) 0x00;
/**
* The integer value for encoding version 0.
*/
public static final int ENCODING_VERSION_0 = 0;
/**
* The GCM tag length in bits to use with an encoding version of zero.
*/
public static final int ENCODING_VERSION_0_GCM_TAG_LENGTH_BITS = 128;
/**
* The generated secret key length in bits to use with an encoding version of
* zero.
*/
public static final int ENCODING_VERSION_0_GENERATED_KEY_LENGTH_BITS = 256;
/**
* The size in bytes to use for the initialization vector with an encoding
* version of zero.
*/
public static final int ENCODING_VERSION_0_IV_LENGTH_BYTES = 16;
/**
* The key factory iteration count to use with an encoding version of zero.
*/
public static final int ENCODING_VERSION_0_KEY_FACTORY_ITERATION_COUNT =
32_768;
/**
* The size in bytes to use for the key factory salt with an encoding version
* of zero.
*/
public static final int ENCODING_VERSION_0_KEY_FACTORY_SALT_LENGTH_BYTES = 16;
/**
* The padding modulus to use with an encoding version of zero.
*/
public static final int ENCODING_VERSION_0_PADDING_MODULUS = 16;
/**
* The name of the cipher algorithm that should be used with an encoding
* version of zero.
*/
@NotNull public static final String ENCODING_VERSION_0_CIPHER_ALGORITHM =
"AES";
/**
* The name of the cipher transformation that should be used with an encoding
* version of zero.
*/
@NotNull public static final String ENCODING_VERSION_0_CIPHER_TRANSFORMATION =
"AES/GCM/NoPadding";
/**
* The name of the key factory algorithm should be used with an encoding
* version of zero.
*/
@NotNull public static final String ENCODING_VERSION_0_KEY_FACTORY_ALGORITHM =
"PBKDF2WithHmacSHA512";
/**
* The prefix that will appear at the beginning of the string representation
* for an encoded password.
*/
@NotNull public static final String PASSWORD_STORAGE_SCHEME_PREFIX =
"{AES256}";
/**
* The serial version UID for this serializable class.
*/
private static final long serialVersionUID = 8663129897722695672L;
// The bytes that comprise the complete raw encoded representation of the
// password.
@NotNull private final byte[] encodedRepresentation;
// The bytes that comprise the encrypted representation of the padded
// password.
@NotNull private final byte[] encryptedPaddedPassword;
// The bytes that comprise the raw encryption settings definition ID whose
// passphrase was used to generate the encoded password.
@NotNull private final byte[] encryptionSettingsDefinitionID;
// The initialization vector used to randomize the output of the encrypted
// password.
@NotNull private final byte[] initializationVector;
// The salt used in the course of generating the encryption key from the
// encryption settings definition passphrase.
@NotNull private final byte[] keyFactorySalt;
// The encoding version used for the password.
private final int encodingVersion;
// The number of zero bytes that were appended to the clear-text password
// before it was encrypted.
private final int paddingBytes;
/**
* Creates a new encoded password with the provided information.
*
* @param encodedRepresentation
* The bytes that comprise the complete raw encoded
* representation of the password.
* @param encodingVersion
* The encoding version for this encoded password.
* @param paddingBytes
* The number of bytes of padding that need to be appended to the
* clear-text password to make its length a multiple of sixteen
* bytes.
* @param keyFactorySalt
* The salt used to generate the encryption key from the
* encryption settings definition passphrase.
* @param initializationVector
* The initialization vector used to randomize the cipher output.
* @param encryptionSettingsDefinitionID
* The bytes that comprise the raw encryption settings definition
* ID whose passphrase was used to generate the encoded password.
* @param encryptedPaddedPassword
* The bytes that comprise the encrypted representation of the
* padded password.
*/
private AES256EncodedPassword(
@NotNull final byte[] encodedRepresentation,
final int encodingVersion,
final int paddingBytes,
@NotNull final byte[] keyFactorySalt,
@NotNull final byte[] initializationVector,
@NotNull final byte[] encryptionSettingsDefinitionID,
@NotNull final byte[] encryptedPaddedPassword)
{
this.encodedRepresentation = encodedRepresentation;
this.encodingVersion = encodingVersion;
this.paddingBytes = paddingBytes;
this.keyFactorySalt = keyFactorySalt;
this.initializationVector = initializationVector;
this.encryptionSettingsDefinitionID = encryptionSettingsDefinitionID;
this.encryptedPaddedPassword = encryptedPaddedPassword;
}
/**
* Retrieves the encoding version for this encoded password.
*
* @return The encoding version for this encoded password.
*/
public int getEncodingVersion()
{
return encodingVersion;
}
/**
* Retrieves the number of bytes of padding that need to be appended to the
* clear-text password to make its length a multiple of sixteen bytes.
*
* @return The number of bytes of padding that need to be appended to the
* clear-text password to make its length a multiple of sixteen
* bytes.
*/
public int getPaddingBytes()
{
return paddingBytes;
}
/**
* Retrieves the salt used to generate the encryption key from the encryption
* settings definition passphrase.
*
* @return The salt used to generate the encryption key from the encryption
* settings definition passphrase.
*/
@NotNull()
public byte[] getKeyFactorySalt()
{
return keyFactorySalt;
}
/**
* Retrieves the initialization vector used to randomize the cipher output.
*
* @return The initialization vector used to randomize the cipher output.
*/
@NotNull()
public byte[] getInitializationVector()
{
return initializationVector;
}
/**
* Retrieves the bytes that comprise the raw ID of the encryption settings
* definition whose passphrase is used to generate the encryption key.
*
* @return A bytes that comprise the raw ID of the encryption settings
* definition whose passphrase is used to generate the encryption
* key.
*/
@NotNull()
public byte[] getEncryptionSettingsDefinitionIDBytes()
{
return encryptionSettingsDefinitionID;
}
/**
* Retrieves a string representation of the ID of the encryption settings
* definition whose passphrase is used to generate the encryption key.
*
* @return A string representation of the ID of the encryption settings
* definition whose passphrase is used to generate the encryption
* key.
*/
@NotNull()
public String getEncryptionSettingsDefinitionIDString()
{
return StaticUtils.toUpperCase(
StaticUtils.toHex(encryptionSettingsDefinitionID));
}
/**
* Retrieves the bytes that comprise the complete raw encoded representation
* of the password.
*
* @return The bytes that comprise the complete raw encoded representation of
* the password.
*/
@NotNull()
public byte[] getEncodedRepresentation()
{
return encodedRepresentation;
}
/**
* Retrieves the string representation of this AES256 password.
*
* @param includeScheme Indicates whether to include the "{AES256}" prefix
* at the beginning of the string representation.
*
* @return The string representation of this encoded password.
*/
@NotNull()
public String getStringRepresentation(final boolean includeScheme)
{
final String base64String = Base64.encode(encodedRepresentation);
if (includeScheme)
{
return PASSWORD_STORAGE_SCHEME_PREFIX + base64String;
}
else
{
return base64String;
}
}
/**
* Encodes a password using the provided information.
*
* @param encryptionSettingsDefinitionID
* A string with the hexadecimal representation of the
* encryption settings definition whose passphrase was used to
* generate the encoded password. It must not be
* {@code null} or empty, and it must represent a valid
* hexadecimal string whose length is an even number less than
* or equal to 510 bytes.
* @param encryptionSettingsDefinitionPassphrase
* The passphrase associated with the specified encryption
* settings definition. It must not be {@code null} or empty.
* @param clearTextPassword
* The clear-text password to encode. It must not be
* {@code null} or empty.
*
* @return An object with all of the encoded password details.
*
* @throws GeneralSecurityException If a problem occurs while attempting to
* perform any of the cryptographic
* processing.
*
* @throws ParseException If the provided encryption settings definition ID
* cannot be parsed as a valid hexadecimal string.
*/
@NotNull()
public static AES256EncodedPassword encode(
@NotNull final String encryptionSettingsDefinitionID,
@NotNull final String encryptionSettingsDefinitionPassphrase,
@NotNull final String clearTextPassword)
throws GeneralSecurityException, ParseException
{
final byte[] encryptionSettingsDefinitionIDBytes =
StaticUtils.fromHex(encryptionSettingsDefinitionID);
final char[] encryptionSettingsDefinitionPassphraseChars =
encryptionSettingsDefinitionPassphrase.toCharArray();
final byte[] clearTextPasswordBytes =
StaticUtils.getBytes(clearTextPassword);
try
{
return encode(encryptionSettingsDefinitionIDBytes,
encryptionSettingsDefinitionPassphraseChars, clearTextPasswordBytes);
}
finally
{
Arrays.fill(encryptionSettingsDefinitionPassphraseChars, '\u0000');
Arrays.fill(clearTextPasswordBytes, (byte) 0x00);
}
}
/**
* Encodes a password using the provided information.
*
* @param encryptionSettingsDefinitionID
* The bytes that comprise the raw encryption settings definition
* ID whose passphrase was used to generate the encoded password.
* It must not be {@code null} or empty, and its length must be
* less than or equal to 255 bytes.
* @param encryptionSettingsDefinitionPassphrase
* The passphrase associated with the specified encryption
* settings definition. It must not be {@code null} or empty.
* @param clearTextPassword
* The bytes that comprise the clear-text password to encode.
* It must not be {@code null} or empty.
*
* @return An object with all of the encoded password details.
*
* @throws GeneralSecurityException If a problem occurs while attempting to
* perform any of the cryptographic
* processing.
*/
@NotNull()
public static AES256EncodedPassword encode(
@NotNull final byte[] encryptionSettingsDefinitionID,
@NotNull final char[] encryptionSettingsDefinitionPassphrase,
@NotNull final byte[] clearTextPassword)
throws GeneralSecurityException
{
final SecureRandom random = ThreadLocalSecureRandom.get();
final byte[] keyFactorySalt =
new byte[ENCODING_VERSION_0_KEY_FACTORY_SALT_LENGTH_BYTES];
random.nextBytes(keyFactorySalt);
final byte[] initializationVector =
new byte[ENCODING_VERSION_0_IV_LENGTH_BYTES];
random.nextBytes(initializationVector);
return encode(encryptionSettingsDefinitionID,
encryptionSettingsDefinitionPassphrase, keyFactorySalt,
initializationVector, clearTextPassword);
}
/**
* Encodes a password using the provided information.
*
* @param encryptionSettingsDefinitionID
* The bytes that comprise the raw encryption settings definition
* ID whose passphrase was used to generate the encoded password.
* It must not be {@code null} or empty, and its length must be
* less than or equal to 255 bytes.
* @param encryptionSettingsDefinitionPassphrase
* The passphrase associated with the specified encryption
* settings definition. It must not be {@code null} or empty.
* @param keyFactorySalt
* The salt used to generate the encryption key from the
* encryption settings definition passphrase. It must not be
* {@code null} and it must have a length of exactly 16 bytes.
* @param initializationVector
* The initialization vector used to randomize the cipher output.
* It must not be [@code null} and it must have a length of
* exactly 16 bytes.
* @param clearTextPassword
* The bytes that comprise the clear-text password to encode.
* It must not be {@code null} or empty.
*
* @return An object with all of the encoded password details.
*
* @throws GeneralSecurityException If a problem occurs while attempting to
* perform any of the cryptographic
* processing.
*/
@NotNull()
public static AES256EncodedPassword encode(
@NotNull final byte[] encryptionSettingsDefinitionID,
@NotNull final char[] encryptionSettingsDefinitionPassphrase,
@NotNull final byte[] keyFactorySalt,
@NotNull final byte[] initializationVector,
@NotNull final byte[] clearTextPassword)
throws GeneralSecurityException
{
final AES256EncodedPasswordSecretKey secretKey =
AES256EncodedPasswordSecretKey.generate(encryptionSettingsDefinitionID,
encryptionSettingsDefinitionPassphrase, keyFactorySalt);
try
{
return encode(secretKey, initializationVector, clearTextPassword);
}
finally
{
secretKey.destroy();
}
}
/**
* Encodes a password using the provided information.
*
* @param secretKey
* The secret key that should be used to encrypt the password.
* It must not be {@code null}. The secret key can be reused
* when
* @param initializationVector
* The initialization vector used to randomize the cipher output.
* It must not be [@code null} and it must have a length of
* exactly 16 bytes.
* @param clearTextPassword
* The bytes that comprise the clear-text password to encode.
* It must not be {@code null} or empty.
*
* @return An object with all of the encoded password details.
*
* @throws GeneralSecurityException If a problem occurs while attempting to
* perform any of the cryptographic
* processing.
*/
@NotNull()
public static AES256EncodedPassword encode(
@NotNull final AES256EncodedPasswordSecretKey secretKey,
@NotNull final byte[] initializationVector,
@NotNull final byte[] clearTextPassword)
throws GeneralSecurityException
{
// Validate all of the provided parameters.
Validator.ensureNotNull(secretKey,
"AES256EncodedPassword.encode.secretKey must not be null.");
Validator.ensureNotNull(initializationVector,
"AES256EncodedPassword.encode.initializationVector must not be null.");
if (initializationVector.length != ENCODING_VERSION_0_IV_LENGTH_BYTES)
{
Validator.violation("AES256EncodedPassword.encode.initializationVector " +
"must have a length of exactly " +
ENCODING_VERSION_0_IV_LENGTH_BYTES + " bytes. The provided " +
"initialization vector had a length of " +
initializationVector.length + " bytes.");
}
Validator.ensureNotNullOrEmpty(clearTextPassword,
"AES256EncodedPassword.encode.clearTextPassword must not be null or " +
"empty.");
// Generate a padded representation of the password.
final byte[] paddedClearTextPassword;
final int paddingBytesNeeded;
final int clearTextPasswordLengthModulus =
clearTextPassword.length % ENCODING_VERSION_0_PADDING_MODULUS;
if (clearTextPasswordLengthModulus == 0)
{
paddedClearTextPassword = clearTextPassword;
paddingBytesNeeded = 0;
}
else
{
paddingBytesNeeded =
ENCODING_VERSION_0_PADDING_MODULUS - clearTextPasswordLengthModulus;
paddedClearTextPassword =
new byte[clearTextPassword.length + paddingBytesNeeded];
Arrays.fill(paddedClearTextPassword, (byte) 0x00);
System.arraycopy(clearTextPassword, 0, paddedClearTextPassword, 0,
clearTextPassword.length);
}
// Create and initialize the cipher and use it to encrypt the padded
// password.
final Cipher cipher =
CryptoHelper.getCipher(ENCODING_VERSION_0_CIPHER_TRANSFORMATION);
cipher.init(Cipher.ENCRYPT_MODE, secretKey.getSecretKey(),
new GCMParameterSpec(ENCODING_VERSION_0_GCM_TAG_LENGTH_BITS,
initializationVector));
final byte[] encryptedPaddedPassword =
cipher.doFinal(paddedClearTextPassword);
// Generate the raw encoded representation of the password.
final ByteStringBuffer buffer = new ByteStringBuffer();
// The first byte will combine the encoding version (the upper four bits)
// and the number of padding bytes (the lower four bits).
buffer.append((byte)
((ENCODING_VERSION_0_MASK & 0xF0) | (paddingBytesNeeded & 0x0F)));
// The next 16 bytes will be the salt.
final byte[] keyFactorySalt = secretKey.getKeyFactorySalt();
buffer.append(keyFactorySalt);
// The next 16 bytes will be the initialization vector.
buffer.append(initializationVector);
// The next byte will be the number of bytes in the raw encryption settings
// definition ID, followed by the encoded ID.
final byte[] encryptionSettingsDefinitionID =
secretKey.getEncryptionSettingsDefinitionID();
buffer.append((byte) (encryptionSettingsDefinitionID.length & 0xFF));
buffer.append(encryptionSettingsDefinitionID);
// The remainder of the encoded representation will be the encrypted
// padded password.
buffer.append(encryptedPaddedPassword);
// Create and return an object with all of the encoded password details.
return new AES256EncodedPassword(buffer.toByteArray(), ENCODING_VERSION_0,
paddingBytesNeeded, keyFactorySalt, initializationVector,
encryptionSettingsDefinitionID, encryptedPaddedPassword);
}
/**
* Decodes the provided password into its component parts.
*
* @param encodedPassword
* The string representation of the encoded password to be
* decoded. It must not be {@code null} or empty, and it must
* contain the base64-encoded representation of the raw encoded
* password, optionally preceded by the "{AES256}" prefix.
*
* @return The decoded representation of the provided password.
*
* @throws ParseException If the provided string does not represent a valid
* encoded password.
*/
@NotNull()
public static AES256EncodedPassword decode(
@NotNull final String encodedPassword)
throws ParseException
{
Validator.ensureNotNullOrEmpty(encodedPassword,
"AES256EncodedPassword.decode.encodedPassword must not be null or " +
"empty.");
// If the provided string starts with a prefix, then strip it off.
final int base64StartPos;
final String base64EncodedString;
if (encodedPassword.startsWith(PASSWORD_STORAGE_SCHEME_PREFIX))
{
base64StartPos = PASSWORD_STORAGE_SCHEME_PREFIX.length();
base64EncodedString = encodedPassword.substring(base64StartPos);
}
else
{
base64StartPos = 0;
base64EncodedString = encodedPassword;
}
// Base64-decode the data.
final byte[] encodedPasswordBytes;
try
{
encodedPasswordBytes = Base64.decode(base64EncodedString);
}
catch (final ParseException e)
{
Debug.debugException(e);
throw new ParseException(
ERR_AES256_ENC_PW_DECODE_NOT_BASE64.get(
StaticUtils.getExceptionMessage(e)),
base64StartPos);
}
return decode(encodedPasswordBytes);
}
/**
* Decodes the provided password into its component parts.
*
* @param encodedPassword
* The bytes that comprise the complete raw encoded
* representation of the password. It must not be {@code null}
* or empty.
*
* @return The decoded representation of the provided password.
*
* @throws ParseException If the provided string does not represent a valid
* encoded password.
*/
@NotNull()
public static AES256EncodedPassword decode(
@NotNull final byte[] encodedPassword)
throws ParseException
{
Validator.ensureNotNullOrEmpty(encodedPassword,
"AES256EncodedPassword.decode.encodedPassword must not be null or " +
"empty.");
// Make sure that the length is at least 36 bytes long. This isn't long
// enough for a valid encoded password, but it's long enough to let us get a
// good starting point.
if (encodedPassword.length < 36)
{
throw new ParseException(
ERR_AES256_ENC_PW_DECODE_TOO_SHORT_INITIAL.get(
encodedPassword.length),
0);
}
// The first byte must contain the encoding version and the number of bytes
// of padding.
final byte encodingVersionAndPaddingByte = encodedPassword[0];
final int encodingVersion = (encodingVersionAndPaddingByte >> 4) & 0x0F;
if (encodingVersion != ENCODING_VERSION_0)
{
throw new ParseException(
ERR_AES256_ENC_PW_DECODE_UNSUPPORTED_ENCODING_VERSION.get(
encodingVersion, ENCODING_VERSION_0),
0);
}
final int paddingBytes = (encodingVersionAndPaddingByte & 0x0F);
// The next 16 bytes must contain the salt.
final byte[] keyFactorySalt =
new byte[ENCODING_VERSION_0_KEY_FACTORY_SALT_LENGTH_BYTES];
System.arraycopy(encodedPassword, 1, keyFactorySalt, 0,
keyFactorySalt.length);
// The next 16 bytes must contain the initialization vector.
final byte[] initializationVector =
new byte[ENCODING_VERSION_0_IV_LENGTH_BYTES];
System.arraycopy(encodedPassword, 1 + keyFactorySalt.length,
initializationVector, 0, initializationVector.length);
// The next byte must indicate how many bytes are in the encryption settings
// definition ID. That should then be followed by the specified number of
// bytes of the encryption settings definition ID.
final int esdIDLengthPos =
1 + keyFactorySalt.length + initializationVector.length;
final int esdIDLength = encodedPassword[esdIDLengthPos] & 0xFF;
if (encodedPassword.length < (esdIDLengthPos + 2 + esdIDLength))
{
throw new ParseException(
ERR_AES256_ENC_PW_DECODE_TOO_SHORT_FOR_ESD_ID.get(
encodedPassword.length, esdIDLength),
esdIDLengthPos);
}
final byte[] encryptionSettingsDefinitionID = new byte[esdIDLength];
System.arraycopy(encodedPassword, esdIDLengthPos + 1,
encryptionSettingsDefinitionID, 0, esdIDLength);
// The remainder of the encoded password must be the encrypted padded
// password.
final int encryptedPaddedPasswordPos =
esdIDLengthPos + 1 + esdIDLength;
final int encryptedPaddedPasswordLength = encodedPassword.length -
encryptedPaddedPasswordPos;
final byte[] encryptedPaddedPassword =
new byte[encryptedPaddedPasswordLength];
System.arraycopy(encodedPassword, encryptedPaddedPasswordPos,
encryptedPaddedPassword, 0, encryptedPaddedPasswordLength);
return new AES256EncodedPassword(encodedPassword, encodingVersion,
paddingBytes, keyFactorySalt, initializationVector,
encryptionSettingsDefinitionID, encryptedPaddedPassword);
}
/**
* Decrypts this encoded password to obtain the original clear-text password
* used to generate it.
*
* @param encryptionSettingsDefinitionPassphrase
* The passphrase associated with the encryption settings
* definition used to encrypt the password. It must not be
* {@code null} or empty.
*
* @return The original clear-txt password used to generate this encoded
* representation.
*
* @throws GeneralSecurityException If an error occurs while attempting to
* decrypt the password using the
* provided encryption settings ID
* passphrase.
*/
@NotNull()
public byte[] decrypt(
@NotNull final String encryptionSettingsDefinitionPassphrase)
throws GeneralSecurityException
{
final char[] passphraseChars =
encryptionSettingsDefinitionPassphrase.toCharArray();
try
{
return decrypt(passphraseChars);
}
finally
{
Arrays.fill(passphraseChars, '\u0000');
}
}
/**
* Decrypts this encoded password to obtain the original clear-text password
* used to generate it.
*
* @param encryptionSettingsDefinitionPassphrase
* The passphrase associated with the encryption settings
* definition used to encrypt the password. It must not be
* {@code null} or empty.
*
* @return The original clear-txt password used to generate this encoded
* representation.
*
* @throws GeneralSecurityException If an error occurs while attempting to
* decrypt the password using the
* provided encryption settings ID
* passphrase.
*/
@NotNull()
public byte[] decrypt(
@NotNull final char[] encryptionSettingsDefinitionPassphrase)
throws GeneralSecurityException
{
final AES256EncodedPasswordSecretKey secretKey =
AES256EncodedPasswordSecretKey.generate(encryptionSettingsDefinitionID,
encryptionSettingsDefinitionPassphrase, keyFactorySalt);
try
{
return decrypt(secretKey);
}
finally
{
secretKey.destroy();
}
}
/**
* Decrypts this encoded password to obtain the original clear-text password
* used to generate it.
*
* @param secretKey
* The that will be used to decrypt the password. It must not
* be {@code null}.
*
* @return The original clear-txt password used to generate this encoded
* representation.
*
* @throws GeneralSecurityException If an error occurs while attempting to
* decrypt the password using the
* provided encryption settings ID
* passphrase.
*/
@NotNull()
public byte[] decrypt(@NotNull final AES256EncodedPasswordSecretKey secretKey)
throws GeneralSecurityException
{
Validator.ensureNotNull(secretKey,
"AES256EncodedPassword.decrypt.secretKey must not be null.");
// Create and initialize the cipher and use it to decrypt the padded
// password.
final Cipher cipher =
CryptoHelper.getCipher(ENCODING_VERSION_0_CIPHER_TRANSFORMATION);
cipher.init(Cipher.DECRYPT_MODE, secretKey.getSecretKey(),
new GCMParameterSpec(ENCODING_VERSION_0_GCM_TAG_LENGTH_BITS,
initializationVector));
final byte[] decryptedPaddedPassword =
cipher.doFinal(encryptedPaddedPassword);
// Strip off any padding and return the resulting password.
final byte[] decryptedPassword;
if (paddingBytes > 0)
{
try
{
decryptedPassword =
new byte[decryptedPaddedPassword.length - paddingBytes];
for (int i=0; i < decryptedPaddedPassword.length; i++)
{
if (i < decryptedPassword.length)
{
// This byte is not padding.
decryptedPassword[i] = decryptedPaddedPassword[i];
}
else
{
// This byte is considered padding. Make sure that it's 0x00.
if (decryptedPaddedPassword[i] != 0x00)
{
throw new BadPaddingException(
ERR_AES256_ENC_PW_DECRYPT_NONZERO_PADDING.get(paddingBytes));
}
}
}
System.arraycopy(decryptedPaddedPassword, 0, decryptedPassword, 0,
decryptedPassword.length);
}
finally
{
Arrays.fill(decryptedPaddedPassword, (byte) 0x00);
}
}
else
{
decryptedPassword = decryptedPaddedPassword;
}
return decryptedPassword;
}
/**
* Retrieves a string representation of this AES256 password.
*
* @return A string representation of this encoded password.
*/
@Override()
@NotNull()
public String toString()
{
final StringBuilder buffer = new StringBuilder();
toString(buffer);
return buffer.toString();
}
/**
* Appends a string representation of this AES256 encoded password to the
* provided buffer.
*
* @param buffer The buffer to which the information should be appended.
*/
public void toString(@NotNull final StringBuilder buffer)
{
buffer.append("AES256EncodedPassword(stringRepresentation='");
buffer.append(getStringRepresentation(true));
buffer.append("', encodingVersion=");
buffer.append(encodingVersion);
buffer.append(", paddingBytes=");
buffer.append(paddingBytes);
buffer.append(", encryptionSettingsDefinitionIDHex='");
StaticUtils.toHex(encryptionSettingsDefinitionID, buffer);
buffer.append("', keyFactorySaltBytesHex='");
StaticUtils.toHex(keyFactorySalt, buffer);
buffer.append("', initializationVectorBytesHex='");
StaticUtils.toHex(keyFactorySalt, buffer);
buffer.append("')");
}
}