blob: 50a818b722f07e08634f98db301d2ad099827846 [file] [log] [blame]
/*
* Copyright 2021 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.server.nearby.common.bluetooth.fastpair;
import static com.google.common.primitives.Bytes.concat;
import androidx.annotation.VisibleForTesting;
import java.security.GeneralSecurityException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Arrays;
/**
* AES-CTR utilities used for encrypting and decrypting Fast Pair packets that contain multiple
* blocks. Encrypts input data by:
*
* <ol>
* <li>encryptedBlock[i] = clearBlock[i] ^ AES(counter), and
* <li>concat(encryptedBlock[0], encryptedBlock[1],...) to create the encrypted result, where
* <li>counter: the 16-byte input of AES. counter = iv + block_index.
* <li>iv: extend 8-byte nonce to 16 bytes with zero padding. i.e. concat(0x0000000000000000,
* nonce).
* <li>nonce: the cryptographically random 8 bytes, must never be reused with the same key.
* </ol>
*/
final class AesCtrMultipleBlockEncryption {
/** Length for AES-128 key. */
static final int KEY_LENGTH = AesEcbSingleBlockEncryption.KEY_LENGTH;
@VisibleForTesting
static final int AES_BLOCK_LENGTH = AesEcbSingleBlockEncryption.AES_BLOCK_LENGTH;
/** Length of the nonce, a byte array of cryptographically random bytes. */
static final int NONCE_SIZE = 8;
private static final int IV_SIZE = AES_BLOCK_LENGTH;
private static final int MAX_NUMBER_OF_BLOCKS = 4;
private AesCtrMultipleBlockEncryption() {}
/** Generates a 16-byte AES key. */
static byte[] generateKey() throws NoSuchAlgorithmException {
return AesEcbSingleBlockEncryption.generateKey();
}
/**
* Encrypts data using AES-CTR by the given secret.
*
* @param secret AES-128 key.
* @param data the plaintext to be encrypted.
* @return the encrypted data with the 8-byte nonce appended to the front.
*/
static byte[] encrypt(byte[] secret, byte[] data) throws GeneralSecurityException {
byte[] nonce = generateNonce();
return concat(nonce, doAesCtr(secret, data, nonce));
}
/**
* Decrypts data using AES-CTR by the given secret and nonce.
*
* @param secret AES-128 key.
* @param data the first 8 bytes is the nonce, and the remaining is the encrypted data to be
* decrypted.
* @return the decrypted data.
*/
static byte[] decrypt(byte[] secret, byte[] data) throws GeneralSecurityException {
if (data == null || data.length <= NONCE_SIZE) {
throw new GeneralSecurityException(
"Incorrect data length "
+ (data == null ? "NULL" : data.length)
+ " to decrypt, the data should contain nonce.");
}
byte[] nonce = Arrays.copyOf(data, NONCE_SIZE);
byte[] encryptedData = Arrays.copyOfRange(data, NONCE_SIZE, data.length);
return doAesCtr(secret, encryptedData, nonce);
}
/**
* Generates cryptographically random NONCE_SIZE bytes nonce. This nonce can be used only once.
* Always call this function to generate a new nonce before a new encryption.
*/
// Suppression for a warning for potentially insecure random numbers on Android 4.3 and older.
// Fast Pair service is only for Android 6.0+ devices.
static byte[] generateNonce() {
SecureRandom random = new SecureRandom();
byte[] nonce = new byte[NONCE_SIZE];
random.nextBytes(nonce);
return nonce;
}
// AES-CTR implementation.
@VisibleForTesting
static byte[] doAesCtr(byte[] secret, byte[] data, byte[] nonce)
throws GeneralSecurityException {
if (secret.length != KEY_LENGTH) {
throw new IllegalArgumentException(
"Incorrect key length for encryption, only supports 16-byte AES Key.");
}
if (nonce.length != NONCE_SIZE) {
throw new IllegalArgumentException(
"Incorrect nonce length for encryption, "
+ "Fast Pair naming scheme only supports 8-byte nonce.");
}
// Keeps the following operations on this byte[], returns it as the final AES-CTR result.
byte[] aesCtrResult = new byte[data.length];
System.arraycopy(data, /*srcPos=*/ 0, aesCtrResult, /*destPos=*/ 0, data.length);
// Initializes counter as IV.
byte[] counter = createIv(nonce);
// The length of the given data is permitted to non-align block size.
int numberOfBlocks =
(data.length / AES_BLOCK_LENGTH) + ((data.length % AES_BLOCK_LENGTH == 0) ? 0 : 1);
if (numberOfBlocks > MAX_NUMBER_OF_BLOCKS) {
throw new IllegalArgumentException(
"Incorrect data size, Fast Pair naming scheme only supports 4 blocks.");
}
for (int i = 0; i < numberOfBlocks; i++) {
// Performs the operation: encryptedBlock[i] = clearBlock[i] ^ AES(counter).
counter[0] = (byte) (i & 0xFF);
byte[] aesOfCounter = doAesSingleBlock(secret, counter);
int start = i * AES_BLOCK_LENGTH;
// The size of the last block of data may not be 16 bytes. If not, still do xor to the
// last byte of data.
int end = Math.min(start + AES_BLOCK_LENGTH, data.length);
for (int j = 0; start < end; j++, start++) {
aesCtrResult[start] ^= aesOfCounter[j];
}
}
return aesCtrResult;
}
private static byte[] doAesSingleBlock(byte[] secret, byte[] counter)
throws GeneralSecurityException {
return AesEcbSingleBlockEncryption.encrypt(secret, counter);
}
/** Extends 8-byte nonce to 16 bytes with zero padding to create IV. */
private static byte[] createIv(byte[] nonce) {
return concat(new byte[IV_SIZE - NONCE_SIZE], nonce);
}
}