• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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