package org.bouncycastle.crypto.engines;
import org.bouncycastle.crypto.BlockCipher;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.DataLengthException;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.Wrapper;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.crypto.params.ParametersWithIV;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.util.Arrays;
/**
* an implementation of the AES Key Wrapper from the NIST Key Wrap
* Specification as described in RFC 3394.
* <p>
* For further details see: <a href="http://www.ietf.org/rfc/rfc3394.txt">http://www.ietf.org/rfc/rfc3394.txt</a>
* and <a href="http://csrc.nist.gov/encryption/kms/key-wrap.pdf">http://csrc.nist.gov/encryption/kms/key-wrap.pdf</a>.
*/
public class RFC3394WrapEngine
implements Wrapper
{
private BlockCipher engine;
private boolean wrapCipherMode;
private KeyParameter param;
private boolean forWrapping;
private byte[] iv = {
(byte)0xa6, (byte)0xa6, (byte)0xa6, (byte)0xa6,
(byte)0xa6, (byte)0xa6, (byte)0xa6, (byte)0xa6 };
/**
* Create a RFC 3394 WrapEngine specifying the encrypt for wrapping, decrypt for unwrapping.
*
* @param engine the block cipher to be used for wrapping.
*/
public RFC3394WrapEngine(BlockCipher engine)
{
this(engine, false);
}
/**
* Create a RFC 3394 WrapEngine specifying the direction for wrapping and unwrapping..
*
* @param engine the block cipher to be used for wrapping.
* @param useReverseDirection true if engine should be used in decryption mode for wrapping, false otherwise.
*/
public RFC3394WrapEngine(BlockCipher engine, boolean useReverseDirection)
{
this.engine = engine;
this.wrapCipherMode = (useReverseDirection) ? false : true;
}
public void init(
boolean forWrapping,
CipherParameters param)
{
this.forWrapping = forWrapping;
if (param instanceof ParametersWithRandom)
{
param = ((ParametersWithRandom) param).getParameters();
}
if (param instanceof KeyParameter)
{
this.param = (KeyParameter)param;
}
else if (param instanceof ParametersWithIV)
{
this.iv = ((ParametersWithIV)param).getIV();
this.param = (KeyParameter)((ParametersWithIV) param).getParameters();
if (this.iv.length != 8)
{
throw new IllegalArgumentException("IV not equal to 8");
}
}
}
public String getAlgorithmName()
{
return engine.getAlgorithmName();
}
public byte[] wrap(
byte[] in,
int inOff,
int inLen)
{
if (!forWrapping)
{
throw new IllegalStateException("not set for wrapping");
}
int n = inLen / 8;
if ((n * 8) != inLen)
{
throw new DataLengthException("wrap data must be a multiple of 8 bytes");
}
byte[] block = new byte[inLen + iv.length];
byte[] buf = new byte[8 + iv.length];
System.arraycopy(iv, 0, block, 0, iv.length);
System.arraycopy(in, inOff, block, iv.length, inLen);
engine.init(wrapCipherMode, param);
for (int j = 0; j != 6; j++)
{
for (int i = 1; i <= n; i++)
{
System.arraycopy(block, 0, buf, 0, iv.length);
System.arraycopy(block, 8 * i, buf, iv.length, 8);
engine.processBlock(buf, 0, buf, 0);
int t = n * j + i;
for (int k = 1; t != 0; k++)
{
byte v = (byte)t;
buf[iv.length - k] ^= v;
t >>>= 8;
}
System.arraycopy(buf, 0, block, 0, 8);
System.arraycopy(buf, 8, block, 8 * i, 8);
}
}
return block;
}
public byte[] unwrap(
byte[] in,
int inOff,
int inLen)
throws InvalidCipherTextException
{
if (forWrapping)
{
throw new IllegalStateException("not set for unwrapping");
}
int n = inLen / 8;
if ((n * 8) != inLen)
{
throw new InvalidCipherTextException("unwrap data must be a multiple of 8 bytes");
}
byte[] block = new byte[inLen - iv.length];
byte[] a = new byte[iv.length];
byte[] buf = new byte[8 + iv.length];
System.arraycopy(in, inOff, a, 0, iv.length);
System.arraycopy(in, inOff + iv.length, block, 0, inLen - iv.length);
engine.init(!wrapCipherMode, param);
n = n - 1;
for (int j = 5; j >= 0; j--)
{
for (int i = n; i >= 1; i--)
{
System.arraycopy(a, 0, buf, 0, iv.length);
System.arraycopy(block, 8 * (i - 1), buf, iv.length, 8);
int t = n * j + i;
for (int k = 1; t != 0; k++)
{
byte v = (byte)t;
buf[iv.length - k] ^= v;
t >>>= 8;
}
engine.processBlock(buf, 0, buf, 0);
System.arraycopy(buf, 0, a, 0, 8);
System.arraycopy(buf, 8, block, 8 * (i - 1), 8);
}
}
if (!Arrays.constantTimeAreEqual(a, iv))
{
throw new InvalidCipherTextException("checksum failed");
}
return block;
}
}