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 2018-2019 Ping Identity Corporation
* All Rights Reserved.
*/
/*
* Copyright (C) 2018-2019 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.util;
import java.io.OutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.security.GeneralSecurityException;
import java.security.InvalidKeyException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.logging.Level;
import javax.crypto.Cipher;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import com.unboundid.asn1.ASN1Element;
import com.unboundid.asn1.ASN1Integer;
import com.unboundid.asn1.ASN1OctetString;
import com.unboundid.asn1.ASN1Sequence;
import com.unboundid.ldap.sdk.LDAPException;
import com.unboundid.ldap.sdk.ResultCode;
import static com.unboundid.util.UtilityMessages.*;
/**
* This class represents a data structure that will be used to hold information
* about the encryption performed by the {@link PassphraseEncryptedOutputStream}
* when writing encrypted data, and that will be used by a
* {@link PassphraseEncryptedInputStream} to obtain the settings needed to
* decrypt the encrypted data.
*
* The data associated with this class is completely threadsafe. The methods
* used to interact with input and output streams are not threadsafe in that
* nothing else should be attempting to read from/write to the stream at the
* same time.
*/
@NotMutable()
@ThreadSafety(level=ThreadSafetyLevel.MOSTLY_THREADSAFE)
public final class PassphraseEncryptedStreamHeader
implements Serializable
{
/**
* The BER type used for the header element that specifies the encoding
* version.
*/
static final byte TYPE_ENCODING_VERSION = (byte) 0x80;
/**
* The BER type used for the header element containing the key factory
* algorithm.
*/
static final byte TYPE_KEY_FACTORY_ALGORITHM = (byte) 0x81;
/**
* The BER type used for the header element containing the key factory
* iteration count.
*/
static final byte TYPE_KEY_FACTORY_ITERATION_COUNT = (byte) 0x82;
/**
* The BER type used for the header element containing the key factory salt.
*/
static final byte TYPE_KEY_FACTORY_SALT = (byte) 0x83;
/**
* The BER type used for the header element containing the key length in bits.
*/
static final byte TYPE_KEY_FACTORY_KEY_LENGTH_BITS = (byte) 0x84;
/**
* The BER type used for the header element containing the cipher
* transformation.
*/
static final byte TYPE_CIPHER_TRANSFORMATION = (byte) 0x85;
/**
* The BER type used for the header element containing the cipher
* initialization vector.
*/
static final byte TYPE_CIPHER_INITIALIZATION_VECTOR = (byte) 0x86;
/**
* The BER type used for the header element containing the key identifier.
*/
static final byte TYPE_KEY_IDENTIFIER = (byte) 0x87;
/**
* The BER type used for the header element containing the MAC algorithm name.
*/
static final byte TYPE_MAC_ALGORITHM = (byte) 0x88;
/**
* The BER type used for the header element containing the MAC value.
*/
static final byte TYPE_MAC_VALUE = (byte) 0x89;
/**
* The "magic" value that will appear at the start of the header.
*/
public static final byte[] MAGIC_BYTES =
{ 0x50, 0x55, 0x4C, 0x53, 0x50, 0x45, 0x53, 0x48 };
/**
* The encoding version for a v1 encoding.
*/
static final int ENCODING_VERSION_1 = 1;
/**
* The serial version UID for this serializable class.
*/
private static final long serialVersionUID = 6756983626170064762L;
// The initialization vector used when creating the cipher.
private final byte[] cipherInitializationVector;
// An encoded representation of this header.
private final byte[] encodedHeader;
// The salt used when generating the encryption key from the passphrase.
private final byte[] keyFactorySalt;
// A MAC of the header content.
private final byte[] macValue;
// The iteration count used when generating the encryption key from the
private final int keyFactoryIterationCount;
// passphrase.
// The length (in bits) of the encryption key generated from the passphrase.
private final int keyFactoryKeyLengthBits;
// The secret key generated from the passphrase.
private final SecretKey secretKey;
// The cipher transformation used for the encryption.
private final String cipherTransformation;
// The name of the key factory used to generate the encryption key from the
// passphrase.
private final String keyFactoryAlgorithm;
// An optional identifier that can be used to associate this header with some
// other encryption settings object.
private final String keyIdentifier;
// The algorithm used to generate a MAC of the header content.
private final String macAlgorithm;
/**
* Creates a new passphrase-encrypted stream header with the provided
* information.
*
* @param keyFactoryAlgorithm The key factory algorithm used to
* generate the encryption key from the
* passphrase. It must not be
* {@code null}.
* @param keyFactoryIterationCount The iteration count used to generate
* the encryption key from the passphrase.
* @param keyFactorySalt The salt used to generate the
* encryption key from the passphrase.
* It must not be {@code null}.
* @param keyFactoryKeyLengthBits The length (in bits) of the encryption
* key generated from the passphrase.
* @param cipherTransformation The cipher transformation used for the
* encryption. It must not be
* {@code null}.
* @param cipherInitializationVector The initialization vector used when
* creating the cipher. It must not be
* {@code null}.
* @param keyIdentifier An optional identifier that can be used
* to associate this passphrase-encrypted
* stream header with some other
* encryption settings object. It may
* optionally be {@code null}.
* @param secretKey The secret key generated from the
* passphrase.
* @param macAlgorithm The MAC algorithm to use when
* generating a MAC of the header
* contents. It must not be {@code null}.
* @param macValue A MAC of the header contents. It must
* not be {@code null}.
* @param encodedHeader An encoded representation of the
* header.
*/
private PassphraseEncryptedStreamHeader(
final String keyFactoryAlgorithm,
final int keyFactoryIterationCount, final byte[] keyFactorySalt,
final int keyFactoryKeyLengthBits,
final String cipherTransformation,
final byte[] cipherInitializationVector,
final String keyIdentifier, final SecretKey secretKey,
final String macAlgorithm, final byte[] macValue,
final byte[] encodedHeader)
{
this.keyFactoryAlgorithm = keyFactoryAlgorithm;
this.keyFactoryIterationCount = keyFactoryIterationCount;
this.keyFactorySalt = Arrays.copyOf(keyFactorySalt, keyFactorySalt.length);
this.keyFactoryKeyLengthBits = keyFactoryKeyLengthBits;
this.cipherTransformation = cipherTransformation;
this.cipherInitializationVector = Arrays.copyOf(cipherInitializationVector,
cipherInitializationVector.length);
this.keyIdentifier = keyIdentifier;
this.secretKey = secretKey;
this.macAlgorithm = macAlgorithm;
this.macValue = macValue;
this.encodedHeader = encodedHeader;
}
/**
* Creates a new passphrase-encrypted stream header with the provided
* information.
*
* @param passphrase The passphrase to use to generate the
* encryption key. It must not be
* {@code null}.
* @param keyFactoryAlgorithm The key factory algorithm used to
* generate the encryption key from the
* passphrase. It must not be
* {@code null}.
* @param keyFactoryIterationCount The iteration count used to generate
* the encryption key from the passphrase.
* @param keyFactorySalt The salt used to generate the
* encryption key from the passphrase.
* It must not be {@code null}.
* @param keyFactoryKeyLengthBits The length (in bits) of the encryption
* key generated from the passphrase.
* @param cipherTransformation The cipher transformation used for the
* encryption. It must not be
* {@code null}.
* @param cipherInitializationVector The initialization vector used when
* creating the cipher. It must not be
* {@code null}.
* @param keyIdentifier An optional identifier that can be used
* to associate this passphrase-encrypted
* stream header with some other
* encryption settings object. It may
* optionally be {@code null}.
* @param macAlgorithm The MAC algorithm to use when
* generating a MAC of the header
* contents. It must not be {@code null}.
*
* @throws GeneralSecurityException If a problem is encountered while
* generating the encryption key or MAC
* from the provided passphrase.
*/
PassphraseEncryptedStreamHeader(final char[] passphrase,
final String keyFactoryAlgorithm,
final int keyFactoryIterationCount,
final byte[] keyFactorySalt,
final int keyFactoryKeyLengthBits,
final String cipherTransformation,
final byte[] cipherInitializationVector,
final String keyIdentifier,
final String macAlgorithm)
throws GeneralSecurityException
{
this.keyFactoryAlgorithm = keyFactoryAlgorithm;
this.keyFactoryIterationCount = keyFactoryIterationCount;
this.keyFactorySalt = Arrays.copyOf(keyFactorySalt, keyFactorySalt.length);
this.keyFactoryKeyLengthBits = keyFactoryKeyLengthBits;
this.cipherTransformation = cipherTransformation;
this.cipherInitializationVector = Arrays.copyOf(cipherInitializationVector,
cipherInitializationVector.length);
this.keyIdentifier = keyIdentifier;
this.macAlgorithm = macAlgorithm;
secretKey = generateKeyReliably(keyFactoryAlgorithm, cipherTransformation,
passphrase, keyFactorySalt, keyFactoryIterationCount,
keyFactoryKeyLengthBits);
final ObjectPair headerPair = encode(keyFactoryAlgorithm,
keyFactoryIterationCount, this.keyFactorySalt, keyFactoryKeyLengthBits,
cipherTransformation, this.cipherInitializationVector, keyIdentifier,
secretKey, macAlgorithm);
encodedHeader = headerPair.getFirst();
macValue = headerPair.getSecond();
}
/**
* Generates an encoded representation of the header with the provided
* settings.
*
* @param keyFactoryAlgorithm The key factory algorithm used to
* generate the encryption key from the
* passphrase. It must not be
* {@code null}.
* @param keyFactoryIterationCount The iteration count used to generate
* the encryption key from the passphrase.
* @param keyFactorySalt The salt used to generate the
* encryption key from the passphrase.
* It must not be {@code null}.
* @param keyFactoryKeyLengthBits The length (in bits) of the encryption
* key generated from the passphrase.
* @param cipherTransformation The cipher transformation used for the
* encryption. It must not be
* {@code null}.
* @param cipherInitializationVector The initialization vector used when
* creating the cipher. It must not be
* {@code null}.
* @param keyIdentifier An optional identifier that can be used
* to associate this passphrase-encrypted
* stream header with some other
* encryption settings object. It may
* optionally be {@code null}.
* @param secretKey The secret key generated from the
* passphrase.
* @param macAlgorithm The MAC algorithm to use when
* generating a MAC of the header
* contents. It must not be {@code null}.
*
* @return The encoded representation of the header.
*
* @throws GeneralSecurityException If a problem is encountered while
* generating the MAC.
*/
private static ObjectPair encode(
final String keyFactoryAlgorithm,
final int keyFactoryIterationCount,
final byte[] keyFactorySalt,
final int keyFactoryKeyLengthBits,
final String cipherTransformation,
final byte[] cipherInitializationVector,
final String keyIdentifier,
final SecretKey secretKey,
final String macAlgorithm)
throws GeneralSecurityException
{
// Construct a list of all elements that will go in the header except the
// MAC value.
final ArrayList elements = new ArrayList<>(10);
elements.add(new ASN1Integer(TYPE_ENCODING_VERSION, ENCODING_VERSION_1));
elements.add(new ASN1OctetString(TYPE_KEY_FACTORY_ALGORITHM,
keyFactoryAlgorithm));
elements.add(new ASN1Integer(TYPE_KEY_FACTORY_ITERATION_COUNT,
keyFactoryIterationCount));
elements.add(new ASN1OctetString(TYPE_KEY_FACTORY_SALT, keyFactorySalt));
elements.add(new ASN1Integer(TYPE_KEY_FACTORY_KEY_LENGTH_BITS,
keyFactoryKeyLengthBits));
elements.add(new ASN1OctetString(TYPE_CIPHER_TRANSFORMATION,
cipherTransformation));
elements.add(new ASN1OctetString(TYPE_CIPHER_INITIALIZATION_VECTOR,
cipherInitializationVector));
if (keyIdentifier != null)
{
elements.add(new ASN1OctetString(TYPE_KEY_IDENTIFIER, keyIdentifier));
}
elements.add(new ASN1OctetString(TYPE_MAC_ALGORITHM, macAlgorithm));
// Compute the MAC value and add it to the list of elements.
final ByteStringBuffer macBuffer = new ByteStringBuffer();
for (final ASN1Element e : elements)
{
macBuffer.append(e.encode());
}
final Mac mac = Mac.getInstance(macAlgorithm);
mac.init(secretKey);
final byte[] macValue = mac.doFinal(macBuffer.toByteArray());
elements.add(new ASN1OctetString(TYPE_MAC_VALUE, macValue));
// Compute and return the encoded header.
final byte[] elementBytes = new ASN1Sequence(elements).encode();
final byte[] headerBytes =
new byte[MAGIC_BYTES.length + elementBytes.length];
System.arraycopy(MAGIC_BYTES, 0, headerBytes, 0, MAGIC_BYTES.length);
System.arraycopy(elementBytes, 0, headerBytes, MAGIC_BYTES.length,
elementBytes.length);
return new ObjectPair<>(headerBytes, macValue);
}
/**
* Writes an encoded representation of this passphrase-encrypted stream header
* to the provided output stream. The output stream will remain open after
* this method completes.
*
* @param outputStream The output stream to which the header will be
* written.
*
* @throws IOException If a problem is encountered while trying to write to
* the provided output stream.
*/
public void writeTo(final OutputStream outputStream)
throws IOException
{
outputStream.write(encodedHeader);
}
/**
* Reads a passphrase-encrypted stream header from the provided input stream.
* This method will not close the provided input stream, regardless of whether
* it returns successfully or throws an exception. If it returns
* successfully, then the position then the header bytes will have been
* consumed, so the next data to be read should be the data encrypted with
* these settings. If it throws an exception, then some unknown amount of
* data may have been read from the stream.
*
* @param inputStream The input stream from which to read the encoded
* passphrase-encrypted stream header. It must not be
* {@code null}.
* @param passphrase The passphrase to use to generate the encryption key.
* If this is {@code null}, then the header will be
* read, but no attempt will be made to validate the MAC,
* and it will not be possible to use this header to
* actually perform encryption or decryption. Providing
* a {@code null} value is primarily useful if
* information in the header (especially the key
* identifier) is needed to determine what passphrase to
* use.
*
* @return The passphrase-encrypted stream header that was read from the
* provided input stream.
*
* @throws IOException If a problem is encountered while attempting to read
* data from the provided input stream.
*
* @throws LDAPException If a problem is encountered while attempting to
* decode the data that was read.
*
* @throws InvalidKeyException If the MAC contained in the header does not
* match the expected value.
*
* @throws GeneralSecurityException If a problem is encountered while trying
* to generate the MAC.
*/
public static PassphraseEncryptedStreamHeader
readFrom(final InputStream inputStream,
final char[] passphrase)
throws IOException, LDAPException, InvalidKeyException,
GeneralSecurityException
{
// Read the magic from the input stream.
for (int i=0; i < MAGIC_BYTES.length; i++)
{
final int magicByte = inputStream.read();
if (magicByte < 0)
{
throw new LDAPException(ResultCode.DECODING_ERROR,
ERR_PW_ENCRYPTED_STREAM_HEADER_READ_END_OF_STREAM_IN_MAGIC.get());
}
else if (magicByte != MAGIC_BYTES[i])
{
throw new LDAPException(ResultCode.DECODING_ERROR,
ERR_PW_ENCRYPTED_STREAM_HEADER_READ_MAGIC_MISMATCH.get());
}
}
// The remainder of the header should be an ASN.1 sequence. Read and
// process that sequenced.
try
{
final ASN1Element headerSequenceElement =
ASN1Element.readFrom(inputStream);
if (headerSequenceElement == null)
{
throw new LDAPException(ResultCode.DECODING_ERROR,
ERR_PW_ENCRYPTED_STREAM_HEADER_READ_END_OF_STREAM_AFTER_MAGIC.get(
));
}
final byte[] encodedHeaderSequence = headerSequenceElement.encode();
final byte[] encodedHeader =
new byte[MAGIC_BYTES.length + encodedHeaderSequence.length];
System.arraycopy(MAGIC_BYTES, 0, encodedHeader, 0, MAGIC_BYTES.length);
System.arraycopy(encodedHeaderSequence, 0, encodedHeader,
MAGIC_BYTES.length, encodedHeaderSequence.length);
final ASN1Sequence headerSequence =
ASN1Sequence.decodeAsSequence(headerSequenceElement);
return decodeHeaderSequence(encodedHeader, headerSequence, passphrase);
}
catch (final IOException | LDAPException | GeneralSecurityException e)
{
Debug.debugException(e);
throw e;
}
catch (final Exception e)
{
Debug.debugException(e);
throw new LDAPException(ResultCode.DECODING_ERROR,
ERR_PW_ENCRYPTED_STREAM_HEADER_READ_ASN1_DECODE_ERROR.get(
StaticUtils.getExceptionMessage(e)),
e);
}
}
/**
* Decodes the contents of the provided byte array as a passphrase-encrypted
* stream header. The provided array must contain only the header, with no
* additional data before or after.
*
* @param encodedHeader The bytes that comprise the header to decode. It
* must not be {@code null} or empty.
* @param passphrase The passphrase to use to generate the encryption
* key. If this is {@code null}, then the header will
* be read, but no attempt will be made to validate the
* MAC, and it will not be possible to use this header
* to actually perform encryption or decryption.
* Providing a {@code null} value is primarily useful
* if information in the header (especially the key
* identifier) is needed to determine what passphrase
* to use.
*
* @return The passphrase-encrypted stream header that was decoded from the
* provided byte array.
*
* @throws LDAPException If a problem is encountered while trying to decode
* the data as a passphrase-encrypted stream header.
*
* @throws InvalidKeyException If the MAC contained in the header does not
* match the expected value.
*
* @throws GeneralSecurityException If a problem is encountered while trying
* to generate the MAC.
*/
public static PassphraseEncryptedStreamHeader
decode(final byte[] encodedHeader, final char[] passphrase)
throws LDAPException, InvalidKeyException, GeneralSecurityException
{
// Make sure that the array is long enough to hold a valid header.
if (encodedHeader.length <= MAGIC_BYTES.length)
{
throw new LDAPException(ResultCode.DECODING_ERROR,
ERR_PW_ENCRYPTED_STREAM_HEADER_DECODE_TOO_SHORT.get());
}
// Make sure that the array starts with the provided magic value.
for (int i=0; i < MAGIC_BYTES.length; i++)
{
if (encodedHeader[i] != MAGIC_BYTES[i])
{
throw new LDAPException(ResultCode.DECODING_ERROR,
ERR_PW_ENCRYPTED_STREAM_HEADER_DECODE_MAGIC_MISMATCH.get());
}
}
// Decode the remainder of the array as an ASN.1 sequence.
final ASN1Sequence headerSequence;
try
{
final byte[] encodedHeaderWithoutMagic =
new byte[encodedHeader.length - MAGIC_BYTES.length];
System.arraycopy(encodedHeader, MAGIC_BYTES.length,
encodedHeaderWithoutMagic, 0, encodedHeaderWithoutMagic.length);
headerSequence = ASN1Sequence.decodeAsSequence(encodedHeaderWithoutMagic);
}
catch (final Exception e)
{
Debug.debugException(e);
throw new LDAPException(ResultCode.DECODING_ERROR,
ERR_PW_ENCRYPTED_STREAM_HEADER_DECODE_ASN1_DECODE_ERROR.get(
StaticUtils.getExceptionMessage(e)),
e);
}
return decodeHeaderSequence(encodedHeader, headerSequence, passphrase);
}
/**
* Decodes the contents of the provided ASN.1 sequence as the portion of a
* passphrase-encrypted stream header that follows the magic bytes.
*
* @param encodedHeader The bytes that comprise the encoded header. It
* must not be {@code null} or empty.
* @param headerSequence The header sequence portion of the encoded header.
* @param passphrase The passphrase to use to generate the encryption
* key. If this is {@code null}, then the header will
* be read, but no attempt will be made to validate
* the MAC, and it will not be possible to use this
* header to actually perform encryption or
* decryption. Providing a {@code null} value is
* primarily useful if information in the header
* (especially the key identifier) is needed to
* determine what passphrase to use.
*
* @return The passphrase-encrypted stream header that was decoded from the
* provided ASN.1 sequence.
*
* @throws LDAPException If a problem is encountered while trying to decode
* the data as a passphrase-encrypted stream header.
*
* @throws InvalidKeyException If the MAC contained in the header does not
* match the expected value.
*
* @throws GeneralSecurityException If a problem is encountered while trying
* to generate the MAC.
*/
private static PassphraseEncryptedStreamHeader decodeHeaderSequence(
final byte[] encodedHeader,
final ASN1Sequence headerSequence,
final char[] passphrase)
throws LDAPException, InvalidKeyException, GeneralSecurityException
{
try
{
// The first element must be the encoding version, and it must be 1.
final ASN1Element[] headerElements = headerSequence.elements();
final ASN1Integer versionElement =
ASN1Integer.decodeAsInteger(headerElements[0]);
if (versionElement.intValue() != ENCODING_VERSION_1)
{
throw new LDAPException(ResultCode.DECODING_ERROR,
ERR_PW_ENCRYPTED_HEADER_SEQUENCE_UNSUPPORTED_VERSION.get(
versionElement.intValue()));
}
// The second element must be the key factory algorithm.
final String keyFactoryAlgorithm =
ASN1OctetString.decodeAsOctetString(headerElements[1]).stringValue();
// The third element must be the key factory iteration count.
final int keyFactoryIterationCount =
ASN1Integer.decodeAsInteger(headerElements[2]).intValue();
// The fourth element must be the key factory salt.
final byte[] keyFactorySalt =
ASN1OctetString.decodeAsOctetString(headerElements[3]).getValue();
// The fifth element must be the key length in bits.
final int keyFactoryKeyLengthBits =
ASN1Integer.decodeAsInteger(headerElements[4]).intValue();
// The sixth element must be the cipher transformation.
final String cipherTransformation =
ASN1OctetString.decodeAsOctetString(headerElements[5]).stringValue();
// The seventh element must be the initialization vector.
final byte[] cipherInitializationVector =
ASN1OctetString.decodeAsOctetString(headerElements[6]).getValue();
// Look through any remaining elements and decode them as appropriate.
byte[] macValue = null;
int macValuePos = -1;
String keyIdentifier = null;
String macAlgorithm = null;
for (int i=7; i < headerElements.length; i++)
{
switch (headerElements[i].getType())
{
case TYPE_KEY_IDENTIFIER:
keyIdentifier = ASN1OctetString.decodeAsOctetString(
headerElements[i]).stringValue();
break;
case TYPE_MAC_ALGORITHM:
macAlgorithm = ASN1OctetString.decodeAsOctetString(
headerElements[i]).stringValue();
break;
case TYPE_MAC_VALUE:
macValuePos = i;
macValue = ASN1OctetString.decodeAsOctetString(
headerElements[i]).getValue();
break;
default:
throw new LDAPException(ResultCode.DECODING_ERROR,
ERR_PW_ENCRYPTED_HEADER_SEQUENCE_UNRECOGNIZED_ELEMENT_TYPE.get(
StaticUtils.toHex(headerElements[i].getType())));
}
}
// Compute a MAC of the appropriate header elements and verify that it
// matches the value contained in the header. If it doesn't match, then
// it means the provided passphrase was invalid.
final SecretKey secretKey;
if (passphrase == null)
{
secretKey = null;
}
else
{
secretKey = generateKeyReliably(keyFactoryAlgorithm,
cipherTransformation, passphrase, keyFactorySalt,
keyFactoryIterationCount, keyFactoryKeyLengthBits);
final ByteStringBuffer macBuffer = new ByteStringBuffer();
for (int i=0; i < headerElements.length; i++)
{
if (i != macValuePos)
{
macBuffer.append(headerElements[i].encode());
}
}
final Mac mac = Mac.getInstance(macAlgorithm);
mac.init(secretKey);
final byte[] computedMacValue = mac.doFinal(macBuffer.toByteArray());
if (! Arrays.equals(computedMacValue, macValue))
{
throw new InvalidKeyException(
ERR_PW_ENCRYPTED_HEADER_SEQUENCE_BAD_PW.get());
}
}
return new PassphraseEncryptedStreamHeader(keyFactoryAlgorithm,
keyFactoryIterationCount, keyFactorySalt, keyFactoryKeyLengthBits,
cipherTransformation, cipherInitializationVector, keyIdentifier,
secretKey, macAlgorithm, macValue, encodedHeader);
}
catch (final LDAPException | GeneralSecurityException e)
{
Debug.debugException(e);
throw e;
}
catch (final Exception e)
{
Debug.debugException(e);
throw new LDAPException(ResultCode.DECODING_ERROR,
ERR_PW_ENCRYPTED_HEADER_SEQUENCE_DECODE_ERROR.get(
StaticUtils.getExceptionMessage(e)),
e);
}
}
/**
* We have seen situations where SecretKeyFactory#generateSecret returns
* inconsistent results for the same parameters. This can lead to data being
* encrypted or decrypted incorrectly. To avoid this, this method computes the
* key multiple times, and only returns the key once an identical key has been
* generated three times in a row.
*
* @param keyFactoryAlgorithm The key factory algorithm to use to
* generate the encryption key from the
* passphrase. It must not be {@code null}.
* @param cipherTransformation The cipher transformation used for the
* encryption key. It must not be {@code
* null}.
* @param passphrase The passphrase to use to generate the
* encryption key. It must not be
* {@code null}.
* @param keyFactorySalt The salt to use to generate the
* encryption key from the passphrase.
* It must not be {@code null}.
* @param keyFactoryIterationCount The iteration count to use to generate
* the encryption key from the passphrase.
* @param keyFactoryKeyLengthBits The length (in bits) of the encryption
* key generated from the passphrase.
*
* @return A SecretKey that has been consistently generated from the provided
* parameters.
*
* @throws GeneralSecurityException If a problem is encountered while
* generating the encryption key including
* not being able to generate a consistent
* key.
*/
private static SecretKey generateKeyReliably(
final String keyFactoryAlgorithm,
final String cipherTransformation,
final char[] passphrase,
final byte[] keyFactorySalt,
final int keyFactoryIterationCount,
final int keyFactoryKeyLengthBits)
throws GeneralSecurityException
{
byte[] prev = null;
byte[] prev2 = null;
final int iterations = 10;
for (int i = 0; i < iterations; i++)
{
final SecretKeyFactory keyFactory =
SecretKeyFactory.getInstance(keyFactoryAlgorithm);
final String cipherAlgorithm = cipherTransformation.substring(0,
cipherTransformation.indexOf('/'));
final PBEKeySpec pbeKeySpec = new PBEKeySpec(passphrase, keyFactorySalt,
keyFactoryIterationCount, keyFactoryKeyLengthBits);
final SecretKey secretKey = new SecretKeySpec(
keyFactory.generateSecret(pbeKeySpec).getEncoded(),
cipherAlgorithm);
final byte[] encoded = secretKey.getEncoded();
// If this encoded key is the same as the previous one, and the one before
// that, then it was likely computed correctly, so return it.
if (Arrays.equals(encoded, prev) && Arrays.equals(encoded, prev2))
{
if (i > 2)
{
Debug.debug(Level.WARNING, DebugType.OTHER,
"The secret key was generated inconsistently initially, but " +
"after " + i + " iterations, we were able to generate a " +
"consistent value.");
}
return secretKey;
}
prev2 = prev;
prev = encoded;
}
Debug.debug(Level.SEVERE, DebugType.OTHER,
"Even after " + iterations + " iterations, the secret key could not " +
"be reliably generated.");
throw new InvalidKeyException(
ERR_PW_ENCRYPTED_STREAM_HEADER_CANNOT_GENERATE_KEY.get());
}
/**
* Creates a {@code Cipher} for the specified purpose.
*
* @param mode The mode to use for the cipher. It must be one of
* {@code Cipher.ENCRYPT_MODE} or {@code Cipher.DECRYPT_MODE}.
*
* @return The {@code Cipher} instance that was created.
*
* @throws InvalidKeyException If no passphrase was provided when decoding
* this passphrase-encrypted stream header.
*
* @throws GeneralSecurityException If a problem is encountered while
* creating the cipher.
*/
Cipher createCipher(final int mode)
throws InvalidKeyException, GeneralSecurityException
{
if (secretKey == null)
{
throw new InvalidKeyException(
ERR_PW_ENCRYPTED_HEADER_NO_KEY_AVAILABLE.get());
}
final Cipher cipher = Cipher.getInstance(cipherTransformation);
cipher.init(mode, secretKey,
new IvParameterSpec(cipherInitializationVector));
return cipher;
}
/**
* Retrieves the key factory algorithm used to generate the encryption key
* from the passphrase.
*
* @return The key factory algorithm used to generate the encryption key from
* the passphrase.
*/
public String getKeyFactoryAlgorithm()
{
return keyFactoryAlgorithm;
}
/**
* Retrieves the iteration count used to generate the encryption key from the
* passphrase.
*
* @return The iteration count used to generate the encryption key from the
* passphrase.
*/
public int getKeyFactoryIterationCount()
{
return keyFactoryIterationCount;
}
/**
* Retrieves the salt used to generate the encryption key from the passphrase.
*
* @return The salt used to generate the encryption key from the passphrase.
*/
public byte[] getKeyFactorySalt()
{
return Arrays.copyOf(keyFactorySalt, keyFactorySalt.length);
}
/**
* Retrieves the length (in bits) of the encryption key generated from the
* passphrase.
*
* @return The length (in bits) of the encryption key generated from the
* passphrase.
*/
public int getKeyFactoryKeyLengthBits()
{
return keyFactoryKeyLengthBits;
}
/**
* Retrieves the cipher transformation used for the encryption.
*
* @return The cipher transformation used for the encryption.
*/
public String getCipherTransformation()
{
return cipherTransformation;
}
/**
* Retrieves the cipher initialization vector used for the encryption.
*
* @return The cipher initialization vector used for the encryption.
*/
public byte[] getCipherInitializationVector()
{
return Arrays.copyOf(cipherInitializationVector,
cipherInitializationVector.length);
}
/**
* Retrieves the key identifier used to associate this passphrase-encrypted
* stream header with some other encryption settings object, if defined.
*
* @return The key identifier used to associate this passphrase-encrypted
* stream header with some other encryption settings object, or
* {@code null} if none was provided.
*/
public String getKeyIdentifier()
{
return keyIdentifier;
}
/**
* Retrieves the algorithm used to generate a MAC of the header content.
*
* @return The algorithm used to generate a MAC of the header content.
*/
public String getMACAlgorithm()
{
return macAlgorithm;
}
/**
* Retrieves an encoded representation of this passphrase-encrypted stream
* header.
*
* @return An encoded representation of this passphrase-encrypted stream
* header.
*/
public byte[] getEncodedHeader()
{
return Arrays.copyOf(encodedHeader, encodedHeader.length);
}
/**
* Indicates whether this passphrase-encrypted stream header includes a secret
* key. If this header was read or decoded with no passphrase provided, then
* it will not have a secret key, which means the MAC will not have been
* validated and it cannot be used to encrypt or decrypt data.
*
* @return {@code true} if this passphrase-encrypted stream header includes a
* secret key and can be used to encrypt or decrypt data, or
* {@code false} if not.
*/
public boolean isSecretKeyAvailable()
{
return (secretKey != null);
}
/**
* Retrieves a string representation of this passphrase-encrypted stream
* header.
*
* @return A string representation of this passphrase-encrypted stream
* header.
*/
@Override()
public String toString()
{
final StringBuilder buffer = new StringBuilder();
toString(buffer);
return buffer.toString();
}
/**
* Appends a string representation of this passphrase-encrypted stream header
* to the provided buffer.
*
* @param buffer The buffer to which the information should be appended.
*/
public void toString(final StringBuilder buffer)
{
buffer.append("PassphraseEncryptedStreamHeader(keyFactoryAlgorithm='");
buffer.append(keyFactoryAlgorithm);
buffer.append("', keyFactoryIterationCount=");
buffer.append(keyFactoryIterationCount);
buffer.append(", keyFactorySaltLengthBytes=");
buffer.append(keyFactorySalt.length);
buffer.append(", keyFactoryKeyLengthBits=");
buffer.append(keyFactoryKeyLengthBits);
buffer.append(", cipherTransformation'=");
buffer.append(cipherTransformation);
buffer.append("', cipherInitializationVectorLengthBytes=");
buffer.append(cipherInitializationVector.length);
buffer.append('\'');
if (keyIdentifier != null)
{
buffer.append(", keyIdentifier='");
buffer.append(keyIdentifier);
buffer.append('\'');
}
buffer.append(", macAlgorithm='");
buffer.append(macAlgorithm);
buffer.append("', macValueLengthBytes=");
buffer.append(macValue.length);
buffer.append(", secretKeyAvailable=");
buffer.append(isSecretKeyAvailable());
buffer.append(", encodedHeaderLengthBytes=");
buffer.append(encodedHeader.length);
buffer.append(')');
}
}