Add encryption and decryption methods

Fix: 232847868
Test: -m
Ignore-AOSP-First: nearby_not_in_aosp_yet
Change-Id: I53a33fd0fc58cb64b2b46817b657f1e8a76dbcf5
diff --git a/nearby/service/java/com/android/server/nearby/presence/ExtendedAdvertisement.java b/nearby/service/java/com/android/server/nearby/presence/ExtendedAdvertisement.java
index c0074fe..ab58918 100644
--- a/nearby/service/java/com/android/server/nearby/presence/ExtendedAdvertisement.java
+++ b/nearby/service/java/com/android/server/nearby/presence/ExtendedAdvertisement.java
@@ -47,9 +47,9 @@
  */
 public class ExtendedAdvertisement extends Advertisement{
 
-    static final int HEADER_LENGTH = 1;
+    public static final int SALT_DATA_LENGTH = 2;
 
-    static final int SALT_DATA_LENGTH = 2;
+    static final int HEADER_LENGTH = 1;
 
     static final int IDENTITY_DATA_LENGTH = 16;
 
diff --git a/nearby/service/java/com/android/server/nearby/util/encryption/CryptorImpV1.java b/nearby/service/java/com/android/server/nearby/util/encryption/CryptorImpV1.java
new file mode 100644
index 0000000..b5a13a2
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/util/encryption/CryptorImpV1.java
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) 2022 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.util.encryption;
+
+import static com.android.server.nearby.NearbyService.TAG;
+
+import android.security.keystore.KeyProperties;
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.Mac;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+/**
+ * {@link android.nearby.BroadcastRequest#PRESENCE_VERSION_V1} for encryption and decryption.
+ */
+public class CryptorImpV1 implements Cryptor{
+
+    /**
+     * In the form of "algorithm/mode/padding". Must be the same across broadcast and scan devices.
+     */
+    private static final String CIPHER_ALGORITHM = "AES/CTR/NoPadding";
+
+    private static final String HMAC_SHA256_ALGORITHM = "HmacSHA256";
+
+    @VisibleForTesting
+    static final String ENCRYPT_ALGORITHM = KeyProperties.KEY_ALGORITHM_AES;
+
+    /** AES only supports key sizes of 16, 24 or 32 bytes. */
+    private static final int AUTHENTICITY_KEY_BYTE_SIZE = 16;
+
+    /** Length of encryption key required by AES/GCM encryption. */
+    private static final int ENCRYPTION_KEY_SIZE = 32;
+
+    /** Length of salt required by AES/GCM encryption. */
+    private static final int AES_CTR_IV_SIZE = 16;
+
+    // 4 16 byte arrays known by both the encryptor and decryptor.
+    private static final byte[] AK_IV =
+            new byte[] {12, -59, 19, 23, 96, 57, -59, 19, 117, -31, -116, -61, 86, -25, -33, -78};
+    private static final byte[] ASALT_IV =
+            new byte[] {111, 48, -83, -79, -10, -102, -16, 73, 43, 55, 102, -127, 58, -19, -113, 4};
+
+    // Lazily instantiated when {@link #getInstance()} is called.
+    @Nullable private static CryptorImpV1 sCryptor;
+
+    /** Returns an instance of CryptorImpV1. */
+    public static CryptorImpV1 getInstance() {
+        if (sCryptor == null) {
+            sCryptor = new CryptorImpV1();
+        }
+        return sCryptor;
+    }
+
+    private CryptorImpV1() {
+    }
+
+    @Nullable
+    @Override
+    public byte[] encrypt(byte[] data, byte[] salt, byte[] authenticityKey) {
+        if (authenticityKey.length != AUTHENTICITY_KEY_BYTE_SIZE) {
+            Log.w(TAG, "Illegal authenticity key size");
+            return null;
+        }
+
+        // Generates a 32 bytes encryption key from authenticity_key
+        byte[] encryptionKey = computeHkdf(authenticityKey, AK_IV, ENCRYPTION_KEY_SIZE);
+        if (encryptionKey == null) {
+            Log.e(TAG, "Failed to generate encryption key.");
+            return null;
+        }
+
+        // Encrypts the data using the encryption key
+        SecretKey secretKey = new SecretKeySpec(encryptionKey, ENCRYPT_ALGORITHM);
+        Cipher cipher;
+        try {
+            cipher = Cipher.getInstance(CIPHER_ALGORITHM);
+        } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
+            Log.e(TAG, "Failed to encrypt with secret key.", e);
+            return null;
+        }
+        byte[] asalt = computeHkdf(salt, ASALT_IV, AES_CTR_IV_SIZE);
+        if (asalt == null) {
+            Log.e(TAG, "Failed to generate salt.");
+            return null;
+        }
+        try {
+            cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(asalt));
+        } catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
+            Log.e(TAG, "Failed to initialize cipher.", e);
+            return null;
+        }
+        try {
+            return cipher.doFinal(data);
+        } catch (IllegalBlockSizeException | BadPaddingException e) {
+            Log.e(TAG, "Failed to encrypt with secret key.", e);
+            return null;
+        }
+    }
+
+    @Nullable
+    @Override
+    public byte[] decrypt(byte[] encryptedData, byte[] salt, byte[] authenticityKey) {
+        if (authenticityKey.length != AUTHENTICITY_KEY_BYTE_SIZE) {
+            Log.w(TAG, "Illegal authenticity key size");
+            return null;
+        }
+
+        // Generates a 32 bytes encryption key from authenticity_key
+        byte[] encryptionKey = computeHkdf(authenticityKey, AK_IV, ENCRYPTION_KEY_SIZE);
+        if (encryptionKey == null) {
+            Log.e(TAG, "Failed to generate encryption key.");
+            return null;
+        }
+
+        // Decrypts the data using the encryption key
+        SecretKey secretKey = new SecretKeySpec(encryptionKey, ENCRYPT_ALGORITHM);
+        Cipher cipher;
+        try {
+            cipher = Cipher.getInstance(CIPHER_ALGORITHM);
+        } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
+            Log.e(TAG, "Failed to get cipher instance.", e);
+            return null;
+        }
+        byte[] asalt = computeHkdf(salt, ASALT_IV, AES_CTR_IV_SIZE);
+        if (asalt == null) {
+            return null;
+        }
+        try {
+            cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(asalt));
+        } catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
+            Log.e(TAG, "Failed to initialize cipher.", e);
+            return null;
+        }
+
+        try {
+            return cipher.doFinal(encryptedData);
+        } catch (IllegalBlockSizeException | BadPaddingException e) {
+            Log.e(TAG, "Failed to decrypt bytes with secret key.", e);
+            return null;
+        }
+    }
+
+    /**
+     * A HAMC sha256 based HKDF algorithm to pseudo randomly hash data and salt into a byte array of
+     * given size.
+     */
+    // Based on google3/third_party/tink/java/src/main/java/com/google/crypto/tink/subtle/Hkdf.java
+    @Nullable
+    private static byte[] computeHkdf(byte[] ikm, byte[] salt, int size) {
+        Mac mac;
+        try {
+            mac = Mac.getInstance(HMAC_SHA256_ALGORITHM);
+        } catch (NoSuchAlgorithmException e) {
+            Log.w(TAG, "HMAC_SHA256_ALGORITHM is not supported.", e);
+            return null;
+        }
+
+        if (size > 255 * mac.getMacLength()) {
+            Log.w(TAG, "Size too large.");
+            return null;
+        }
+
+        if (salt.length == 0) {
+            Log.w(TAG, "Salt cannot be empty.");
+            return null;
+        }
+
+        try {
+            mac.init(new SecretKeySpec(salt, HMAC_SHA256_ALGORITHM));
+        } catch (InvalidKeyException e) {
+            Log.w(TAG, "Invalid key.", e);
+            return null;
+        }
+
+        byte[] prk = mac.doFinal(ikm);
+        byte[] result = new byte[size];
+        try {
+            mac.init(new SecretKeySpec(prk, HMAC_SHA256_ALGORITHM));
+        } catch (InvalidKeyException e) {
+            Log.w(TAG, "Invalid key.", e);
+            return null;
+        }
+
+        byte[] digest = new byte[0];
+        int ctr = 1;
+        int pos = 0;
+        while (true) {
+            mac.update(digest);
+            mac.update((byte) ctr);
+            digest = mac.doFinal();
+            if (pos + digest.length < size) {
+                System.arraycopy(digest, 0, result, pos, digest.length);
+                pos += digest.length;
+                ctr++;
+            } else {
+                System.arraycopy(digest, 0, result, pos, size - pos);
+                break;
+            }
+        }
+
+        return result;
+    }
+}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/util/encryption/CryptorImpV1Test.java b/nearby/tests/unit/src/com/android/server/nearby/util/encryption/CryptorImpV1Test.java
new file mode 100644
index 0000000..37b5353
--- /dev/null
+++ b/nearby/tests/unit/src/com/android/server/nearby/util/encryption/CryptorImpV1Test.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2022 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.util.encryption;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Test;
+
+/**
+ * Unit test for {@link CryptorImpV1}
+ */
+public final class CryptorImpV1Test {
+    private static final byte[] SALT = new byte[] {102, 22};
+    private static final byte[] DATA =
+            new byte[] {107, -102, 101, 107, 20, 62, 2, 73, 113, 59, 8, -14, -58, 122};
+    private static final byte[] AUTHENTICITY_KEY =
+            new byte[] {-89, 88, -50, -42, -99, 57, 84, -24, 121, 1, -104, -8, -26, -73, -36, 100};
+
+    @Test
+    public void decryptionEncryptionTest() {
+        Cryptor v1Cryptor = CryptorImpV1.getInstance();
+        byte[] encryptedData = v1Cryptor.encrypt(DATA, SALT, AUTHENTICITY_KEY);
+        byte[] decryptedData =
+                v1Cryptor.decrypt(encryptedData, SALT, AUTHENTICITY_KEY);
+        assertThat(decryptedData).isEqualTo(DATA);
+    }
+}