blob: e5eeaa0a8ee78971bc3584fff11ccd8e061559aa [file] [log] [blame]
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.internal.net.ipsec.ike.crypto;
import com.android.internal.util.HexDump;
import java.nio.ByteBuffer;
import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.util.Arrays;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.ShortBufferException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class AesXCbcImpl {
// Use AES CBC as per NIST Special Publication 800-38B 6.2
private static final String AES_CBC = "AES/CBC/NoPadding";
private static final int AES_CBC_IV_LEN = 16;
private static final int AES_CBC_BLOCK_LEN = 16;
private static final int AES_XCBC_96_MAC_LEN = 12;
private final Cipher mCipher;
private static final String KEY1_SEED_HEX_STRING = "01010101010101010101010101010101";
private static final String KEY2_SEED_HEX_STRING = "02020202020202020202020202020202";
private static final String KEY3_SEED_HEX_STRING = "03030303030303030303030303030303";
private static final byte[] E_INITIAL = new byte[AES_CBC_BLOCK_LEN];
public AesXCbcImpl() throws GeneralSecurityException {
mCipher = Cipher.getInstance(AES_CBC);
}
/** Calculate the MAC */
public byte[] mac(byte[] keyBytes, byte[] dataToSign, boolean needTruncation) {
// See RFC 3566#section-4 for the algorithm
int blockSize = mCipher.getBlockSize();
boolean isPaddingNeeded = dataToSign.length % blockSize != 0;
byte[] paddedData = dataToSign;
if (isPaddingNeeded) {
paddedData = padData(dataToSign, blockSize);
}
// (1) Derive 3 128-bit keys (K1, K2 and K3) from the 128-bit secret key K, as follows:
// K1 = 0x01010101010101010101010101010101 encrypted with Key K
// K2 = 0x02020202020202020202020202020202 encrypted with Key K
// K3 = 0x03030303030303030303030303030303 encrypted with Key K
byte[] key1 = encryptAesBlock(keyBytes, HexDump.hexStringToByteArray(KEY1_SEED_HEX_STRING));
byte[] key2 = encryptAesBlock(keyBytes, HexDump.hexStringToByteArray(KEY2_SEED_HEX_STRING));
byte[] key3 = encryptAesBlock(keyBytes, HexDump.hexStringToByteArray(KEY3_SEED_HEX_STRING));
// (2) Define E[0] = 0x00000000000000000000000000000000
byte[] e = E_INITIAL;
int numMessageBlocks = paddedData.length / blockSize;
// (3) For each block M[i], where i = 1 ... n-1:
// XOR M[i] with E[i-1], then encrypt the result with Key K1, yielding E[i].
byte[] message;
for (int i = 0; i < numMessageBlocks - 1; ++i) {
message = Arrays.copyOfRange(paddedData, i * blockSize, i * blockSize + blockSize);
message = xorByteArrays(message, e);
e = encryptAesBlock(key1, message);
}
// (4) For block M[n]:
// a) If the blocksize of M[n] is 128 bits:
// XOR M[n] with E[n-1] and Key K2, then encrypt the result with Key K1, yielding E[n].
//
// b) If the blocksize of M[n] is less than 128 bits:
//
// i) Pad M[n] with a single "1" bit, followed by the number of "0" bits (possibly none)
// required to increase M[n]'s blocksize to 128 bits.
//
// ii) XOR M[n] with E[n-1] and Key K3, then encrypt the result with Key K1, yielding E[n].
message = Arrays.copyOfRange(paddedData, paddedData.length - blockSize, paddedData.length);
message = xorByteArrays(message, e);
if (isPaddingNeeded) {
message = xorByteArrays(message, key3);
} else {
message = xorByteArrays(message, key2);
}
byte[] encryptedMessage = encryptAesBlock(key1, message);
// AES-XCBC-MAC-96 algorithm requires the output to be truncated to 96-bits
if (needTruncation) {
encryptedMessage = Arrays.copyOfRange(encryptedMessage, 0, AES_XCBC_96_MAC_LEN);
}
return encryptedMessage;
}
private static byte[] xorByteArrays(byte[] message, byte[] e) {
byte[] output = new byte[message.length];
for (int i = 0; i < output.length; ++i) {
output[i] = (byte) (message[i] ^ e[i]);
}
return output;
}
private static byte[] padData(byte[] dataToSign, int blockSize) {
int dataLen = dataToSign.length;
int padLen = (dataLen + blockSize - 1) / blockSize * blockSize - dataLen;
ByteBuffer paddedData = ByteBuffer.allocate(dataLen + padLen);
byte[] padding = new byte[padLen];
// Obligatory 10* Padding as per RFC 3566#section-4
padding[0] = (byte) 128;
paddedData.put(dataToSign).put(padding);
return paddedData.array();
}
private byte[] encryptAesBlock(byte[] keyBytes, byte[] dataToEncrypt) {
// IV is zero as per the recommendation in NIST Special Publication 800-38B
IvParameterSpec iv = new IvParameterSpec(new byte[AES_CBC_IV_LEN]);
ByteBuffer inputBuffer = ByteBuffer.wrap(dataToEncrypt);
ByteBuffer outputBuffer = ByteBuffer.allocate(dataToEncrypt.length);
try {
mCipher.init(
Cipher.ENCRYPT_MODE, new SecretKeySpec(keyBytes, mCipher.getAlgorithm()), iv);
mCipher.doFinal(inputBuffer, outputBuffer);
return outputBuffer.array();
} catch (InvalidAlgorithmParameterException
| InvalidKeyException
| BadPaddingException
| IllegalBlockSizeException
| ShortBufferException e) {
throw new IllegalArgumentException("Failed to sign data: ", e);
}
}
}