package com.google.crypto.tink.subtle;
import com.google.crypto.tink.Mac;
import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException;
import java.util.Arrays;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
public final class AesCmac implements Mac {
static final int MIN_TAG_SIZE_IN_BYTES = 10;
private final SecretKey keySpec;
private final int tagSizeInBytes;
private byte[] subKey1;
private byte[] subKey2;
private static Cipher instance() throws GeneralSecurityException {
return EngineFactory.CIPHER.getInstance("AES/ECB/NoPadding");
}
public AesCmac(final byte[] key, int tagSizeInBytes) throws GeneralSecurityException {
Validators.validateAesKeySize(key.length);
if (tagSizeInBytes < MIN_TAG_SIZE_IN_BYTES) {
throw new InvalidAlgorithmParameterException(
"tag size too small, min is " + MIN_TAG_SIZE_IN_BYTES + " bytes");
}
if (tagSizeInBytes > AesUtil.BLOCK_SIZE) {
throw new InvalidAlgorithmParameterException(
"tag size too large, max is " + AesUtil.BLOCK_SIZE + " bytes");
}
keySpec = new SecretKeySpec(key, "AES");
this.tagSizeInBytes = tagSizeInBytes;
generateSubKeys();
}
@Override
public byte[] computeMac(final byte[] data) throws GeneralSecurityException {
Cipher aes = instance();
aes.init(Cipher.ENCRYPT_MODE, keySpec);
int n = Math.max(1, (int) Math.ceil((double) data.length / AesUtil.BLOCK_SIZE));
boolean flag = (n * AesUtil.BLOCK_SIZE == data.length);
byte[] mLast;
if (flag) {
mLast = Bytes.xor(data, (n - 1) * AesUtil.BLOCK_SIZE, subKey1, 0, AesUtil.BLOCK_SIZE);
} else {
mLast =
Bytes.xor(
AesUtil.cmacPad(Arrays.copyOfRange(data, (n - 1) * AesUtil.BLOCK_SIZE, data.length)),
subKey2);
}
byte[] x = new byte[AesUtil.BLOCK_SIZE];
byte[] y;
for (int i = 0; i < n - 1; i++) {
y = Bytes.xor(x, 0, data, i * AesUtil.BLOCK_SIZE, AesUtil.BLOCK_SIZE);
x = aes.doFinal(y);
}
y = Bytes.xor(mLast, x);
byte[] tag = new byte[tagSizeInBytes];
System.arraycopy(aes.doFinal(y), 0, tag, 0, tagSizeInBytes);
return tag;
}
@Override
public void verifyMac(final byte[] mac, byte[] data) throws GeneralSecurityException {
if (!Bytes.equal(mac, this.computeMac(data))) {
throw new GeneralSecurityException("invalid MAC");
}
}
private void generateSubKeys() throws GeneralSecurityException {
Cipher aes = instance();
aes.init(Cipher.ENCRYPT_MODE, keySpec);
byte[] zeroes = new byte[AesUtil.BLOCK_SIZE];
byte[] l = aes.doFinal(zeroes);
subKey1 = AesUtil.dbl(l);
subKey2 = AesUtil.dbl(subKey1);
}
}