1 /* 2 * Copyright (C) 2017 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.security; 18 19 import android.annotation.Nullable; 20 21 import com.android.internal.annotations.VisibleForTesting; 22 import com.android.internal.util.ArrayUtils; 23 24 import java.math.BigInteger; 25 import java.nio.BufferUnderflowException; 26 import java.nio.ByteBuffer; 27 import java.nio.charset.StandardCharsets; 28 import java.security.InvalidAlgorithmParameterException; 29 import java.security.InvalidKeyException; 30 import java.security.KeyFactory; 31 import java.security.KeyPair; 32 import java.security.KeyPairGenerator; 33 import java.security.NoSuchAlgorithmException; 34 import java.security.PrivateKey; 35 import java.security.PublicKey; 36 import java.security.SecureRandom; 37 import java.security.interfaces.ECPublicKey; 38 import java.security.spec.ECFieldFp; 39 import java.security.spec.ECGenParameterSpec; 40 import java.security.spec.ECParameterSpec; 41 import java.security.spec.ECPoint; 42 import java.security.spec.ECPublicKeySpec; 43 import java.security.spec.EllipticCurve; 44 import java.security.spec.InvalidKeySpecException; 45 import java.util.Arrays; 46 47 import javax.crypto.AEADBadTagException; 48 import javax.crypto.BadPaddingException; 49 import javax.crypto.Cipher; 50 import javax.crypto.IllegalBlockSizeException; 51 import javax.crypto.KeyAgreement; 52 import javax.crypto.Mac; 53 import javax.crypto.NoSuchPaddingException; 54 import javax.crypto.SecretKey; 55 import javax.crypto.spec.GCMParameterSpec; 56 import javax.crypto.spec.SecretKeySpec; 57 58 /** 59 * Implementation of the SecureBox v2 crypto functions. 60 * 61 * <p>Securebox v2 provides a simple interface to perform encryptions by using any of the following 62 * credential types: 63 * 64 * <ul> 65 * <li>A public key owned by the recipient, 66 * <li>A secret shared between the sender and the recipient, or 67 * <li>Both a recipient's public key and a shared secret. 68 * </ul> 69 * 70 * @hide 71 */ 72 public class SecureBox { 73 74 private static final byte[] VERSION = new byte[] {(byte) 0x02, 0}; // LITTLE_ENDIAN_TWO_BYTES(2) 75 private static final byte[] HKDF_SALT = 76 ArrayUtils.concat("SECUREBOX".getBytes(StandardCharsets.UTF_8), VERSION); 77 private static final byte[] HKDF_INFO_WITH_PUBLIC_KEY = 78 "P256 HKDF-SHA-256 AES-128-GCM".getBytes(StandardCharsets.UTF_8); 79 private static final byte[] HKDF_INFO_WITHOUT_PUBLIC_KEY = 80 "SHARED HKDF-SHA-256 AES-128-GCM".getBytes(StandardCharsets.UTF_8); 81 private static final byte[] CONSTANT_01 = {(byte) 0x01}; 82 private static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; 83 private static final byte EC_PUBLIC_KEY_PREFIX = (byte) 0x04; 84 85 private static final String CIPHER_ALG = "AES"; 86 private static final String EC_ALG = "EC"; 87 private static final String EC_P256_COMMON_NAME = "secp256r1"; 88 private static final String EC_P256_OPENSSL_NAME = "prime256v1"; 89 private static final String ENC_ALG = "AES/GCM/NoPadding"; 90 private static final String KA_ALG = "ECDH"; 91 private static final String MAC_ALG = "HmacSHA256"; 92 93 private static final int EC_COORDINATE_LEN_BYTES = 32; 94 private static final int EC_PUBLIC_KEY_LEN_BYTES = 2 * EC_COORDINATE_LEN_BYTES + 1; 95 private static final int GCM_NONCE_LEN_BYTES = 12; 96 private static final int GCM_KEY_LEN_BYTES = 16; 97 private static final int GCM_TAG_LEN_BYTES = 16; 98 99 private static final BigInteger BIG_INT_02 = BigInteger.valueOf(2); 100 101 private enum AesGcmOperation { 102 ENCRYPT, 103 DECRYPT 104 } 105 106 // Parameters for the NIST P-256 curve y^2 = x^3 + ax + b (mod p) 107 private static final BigInteger EC_PARAM_P = 108 new BigInteger("ffffffff00000001000000000000000000000000ffffffffffffffffffffffff", 16); 109 private static final BigInteger EC_PARAM_A = EC_PARAM_P.subtract(new BigInteger("3")); 110 private static final BigInteger EC_PARAM_B = 111 new BigInteger("5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b", 16); 112 113 @VisibleForTesting static final ECParameterSpec EC_PARAM_SPEC; 114 115 static { 116 EllipticCurve curveSpec = 117 new EllipticCurve(new ECFieldFp(EC_PARAM_P), EC_PARAM_A, EC_PARAM_B); 118 ECPoint generator = 119 new ECPoint( 120 new BigInteger( 121 "6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296", 122 16), 123 new BigInteger( 124 "4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5", 125 16)); 126 BigInteger generatorOrder = 127 new BigInteger( 128 "ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551", 16); 129 EC_PARAM_SPEC = new ECParameterSpec(curveSpec, generator, generatorOrder, /* cofactor */ 1); 130 } 131 SecureBox()132 private SecureBox() {} 133 134 /** 135 * Randomly generates a public-key pair that can be used for the functions {@link #encrypt} and 136 * {@link #decrypt}. 137 * 138 * @return the randomly generated public-key pair 139 * @throws NoSuchAlgorithmException if the underlying crypto algorithm is not supported 140 * @hide 141 */ genKeyPair()142 public static KeyPair genKeyPair() throws NoSuchAlgorithmException { 143 KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(EC_ALG); 144 try { 145 // Try using the OpenSSL provider first 146 keyPairGenerator.initialize(new ECGenParameterSpec(EC_P256_OPENSSL_NAME)); 147 return keyPairGenerator.generateKeyPair(); 148 } catch (InvalidAlgorithmParameterException ex) { 149 // Try another name for NIST P-256 150 } 151 try { 152 keyPairGenerator.initialize(new ECGenParameterSpec(EC_P256_COMMON_NAME)); 153 return keyPairGenerator.generateKeyPair(); 154 } catch (InvalidAlgorithmParameterException ex) { 155 throw new NoSuchAlgorithmException("Unable to find the NIST P-256 curve", ex); 156 } 157 } 158 159 /** 160 * Encrypts {@code payload} by using {@code theirPublicKey} and/or {@code sharedSecret}. At 161 * least one of {@code theirPublicKey} and {@code sharedSecret} must be non-null, and an empty 162 * {@code sharedSecret} is equivalent to null. 163 * 164 * <p>Note that {@code header} will be authenticated (but not encrypted) together with {@code 165 * payload}, and the same {@code header} has to be provided for {@link #decrypt}. 166 * 167 * @param theirPublicKey the recipient's public key, or null if the payload is to be encrypted 168 * only with the shared secret 169 * @param sharedSecret the secret shared between the sender and the recipient, or null if the 170 * payload is to be encrypted only with the recipient's public key 171 * @param header the data that will be authenticated with {@code payload} but not encrypted, or 172 * null if the data is empty 173 * @param payload the data to be encrypted, or null if the data is empty 174 * @return the encrypted payload 175 * @throws NoSuchAlgorithmException if any underlying crypto algorithm is not supported 176 * @throws InvalidKeyException if the provided key is invalid for underlying crypto algorithms 177 * @hide 178 */ encrypt( @ullable PublicKey theirPublicKey, @Nullable byte[] sharedSecret, @Nullable byte[] header, @Nullable byte[] payload)179 public static byte[] encrypt( 180 @Nullable PublicKey theirPublicKey, 181 @Nullable byte[] sharedSecret, 182 @Nullable byte[] header, 183 @Nullable byte[] payload) 184 throws NoSuchAlgorithmException, InvalidKeyException { 185 sharedSecret = emptyByteArrayIfNull(sharedSecret); 186 if (theirPublicKey == null && sharedSecret.length == 0) { 187 throw new IllegalArgumentException("Both the public key and shared secret are empty"); 188 } 189 header = emptyByteArrayIfNull(header); 190 payload = emptyByteArrayIfNull(payload); 191 192 KeyPair senderKeyPair; 193 byte[] dhSecret; 194 byte[] hkdfInfo; 195 if (theirPublicKey == null) { 196 senderKeyPair = null; 197 dhSecret = EMPTY_BYTE_ARRAY; 198 hkdfInfo = HKDF_INFO_WITHOUT_PUBLIC_KEY; 199 } else { 200 senderKeyPair = genKeyPair(); 201 dhSecret = dhComputeSecret(senderKeyPair.getPrivate(), theirPublicKey); 202 hkdfInfo = HKDF_INFO_WITH_PUBLIC_KEY; 203 } 204 205 byte[] randNonce = genRandomNonce(); 206 byte[] keyingMaterial = ArrayUtils.concat(dhSecret, sharedSecret); 207 SecretKey encryptionKey = hkdfDeriveKey(keyingMaterial, HKDF_SALT, hkdfInfo); 208 byte[] ciphertext = aesGcmEncrypt(encryptionKey, randNonce, payload, header); 209 if (senderKeyPair == null) { 210 return ArrayUtils.concat(VERSION, randNonce, ciphertext); 211 } else { 212 return ArrayUtils.concat( 213 VERSION, encodePublicKey(senderKeyPair.getPublic()), randNonce, ciphertext); 214 } 215 } 216 217 /** 218 * Decrypts {@code encryptedPayload} by using {@code ourPrivateKey} and/or {@code sharedSecret}. 219 * At least one of {@code ourPrivateKey} and {@code sharedSecret} must be non-null, and an empty 220 * {@code sharedSecret} is equivalent to null. 221 * 222 * <p>Note that {@code header} should be the same data used for {@link #encrypt}, which is 223 * authenticated (but not encrypted) together with {@code payload}; otherwise, an {@code 224 * AEADBadTagException} will be thrown. 225 * 226 * @param ourPrivateKey the recipient's private key, or null if the payload was encrypted only 227 * with the shared secret 228 * @param sharedSecret the secret shared between the sender and the recipient, or null if the 229 * payload was encrypted only with the recipient's public key 230 * @param header the data that was authenticated with the original payload but not encrypted, or 231 * null if the data is empty 232 * @param encryptedPayload the data to be decrypted 233 * @return the original payload that was encrypted 234 * @throws NoSuchAlgorithmException if any underlying crypto algorithm is not supported 235 * @throws InvalidKeyException if the provided key is invalid for underlying crypto algorithms 236 * @throws AEADBadTagException if the authentication tag contained in {@code encryptedPayload} 237 * cannot be validated, or if the payload is not a valid SecureBox V2 payload. 238 * @hide 239 */ decrypt( @ullable PrivateKey ourPrivateKey, @Nullable byte[] sharedSecret, @Nullable byte[] header, byte[] encryptedPayload)240 public static byte[] decrypt( 241 @Nullable PrivateKey ourPrivateKey, 242 @Nullable byte[] sharedSecret, 243 @Nullable byte[] header, 244 byte[] encryptedPayload) 245 throws NoSuchAlgorithmException, InvalidKeyException, AEADBadTagException { 246 sharedSecret = emptyByteArrayIfNull(sharedSecret); 247 if (ourPrivateKey == null && sharedSecret.length == 0) { 248 throw new IllegalArgumentException("Both the private key and shared secret are empty"); 249 } 250 header = emptyByteArrayIfNull(header); 251 if (encryptedPayload == null) { 252 throw new NullPointerException("Encrypted payload must not be null."); 253 } 254 255 ByteBuffer ciphertextBuffer = ByteBuffer.wrap(encryptedPayload); 256 byte[] version = readEncryptedPayload(ciphertextBuffer, VERSION.length); 257 if (!Arrays.equals(version, VERSION)) { 258 throw new AEADBadTagException("The payload was not encrypted by SecureBox v2"); 259 } 260 261 byte[] senderPublicKeyBytes; 262 byte[] dhSecret; 263 byte[] hkdfInfo; 264 if (ourPrivateKey == null) { 265 dhSecret = EMPTY_BYTE_ARRAY; 266 hkdfInfo = HKDF_INFO_WITHOUT_PUBLIC_KEY; 267 } else { 268 senderPublicKeyBytes = readEncryptedPayload(ciphertextBuffer, EC_PUBLIC_KEY_LEN_BYTES); 269 dhSecret = dhComputeSecret(ourPrivateKey, decodePublicKey(senderPublicKeyBytes)); 270 hkdfInfo = HKDF_INFO_WITH_PUBLIC_KEY; 271 } 272 273 byte[] randNonce = readEncryptedPayload(ciphertextBuffer, GCM_NONCE_LEN_BYTES); 274 byte[] ciphertext = readEncryptedPayload(ciphertextBuffer, ciphertextBuffer.remaining()); 275 byte[] keyingMaterial = ArrayUtils.concat(dhSecret, sharedSecret); 276 SecretKey decryptionKey = hkdfDeriveKey(keyingMaterial, HKDF_SALT, hkdfInfo); 277 return aesGcmDecrypt(decryptionKey, randNonce, ciphertext, header); 278 } 279 readEncryptedPayload(ByteBuffer buffer, int length)280 private static byte[] readEncryptedPayload(ByteBuffer buffer, int length) 281 throws AEADBadTagException { 282 byte[] output = new byte[length]; 283 try { 284 buffer.get(output); 285 } catch (BufferUnderflowException ex) { 286 throw new AEADBadTagException("The encrypted payload is too short"); 287 } 288 return output; 289 } 290 dhComputeSecret(PrivateKey ourPrivateKey, PublicKey theirPublicKey)291 private static byte[] dhComputeSecret(PrivateKey ourPrivateKey, PublicKey theirPublicKey) 292 throws NoSuchAlgorithmException, InvalidKeyException { 293 KeyAgreement agreement = KeyAgreement.getInstance(KA_ALG); 294 try { 295 agreement.init(ourPrivateKey); 296 } catch (RuntimeException ex) { 297 // Rethrow the RuntimeException as InvalidKeyException 298 throw new InvalidKeyException(ex); 299 } 300 agreement.doPhase(theirPublicKey, /*lastPhase=*/ true); 301 return agreement.generateSecret(); 302 } 303 304 /** Derives a 128-bit AES key. */ hkdfDeriveKey(byte[] secret, byte[] salt, byte[] info)305 private static SecretKey hkdfDeriveKey(byte[] secret, byte[] salt, byte[] info) 306 throws NoSuchAlgorithmException { 307 Mac mac = Mac.getInstance(MAC_ALG); 308 try { 309 mac.init(new SecretKeySpec(salt, MAC_ALG)); 310 } catch (InvalidKeyException ex) { 311 // This should never happen 312 throw new RuntimeException(ex); 313 } 314 byte[] pseudorandomKey = mac.doFinal(secret); 315 316 try { 317 mac.init(new SecretKeySpec(pseudorandomKey, MAC_ALG)); 318 } catch (InvalidKeyException ex) { 319 // This should never happen 320 throw new RuntimeException(ex); 321 } 322 mac.update(info); 323 // Hashing just one block will yield 256 bits, which is enough to construct the AES key 324 byte[] hkdfOutput = mac.doFinal(CONSTANT_01); 325 326 return new SecretKeySpec(Arrays.copyOf(hkdfOutput, GCM_KEY_LEN_BYTES), CIPHER_ALG); 327 } 328 aesGcmEncrypt(SecretKey key, byte[] nonce, byte[] plaintext, byte[] aad)329 private static byte[] aesGcmEncrypt(SecretKey key, byte[] nonce, byte[] plaintext, byte[] aad) 330 throws NoSuchAlgorithmException, InvalidKeyException { 331 try { 332 return aesGcmInternal(AesGcmOperation.ENCRYPT, key, nonce, plaintext, aad); 333 } catch (AEADBadTagException ex) { 334 // This should never happen 335 throw new RuntimeException(ex); 336 } 337 } 338 aesGcmDecrypt(SecretKey key, byte[] nonce, byte[] ciphertext, byte[] aad)339 private static byte[] aesGcmDecrypt(SecretKey key, byte[] nonce, byte[] ciphertext, byte[] aad) 340 throws NoSuchAlgorithmException, InvalidKeyException, AEADBadTagException { 341 return aesGcmInternal(AesGcmOperation.DECRYPT, key, nonce, ciphertext, aad); 342 } 343 aesGcmInternal( AesGcmOperation operation, SecretKey key, byte[] nonce, byte[] text, byte[] aad)344 private static byte[] aesGcmInternal( 345 AesGcmOperation operation, SecretKey key, byte[] nonce, byte[] text, byte[] aad) 346 throws NoSuchAlgorithmException, InvalidKeyException, AEADBadTagException { 347 Cipher cipher; 348 try { 349 cipher = Cipher.getInstance(ENC_ALG); 350 } catch (NoSuchPaddingException ex) { 351 // This should never happen because AES-GCM doesn't use padding 352 throw new RuntimeException(ex); 353 } 354 GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_LEN_BYTES * 8, nonce); 355 try { 356 if (operation == AesGcmOperation.DECRYPT) { 357 cipher.init(Cipher.DECRYPT_MODE, key, spec); 358 } else { 359 cipher.init(Cipher.ENCRYPT_MODE, key, spec); 360 } 361 } catch (InvalidAlgorithmParameterException ex) { 362 // This should never happen 363 throw new RuntimeException(ex); 364 } 365 try { 366 cipher.updateAAD(aad); 367 return cipher.doFinal(text); 368 } catch (AEADBadTagException ex) { 369 // Catch and rethrow AEADBadTagException first because it's a subclass of 370 // BadPaddingException 371 throw ex; 372 } catch (IllegalBlockSizeException | BadPaddingException ex) { 373 // This should never happen because AES-GCM can handle inputs of any length without 374 // padding 375 throw new RuntimeException(ex); 376 } 377 } 378 379 /** 380 * Encodes public key in format expected by the secure hardware module. This is used as part 381 * of the vault params. 382 * 383 * @param publicKey The public key. 384 * @return The key packed into a 65-byte array. 385 */ encodePublicKey(PublicKey publicKey)386 public static byte[] encodePublicKey(PublicKey publicKey) { 387 ECPoint point = ((ECPublicKey) publicKey).getW(); 388 byte[] x = point.getAffineX().toByteArray(); 389 byte[] y = point.getAffineY().toByteArray(); 390 391 byte[] output = new byte[EC_PUBLIC_KEY_LEN_BYTES]; 392 // The order of arraycopy() is important, because the coordinates may have a one-byte 393 // leading 0 for the sign bit of two's complement form 394 System.arraycopy(y, 0, output, EC_PUBLIC_KEY_LEN_BYTES - y.length, y.length); 395 System.arraycopy(x, 0, output, 1 + EC_COORDINATE_LEN_BYTES - x.length, x.length); 396 output[0] = EC_PUBLIC_KEY_PREFIX; 397 return output; 398 } 399 400 /** 401 * Decodes byte[] encoded public key. 402 * 403 * @param keyBytes encoded public key 404 * @return the public key 405 */ decodePublicKey(byte[] keyBytes)406 public static PublicKey decodePublicKey(byte[] keyBytes) 407 throws NoSuchAlgorithmException, InvalidKeyException { 408 BigInteger x = 409 new BigInteger( 410 /*signum=*/ 1, 411 Arrays.copyOfRange(keyBytes, 1, 1 + EC_COORDINATE_LEN_BYTES)); 412 BigInteger y = 413 new BigInteger( 414 /*signum=*/ 1, 415 Arrays.copyOfRange( 416 keyBytes, 1 + EC_COORDINATE_LEN_BYTES, EC_PUBLIC_KEY_LEN_BYTES)); 417 418 // Checks if the point is indeed on the P-256 curve for security considerations 419 validateEcPoint(x, y); 420 421 KeyFactory keyFactory = KeyFactory.getInstance(EC_ALG); 422 try { 423 return keyFactory.generatePublic(new ECPublicKeySpec(new ECPoint(x, y), EC_PARAM_SPEC)); 424 } catch (InvalidKeySpecException ex) { 425 // This should never happen 426 throw new RuntimeException(ex); 427 } 428 } 429 validateEcPoint(BigInteger x, BigInteger y)430 private static void validateEcPoint(BigInteger x, BigInteger y) throws InvalidKeyException { 431 if (x.compareTo(EC_PARAM_P) >= 0 432 || y.compareTo(EC_PARAM_P) >= 0 433 || x.signum() == -1 434 || y.signum() == -1) { 435 throw new InvalidKeyException("Point lies outside of the expected curve"); 436 } 437 438 // Points on the curve satisfy y^2 = x^3 + ax + b (mod p) 439 BigInteger lhs = y.modPow(BIG_INT_02, EC_PARAM_P); 440 BigInteger rhs = 441 x.modPow(BIG_INT_02, EC_PARAM_P) // x^2 442 .add(EC_PARAM_A) // x^2 + a 443 .mod(EC_PARAM_P) // This will speed up the next multiplication 444 .multiply(x) // (x^2 + a) * x = x^3 + ax 445 .add(EC_PARAM_B) // x^3 + ax + b 446 .mod(EC_PARAM_P); 447 if (!lhs.equals(rhs)) { 448 throw new InvalidKeyException("Point lies outside of the expected curve"); 449 } 450 } 451 genRandomNonce()452 private static byte[] genRandomNonce() throws NoSuchAlgorithmException { 453 byte[] nonce = new byte[GCM_NONCE_LEN_BYTES]; 454 new SecureRandom().nextBytes(nonce); 455 return nonce; 456 } 457 emptyByteArrayIfNull(@ullable byte[] input)458 private static byte[] emptyByteArrayIfNull(@Nullable byte[] input) { 459 return input == null ? EMPTY_BYTE_ARRAY : input; 460 } 461 } 462