blob: 7d860375538ebe5f29ec3abcc5926ac2653743d9 [file] [log] [blame]
/*
* Copyright (C) 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.NONCE_SIZE;
import static com.android.server.nearby.common.bluetooth.fastpair.AesEcbSingleBlockEncryption.KEY_LENGTH;
import static com.google.common.io.BaseEncoding.base16;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
import static java.nio.charset.StandardCharsets.UTF_8;
import android.platform.test.annotations.Presubmit;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SdkSuppress;
import androidx.test.filters.SmallTest;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.security.GeneralSecurityException;
import java.security.SecureRandom;
import java.util.Arrays;
/** Unit tests for {@link AesCtrMultpleBlockEncryption}. */
@Presubmit
@SmallTest
@RunWith(AndroidJUnit4.class)
public class AesCtrMultipleBlockEncryptionTest {
@Test
@SdkSuppress(minSdkVersion = 32, codeName = "T")
public void decryptEncryptedData_nonBlockSizeAligned_mustEqualToPlaintext() throws Exception {
byte[] secret = AesCtrMultipleBlockEncryption.generateKey();
byte[] plaintext = "Someone's Google Headphone 2019".getBytes(UTF_8); // The length is 31.
byte[] encrypted = AesCtrMultipleBlockEncryption.encrypt(secret, plaintext);
byte[] decrypted = AesCtrMultipleBlockEncryption.decrypt(secret, encrypted);
assertThat(decrypted).isEqualTo(plaintext);
}
@Test
@SdkSuppress(minSdkVersion = 32, codeName = "T")
public void decryptEncryptedData_blockSizeAligned_mustEqualToPlaintext() throws Exception {
byte[] secret = AesCtrMultipleBlockEncryption.generateKey();
byte[] plaintext =
// The length is 32.
base16().decode("0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF");
byte[] encrypted = AesCtrMultipleBlockEncryption.encrypt(secret, plaintext);
byte[] decrypted = AesCtrMultipleBlockEncryption.decrypt(secret, encrypted);
assertThat(decrypted).isEqualTo(plaintext);
}
@Test
@SdkSuppress(minSdkVersion = 32, codeName = "T")
public void generateNonceTwice_mustBeDifferent() {
byte[] nonce1 = AesCtrMultipleBlockEncryption.generateNonce();
byte[] nonce2 = AesCtrMultipleBlockEncryption.generateNonce();
assertThat(nonce1).isNotEqualTo(nonce2);
}
@Test
@SdkSuppress(minSdkVersion = 32, codeName = "T")
public void encryptedSamePlaintext_mustBeDifferentEncryptedResult() throws Exception {
byte[] secret = AesCtrMultipleBlockEncryption.generateKey();
byte[] plaintext = "Someone's Google Headphone 2019".getBytes(UTF_8);
byte[] encrypted1 = AesCtrMultipleBlockEncryption.encrypt(secret, plaintext);
byte[] encrypted2 = AesCtrMultipleBlockEncryption.encrypt(secret, plaintext);
assertThat(encrypted1).isNotEqualTo(encrypted2);
}
@Test
@SdkSuppress(minSdkVersion = 32, codeName = "T")
public void encryptData_mustBeDifferentToUnencrypted() throws Exception {
byte[] secret = AesCtrMultipleBlockEncryption.generateKey();
byte[] plaintext = "Someone's Google Headphone 2019".getBytes(UTF_8);
byte[] encrypted = AesCtrMultipleBlockEncryption.encrypt(secret, plaintext);
assertThat(encrypted).isNotEqualTo(plaintext);
}
@Test
@SdkSuppress(minSdkVersion = 32, codeName = "T")
public void inputIncorrectKeySizeToEncrypt_mustThrowException() {
byte[] secret = new byte[KEY_LENGTH + 1];
byte[] plaintext = "Someone's Google Headphone 2019".getBytes(UTF_8);
IllegalArgumentException exception =
assertThrows(
IllegalArgumentException.class,
() -> AesCtrMultipleBlockEncryption.encrypt(secret, plaintext));
assertThat(exception)
.hasMessageThat()
.contains("Incorrect key length for encryption, only supports 16-byte AES Key.");
}
@Test
@SdkSuppress(minSdkVersion = 32, codeName = "T")
public void inputIncorrectKeySizeToDecrypt_mustThrowException() {
byte[] secret = new byte[KEY_LENGTH - 1];
byte[] plaintext = "Someone's Google Headphone 2019".getBytes(UTF_8);
IllegalArgumentException exception =
assertThrows(
IllegalArgumentException.class,
() -> AesCtrMultipleBlockEncryption.decrypt(secret, plaintext));
assertThat(exception)
.hasMessageThat()
.contains("Incorrect key length for encryption, only supports 16-byte AES Key.");
}
@Test
@SdkSuppress(minSdkVersion = 32, codeName = "T")
public void inputIncorrectDataSizeToDecrypt_mustThrowException()
throws GeneralSecurityException {
byte[] secret = AesCtrMultipleBlockEncryption.generateKey();
byte[] plaintext = "Someone's Google Headphone 2019".getBytes(UTF_8);
byte[] encryptedData = Arrays.copyOfRange(
AesCtrMultipleBlockEncryption.encrypt(secret, plaintext), /*from=*/ 0, NONCE_SIZE);
GeneralSecurityException exception =
assertThrows(
GeneralSecurityException.class,
() -> AesCtrMultipleBlockEncryption.decrypt(secret, encryptedData));
assertThat(exception).hasMessageThat().contains("Incorrect data length");
}
// Add some random tests that for a certain amount of random plaintext of random length to prove
// our encryption/decryption is correct. This is suggested by security team.
@Test
@SdkSuppress(minSdkVersion = 32, codeName = "T")
public void decryptEncryptedRandomDataForCertainAmount_mustEqualToOriginalData()
throws Exception {
SecureRandom random = new SecureRandom();
for (int i = 0; i < 1000; i++) {
byte[] secret = AesCtrMultipleBlockEncryption.generateKey();
int dataLength = random.nextInt(64) + 1;
byte[] data = new byte[dataLength];
random.nextBytes(data);
byte[] encrypted = AesCtrMultipleBlockEncryption.encrypt(secret, data);
byte[] decrypted = AesCtrMultipleBlockEncryption.decrypt(secret, encrypted);
assertThat(decrypted).isEqualTo(data);
}
}
// Add some random tests that for a certain amount of random plaintext of random length to prove
// our encryption is correct. This is suggested by security team.
@Test
@SdkSuppress(minSdkVersion = 32, codeName = "T")
public void twoDistinctEncryptionOnSameRandomData_mustBeDifferentResult() throws Exception {
SecureRandom random = new SecureRandom();
for (int i = 0; i < 1000; i++) {
byte[] secret = AesCtrMultipleBlockEncryption.generateKey();
int dataLength = random.nextInt(64) + 1;
byte[] data = new byte[dataLength];
random.nextBytes(data);
byte[] encrypted1 = AesCtrMultipleBlockEncryption.encrypt(secret, data);
byte[] encrypted2 = AesCtrMultipleBlockEncryption.encrypt(secret, data);
assertThat(encrypted1).isNotEqualTo(encrypted2);
}
}
// Adds this test example on spec. Also we can easily change the parameters(e.g. secret, data,
// nonce) to clarify test results with partners.
@Test
@SdkSuppress(minSdkVersion = 32, codeName = "T")
public void inputTestExampleToEncrypt_getCorrectResult() throws GeneralSecurityException {
byte[] secret = base16().decode("0123456789ABCDEF0123456789ABCDEF");
byte[] nonce = base16().decode("0001020304050607");
// "Someone's Google Headphone".getBytes(UTF_8) is
// base16().decode("536F6D656F6E65277320476F6F676C65204865616470686F6E65");
byte[] encryptedData =
AesCtrMultipleBlockEncryption.doAesCtr(
secret,
"Someone's Google Headphone".getBytes(UTF_8),
nonce);
assertThat(encryptedData)
.isEqualTo(base16().decode("EE4A2483738052E44E9B2A145E5DDFAA44B9E5536AF438E1E5C6"));
}
}