blob: cc7a300dbb3692a29adef4378ab71d91c3848878 [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.android.server.nearby.common.bluetooth.fastpair.AesCtrMultipleBlockEncryption.KEY_LENGTH;
import androidx.annotation.VisibleForTesting;
import java.security.GeneralSecurityException;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
/**
* HMAC-SHA256 utility used to generate key-SHA256 based message authentication code. This is
* specific for Fast Pair GATT connection exchanging data to verify both the data integrity and the
* authentication of a message. It is defined as:
*
* <ol>
* <li>SHA256(concat((key ^ opad),SHA256(concat((key ^ ipad), data)))), where
* <li>key is the given secret extended to 64 bytes by concat(secret, ZEROS).
* <li>opad is 64 bytes outer padding, consisting of repeated bytes valued 0x5c.
* <li>ipad is 64 bytes inner padding, consisting of repeated bytes valued 0x36.
* </ol>
*
*/
final class HmacSha256 {
@VisibleForTesting static final int HMAC_SHA256_BLOCK_SIZE = 64;
private HmacSha256() {}
/**
* Generates the HMAC for given parameters, this is specific for Fast Pair GATT connection
* exchanging data which is encrypted using AES-CTR.
*
* @param secret 16 bytes shared secret.
* @param data the data encrypted using AES-CTR and the given nonce.
* @return HMAC-SHA256 result.
*/
static byte[] build(byte[] secret, byte[] data) throws GeneralSecurityException {
// Currently we only accept AES-128 key here, the second check is to secure we won't
// modify KEY_LENGTH to > HMAC_SHA256_BLOCK_SIZE by mistake.
if (secret.length != KEY_LENGTH) {
throw new GeneralSecurityException("Incorrect key length, should be the AES-128 key.");
}
if (KEY_LENGTH > HMAC_SHA256_BLOCK_SIZE) {
throw new GeneralSecurityException("KEY_LENGTH > HMAC_SHA256_BLOCK_SIZE!");
}
return buildWith64BytesKey(secret, data);
}
/**
* Generates the HMAC for given parameters, this is specific for Fast Pair GATT connection
* exchanging data which is encrypted using AES-CTR.
*
* @param secret 16 bytes shared secret.
* @param data the data encrypted using AES-CTR and the given nonce.
* @return HMAC-SHA256 result.
*/
static byte[] buildWith64BytesKey(byte[] secret, byte[] data) throws GeneralSecurityException {
if (secret.length > HMAC_SHA256_BLOCK_SIZE) {
throw new GeneralSecurityException("KEY_LENGTH > HMAC_SHA256_BLOCK_SIZE!");
}
Mac mac = Mac.getInstance("HmacSHA256");
SecretKeySpec keySpec = new SecretKeySpec(secret, "HmacSHA256");
mac.init(keySpec);
return mac.doFinal(data);
}
/**
* Constant-time HMAC comparison to prevent a possible timing attack, e.g. time the same MAC
* with all different first byte for a given ciphertext, the right one will take longer as it
* will fail on the second byte's verification.
*
* @param hmac1 HMAC want to be compared with.
* @param hmac2 HMAC want to be compared with.
* @return true if and ony if the give 2 HMACs are identical and non-null.
*/
static boolean compareTwoHMACs(byte[] hmac1, byte[] hmac2) {
if (hmac1 == null || hmac2 == null) {
return false;
}
if (hmac1.length != hmac2.length) {
return false;
}
// This is for constant-time comparison, don't optimize it.
int res = 0;
for (int i = 0; i < hmac1.length; i++) {
res |= hmac1[i] ^ hmac2[i];
}
return res == 0;
}
}