package im.actor.crypto.box;
import im.actor.crypto.IntegrityException;
import im.actor.crypto.primitives.BlockCipher;
import im.actor.crypto.primitives.util.ByteStrings;
import im.actor.crypto.primitives.Digest;
import im.actor.crypto.primitives.Padding;
import im.actor.crypto.primitives.modes.CBCBlockCipher;
import im.actor.crypto.primitives.hmac.HMAC;
import im.actor.crypto.primitives.padding.PKCS7Padding;
/**
* CBC-encrypted package with HMAC (MAC-THEN-ENCRYPT).
* <p/>
* Package format:
* 1) content.length[4 bytes]
* 2) content[content.length]
* 4) HMAC[HMAC.lenght]
* 5) PCKS#7 padding
* Then this package is encrypted with baseCipher in CBC mode
*
* @author Steve Kite (steve@actor.im)
*/
public class CBCHmacBox {
private final CBCBlockCipher cbcBlockCipher;
private final BlockCipher baseCipher;
private final Digest baseDigest;
private final HMAC hmac;
private final byte[] hmacKey;
private final Padding padding;
public CBCHmacBox(BlockCipher baseCipher, Digest baseDigest, byte[] hmacKey) {
this.cbcBlockCipher = new CBCBlockCipher(baseCipher);
this.baseCipher = baseCipher;
this.baseDigest = baseDigest;
this.hmacKey = hmacKey;
this.padding = new PKCS7Padding();
this.hmac = new HMAC(hmacKey, baseDigest);
}
public byte[] encryptPackage(byte[] header, byte[] iv, byte[] content) throws IntegrityException {
if (iv.length != 16) {
throw new IntegrityException("IV MUST be 16 bytes long!");
}
int paddingLength = 0;
int length =/*Digest size*/ 32 + /*Length prefix*/ 4 + content.length + /*padding length prefix*/1;
if (length % 32 != 0) {
paddingLength = 32 - length % 32;
length += paddingLength;
}
byte[] res = new byte[length];
ByteStrings.write(res, 0, ByteStrings.intToBytes(content.length), 0, 4);
ByteStrings.write(res, 4, content, 0, content.length);
hmac.reset();
hmac.update(header, 0, header.length);
hmac.update(iv, 0, 16);
hmac.update(res, 0, content.length + 4);
hmac.doFinal(res, content.length + 4);
padding.padding(res, res.length - paddingLength - 1, paddingLength);
res[res.length - 1] = (byte) paddingLength;
return cbcBlockCipher.encrypt(iv, res);
}
public byte[] decryptPackage(byte[] header, byte[] iv, byte[] encryptedContent) throws IntegrityException {
if (iv.length != 16) {
throw new IntegrityException("IV MUST be 16 bytes long!");
}
byte[] content = cbcBlockCipher.decrypt(iv, encryptedContent);
byte[] hmacValue = new byte[32];
int length = ByteStrings.bytesToInt(content);
hmac.reset();
hmac.update(header, 0, header.length);
hmac.update(iv, 0, 16);
hmac.update(content, 0, length + 4);
hmac.doFinal(hmacValue, 0);
for (int i = 0; i < 32; i++) {
if (hmacValue[i] != content[length + 4 + i]) {
throw new IntegrityException("Broken package!");
}
}
int paddingSize = content[content.length - 1] & 0xFF;
if (!padding.validate(content, content.length - paddingSize, paddingSize)) {
throw new IntegrityException("Broken package!");
}
return ByteStrings.substring(content, 4, length);
}
}