org.cryptacular.CiphertextHeader Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of cryptacular Show documentation
Show all versions of cryptacular Show documentation
The spectacular complement to the Bouncy Castle crypto API for Java.
The newest version!
/* See LICENSE for licensing and NOTICE for copyright. */
package org.cryptacular;
import java.io.IOException;
import java.io.InputStream;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import org.cryptacular.util.ByteUtil;
/**
* Cleartext header prepended to ciphertext providing data required for decryption.
*
* Data format:
*
*
+-----+----------+-------+------------+---------+
| Len | NonceLen | Nonce | KeyNameLen | KeyName |
+-----+----------+-------+------------+---------+
*
*
* Where fields are defined as follows:
*
*
* - Len - Total header length in bytes (4-byte integer)
* - NonceLen - Nonce length in bytes (4-byte integer)
* - Nonce - Nonce bytes (variable length)
* - KeyNameLen (OPTIONAL) - Key name length in bytes (4-byte integer)
* - KeyName (OPTIONAL) - Key name encoded as bytes in platform-specific encoding (variable length)
*
*
* The last two fields are optional and provide support for multiple keys at the encryption provider. A common case
* for multiple keys is key rotation; by tagging encrypted data with a key name, an old key may be retrieved by name to
* decrypt outstanding data which will be subsequently re-encrypted with a new key.
*
* @author Middleware Services
*
* @deprecated Superseded by {@link CiphertextHeaderV2}
*/
@Deprecated
public class CiphertextHeader
{
/** Maximum nonce length in bytes. */
protected static final int MAX_NONCE_LEN = 255;
/** Maximum key name length in bytes. */
protected static final int MAX_KEYNAME_LEN = 500;
/** Header nonce field value. */
protected final byte[] nonce;
/** Header key name field value. */
protected String keyName;
/** Header length in bytes. */
protected int length;
/**
* Creates a new instance with only a nonce.
*
* @param nonce Nonce bytes.
*/
public CiphertextHeader(final byte[] nonce)
{
this(nonce, null);
}
/**
* Creates a new instance with a nonce and named key.
*
* @param nonce Nonce bytes.
* @param keyName Key name.
*/
public CiphertextHeader(final byte[] nonce, final String keyName)
{
if (nonce.length > MAX_NONCE_LEN) {
throw new IllegalArgumentException("Nonce exceeds size limit in bytes (" + MAX_NONCE_LEN + ")");
}
if (keyName != null) {
if (ByteUtil.toBytes(keyName).length > MAX_KEYNAME_LEN) {
throw new IllegalArgumentException("Key name exceeds size limit in bytes (" + MAX_KEYNAME_LEN + ")");
}
}
this.nonce = nonce;
this.keyName = keyName;
length = computeLength();
}
/**
* Gets the header length in bytes.
*
* @return Header length in bytes.
*/
public int getLength()
{
return this.length;
}
/**
* Gets the bytes of the nonce/IV.
*
* @return Nonce bytes.
*/
public byte[] getNonce()
{
return this.nonce;
}
/**
* Gets the encryption key name stored in the header.
*
* @return Encryption key name.
*/
public String getKeyName()
{
return this.keyName;
}
/**
* Encodes the header into bytes.
*
* @return Byte representation of header.
*/
public byte[] encode()
{
final ByteBuffer bb = ByteBuffer.allocate(length);
bb.order(ByteOrder.BIG_ENDIAN);
bb.putInt(length);
bb.putInt(nonce.length);
bb.put(nonce);
if (keyName != null) {
final byte[] b = keyName.getBytes();
bb.putInt(b.length);
bb.put(b);
}
return bb.array();
}
/**
* @return Length of this header encoded as bytes.
*/
protected int computeLength()
{
int len = 8 + nonce.length;
if (keyName != null) {
len += 4 + keyName.getBytes().length;
}
return len;
}
/**
* Creates a header from encrypted data containing a cleartext header prepended to the start.
*
* @param data Encrypted data with prepended header data.
*
* @return Decoded header.
*
* @throws EncodingException when ciphertext header cannot be decoded.
*/
public static CiphertextHeader decode(final byte[] data) throws EncodingException
{
final ByteBuffer bb = ByteBuffer.wrap(data);
bb.order(ByteOrder.BIG_ENDIAN);
final int length = bb.getInt();
if (length < 0) {
throw new EncodingException("Bad ciphertext header");
}
final byte[] nonce;
final int nonceLen;
try {
nonceLen = bb.getInt();
if (nonceLen > MAX_NONCE_LEN) {
throw new EncodingException("Bad ciphertext header: maximum nonce length exceeded");
}
nonce = new byte[nonceLen];
bb.get(nonce);
} catch (IndexOutOfBoundsException | BufferUnderflowException e) {
throw new EncodingException("Bad ciphertext header");
}
String keyName = null;
if (length > nonce.length + 8) {
final byte[] b;
final int keyLen;
try {
keyLen = bb.getInt();
if (keyLen > MAX_KEYNAME_LEN) {
throw new EncodingException("Bad ciphertext header: maximum key length exceeded");
}
b = new byte[keyLen];
bb.get(b);
keyName = new String(b);
} catch (IndexOutOfBoundsException | BufferUnderflowException e) {
throw new EncodingException("Bad ciphertext header");
}
}
return new CiphertextHeader(nonce, keyName);
}
/**
* Creates a header from encrypted data containing a cleartext header prepended to the start.
*
* @param input Input stream that is positioned at the start of ciphertext header data.
*
* @return Decoded header.
*
* @throws EncodingException when ciphertext header cannot be decoded.
* @throws StreamException on stream IO errors.
*/
public static CiphertextHeader decode(final InputStream input) throws EncodingException, StreamException
{
final int length = ByteUtil.readInt(input);
if (length < 0) {
throw new EncodingException("Bad ciphertext header");
}
final byte[] nonce;
final int nonceLen;
try {
nonceLen = ByteUtil.readInt(input);
if (nonceLen > MAX_NONCE_LEN) {
throw new EncodingException("Bad ciphertext header: maximum nonce size exceeded");
}
nonce = new byte[nonceLen];
input.read(nonce);
} catch (ArrayIndexOutOfBoundsException e) {
throw new EncodingException("Bad ciphertext header");
} catch (IOException e) {
throw new StreamException(e);
}
String keyName = null;
if (length > nonce.length + 8) {
final byte[] b;
final int keyLen;
try {
keyLen = ByteUtil.readInt(input);
if (keyLen > MAX_KEYNAME_LEN) {
throw new EncodingException("Bad ciphertext header: maximum key length exceeded");
}
b = new byte[keyLen];
input.read(b);
} catch (ArrayIndexOutOfBoundsException e) {
throw new EncodingException("Bad ciphertext header");
} catch (IOException e) {
throw new StreamException(e);
}
keyName = new String(b);
}
return new CiphertextHeader(nonce, keyName);
}
}