1 /* 2 * Copyright 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.nearby.common.bluetooth.fastpair; 18 19 import static com.android.server.nearby.common.bluetooth.fastpair.AesCtrMultipleBlockEncryption.KEY_LENGTH; 20 21 import androidx.annotation.VisibleForTesting; 22 23 import java.security.GeneralSecurityException; 24 25 import javax.crypto.Mac; 26 import javax.crypto.spec.SecretKeySpec; 27 28 /** 29 * HMAC-SHA256 utility used to generate key-SHA256 based message authentication code. This is 30 * specific for Fast Pair GATT connection exchanging data to verify both the data integrity and the 31 * authentication of a message. It is defined as: 32 * 33 * <ol> 34 * <li>SHA256(concat((key ^ opad),SHA256(concat((key ^ ipad), data)))), where 35 * <li>key is the given secret extended to 64 bytes by concat(secret, ZEROS). 36 * <li>opad is 64 bytes outer padding, consisting of repeated bytes valued 0x5c. 37 * <li>ipad is 64 bytes inner padding, consisting of repeated bytes valued 0x36. 38 * </ol> 39 * 40 */ 41 final class HmacSha256 { 42 @VisibleForTesting static final int HMAC_SHA256_BLOCK_SIZE = 64; 43 HmacSha256()44 private HmacSha256() {} 45 46 /** 47 * Generates the HMAC for given parameters, this is specific for Fast Pair GATT connection 48 * exchanging data which is encrypted using AES-CTR. 49 * 50 * @param secret 16 bytes shared secret. 51 * @param data the data encrypted using AES-CTR and the given nonce. 52 * @return HMAC-SHA256 result. 53 */ build(byte[] secret, byte[] data)54 static byte[] build(byte[] secret, byte[] data) throws GeneralSecurityException { 55 // Currently we only accept AES-128 key here, the second check is to secure we won't 56 // modify KEY_LENGTH to > HMAC_SHA256_BLOCK_SIZE by mistake. 57 if (secret.length != KEY_LENGTH) { 58 throw new GeneralSecurityException("Incorrect key length, should be the AES-128 key."); 59 } 60 if (KEY_LENGTH > HMAC_SHA256_BLOCK_SIZE) { 61 throw new GeneralSecurityException("KEY_LENGTH > HMAC_SHA256_BLOCK_SIZE!"); 62 } 63 64 return buildWith64BytesKey(secret, data); 65 } 66 67 /** 68 * Generates the HMAC for given parameters, this is specific for Fast Pair GATT connection 69 * exchanging data which is encrypted using AES-CTR. 70 * 71 * @param secret 16 bytes shared secret. 72 * @param data the data encrypted using AES-CTR and the given nonce. 73 * @return HMAC-SHA256 result. 74 */ buildWith64BytesKey(byte[] secret, byte[] data)75 static byte[] buildWith64BytesKey(byte[] secret, byte[] data) throws GeneralSecurityException { 76 if (secret.length > HMAC_SHA256_BLOCK_SIZE) { 77 throw new GeneralSecurityException("KEY_LENGTH > HMAC_SHA256_BLOCK_SIZE!"); 78 } 79 80 Mac mac = Mac.getInstance("HmacSHA256"); 81 SecretKeySpec keySpec = new SecretKeySpec(secret, "HmacSHA256"); 82 mac.init(keySpec); 83 84 return mac.doFinal(data); 85 } 86 87 /** 88 * Constant-time HMAC comparison to prevent a possible timing attack, e.g. time the same MAC 89 * with all different first byte for a given ciphertext, the right one will take longer as it 90 * will fail on the second byte's verification. 91 * 92 * @param hmac1 HMAC want to be compared with. 93 * @param hmac2 HMAC want to be compared with. 94 * @return true if and ony if the give 2 HMACs are identical and non-null. 95 */ compareTwoHMACs(byte[] hmac1, byte[] hmac2)96 static boolean compareTwoHMACs(byte[] hmac1, byte[] hmac2) { 97 if (hmac1 == null || hmac2 == null) { 98 return false; 99 } 100 101 if (hmac1.length != hmac2.length) { 102 return false; 103 } 104 // This is for constant-time comparison, don't optimize it. 105 int res = 0; 106 for (int i = 0; i < hmac1.length; i++) { 107 res |= hmac1[i] ^ hmac2[i]; 108 } 109 return res == 0; 110 } 111 } 112