com.unboundid.ldap.sdk.unboundidds.AES256EncodedPassword Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of unboundid-ldapsdk Show documentation
Show all versions of unboundid-ldapsdk Show documentation
The UnboundID LDAP SDK for Java is a fast, comprehensive, and easy-to-use
Java API for communicating with LDAP directory servers and performing
related tasks like reading and writing LDIF, encoding and decoding data
using base64 and ASN.1 BER, and performing secure communication. This
package contains the Standard Edition of the LDAP SDK, which is a
complete, general-purpose library for communicating with LDAPv3 directory
servers.
/*
* Copyright 2020-2022 Ping Identity Corporation
* All Rights Reserved.
*/
/*
* Copyright 2020-2022 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-2022 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:
*
* - Cipher Transformation: AES/GCM/NoPadding
* - Key Factory Algorithm: PBKDF2WithHmacSHA512
* - Key Factory Iteration Count: 32,768
* - Key Factory Salt Length: 128 bits (16 bytes)
* - Key Factory Generated Key length: 256 bits (32 bytes)
* - Initialization Vector Length: 128 bits (16 bytes)
* - GCM Tag Length: 128 bits
* - Padding Modulus: 16
*
*
* -
* 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("')");
}
}