Merge "Improve CryptoHelper" into nyc-dev

This commit is contained in:
Carlos Valdivia
2016-04-14 01:17:07 +00:00
committed by Android (Google) Code Review

View File

@@ -10,15 +10,12 @@ import com.android.internal.util.Preconditions;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Arrays;
import javax.crypto.Cipher; import javax.crypto.Cipher;
import javax.crypto.KeyGenerator; import javax.crypto.KeyGenerator;
import javax.crypto.Mac; import javax.crypto.Mac;
import javax.crypto.SecretKey; import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
/** /**
* A crypto helper for encrypting and decrypting bundle with in-memory symmetric * A crypto helper for encrypting and decrypting bundle with in-memory symmetric
@@ -30,15 +27,15 @@ import javax.crypto.spec.SecretKeySpec;
private static final String KEY_CIPHER = "cipher"; private static final String KEY_CIPHER = "cipher";
private static final String KEY_MAC = "mac"; private static final String KEY_MAC = "mac";
private static final String KEY_ALGORITHM = "AES"; private static final String KEY_ALGORITHM = "AES";
private static final String KEY_IV = "iv";
private static final String CIPHER_ALGORITHM = "AES/CBC/PKCS5Padding"; private static final String CIPHER_ALGORITHM = "AES/CBC/PKCS5Padding";
private static final String MAC_ALGORITHM = "HMACSHA256"; private static final String MAC_ALGORITHM = "HMACSHA256";
private static final int IV_LENGTH = 16; private static final int IV_LENGTH = 16;
private static CryptoHelper sInstance; private static CryptoHelper sInstance;
// Keys used for encrypting and decrypting data returned in a Bundle. // Keys used for encrypting and decrypting data returned in a Bundle.
private final SecretKeySpec mCipherKeySpec; private final SecretKey mEncryptionKey;
private final SecretKeySpec mMacKeySpec; private final SecretKey mMacKey;
private final IvParameterSpec mIv;
/* default */ synchronized static CryptoHelper getInstance() throws NoSuchAlgorithmException { /* default */ synchronized static CryptoHelper getInstance() throws NoSuchAlgorithmException {
if (sInstance == null) { if (sInstance == null) {
@@ -49,18 +46,10 @@ import javax.crypto.spec.SecretKeySpec;
private CryptoHelper() throws NoSuchAlgorithmException { private CryptoHelper() throws NoSuchAlgorithmException {
KeyGenerator kgen = KeyGenerator.getInstance(KEY_ALGORITHM); KeyGenerator kgen = KeyGenerator.getInstance(KEY_ALGORITHM);
SecretKey skey = kgen.generateKey(); mEncryptionKey = kgen.generateKey();
mCipherKeySpec = new SecretKeySpec(skey.getEncoded(), KEY_ALGORITHM); // Use a different key for mac-ing than encryption/decryption.
kgen = KeyGenerator.getInstance(MAC_ALGORITHM); kgen = KeyGenerator.getInstance(MAC_ALGORITHM);
skey = kgen.generateKey(); mMacKey = kgen.generateKey();
mMacKeySpec = new SecretKeySpec(skey.getEncoded(), MAC_ALGORITHM);
// Create random iv
byte[] iv = new byte[IV_LENGTH];
SecureRandom secureRandom = new SecureRandom();
secureRandom.nextBytes(iv);
mIv = new IvParameterSpec(iv);
} }
@NonNull @NonNull
@@ -68,16 +57,19 @@ import javax.crypto.spec.SecretKeySpec;
Preconditions.checkNotNull(bundle, "Cannot encrypt null bundle."); Preconditions.checkNotNull(bundle, "Cannot encrypt null bundle.");
Parcel parcel = Parcel.obtain(); Parcel parcel = Parcel.obtain();
bundle.writeToParcel(parcel, 0); bundle.writeToParcel(parcel, 0);
byte[] bytes = parcel.marshall(); byte[] clearBytes = parcel.marshall();
parcel.recycle(); parcel.recycle();
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, mEncryptionKey);
byte[] encryptedBytes = cipher.doFinal(clearBytes);
byte[] iv = cipher.getIV();
byte[] mac = createMac(encryptedBytes, iv);
Bundle encryptedBundle = new Bundle(); Bundle encryptedBundle = new Bundle();
encryptedBundle.putByteArray(KEY_CIPHER, encryptedBytes);
byte[] cipher = encrypt(bytes);
byte[] mac = createMac(cipher);
encryptedBundle.putByteArray(KEY_CIPHER, cipher);
encryptedBundle.putByteArray(KEY_MAC, mac); encryptedBundle.putByteArray(KEY_MAC, mac);
encryptedBundle.putByteArray(KEY_IV, iv);
return encryptedBundle; return encryptedBundle;
} }
@@ -85,19 +77,18 @@ import javax.crypto.spec.SecretKeySpec;
@Nullable @Nullable
/* default */ Bundle decryptBundle(@NonNull Bundle bundle) throws GeneralSecurityException { /* default */ Bundle decryptBundle(@NonNull Bundle bundle) throws GeneralSecurityException {
Preconditions.checkNotNull(bundle, "Cannot decrypt null bundle."); Preconditions.checkNotNull(bundle, "Cannot decrypt null bundle.");
byte[] cipherArray = bundle.getByteArray(KEY_CIPHER); byte[] iv = bundle.getByteArray(KEY_IV);
byte[] macArray = bundle.getByteArray(KEY_MAC); byte[] encryptedBytes = bundle.getByteArray(KEY_CIPHER);
byte[] mac = bundle.getByteArray(KEY_MAC);
if (!verifyMac(cipherArray, macArray)) { if (!verifyMac(encryptedBytes, iv, mac)) {
if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.w(TAG, "Escrow mac mismatched!");
Log.v(TAG, "Escrow mac mismatched!");
}
return null; return null;
} }
IvParameterSpec ivSpec = new IvParameterSpec(iv);
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, mCipherKeySpec, mIv); cipher.init(Cipher.DECRYPT_MODE, mEncryptionKey, ivSpec);
byte[] decryptedBytes = cipher.doFinal(cipherArray); byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
Parcel decryptedParcel = Parcel.obtain(); Parcel decryptedParcel = Parcel.obtain();
decryptedParcel.unmarshall(decryptedBytes, 0, decryptedBytes.length); decryptedParcel.unmarshall(decryptedBytes, 0, decryptedBytes.length);
@@ -108,9 +99,8 @@ import javax.crypto.spec.SecretKeySpec;
return decryptedBundle; return decryptedBundle;
} }
private boolean verifyMac(@Nullable byte[] cipherArray, @Nullable byte[] macArray) private boolean verifyMac(@Nullable byte[] cipherArray, @Nullable byte[] iv, @Nullable byte[] macArray)
throws GeneralSecurityException { throws GeneralSecurityException {
if (cipherArray == null || cipherArray.length == 0 || macArray == null if (cipherArray == null || cipherArray.length == 0 || macArray == null
|| macArray.length == 0) { || macArray.length == 0) {
if (Log.isLoggable(TAG, Log.VERBOSE)) { if (Log.isLoggable(TAG, Log.VERBOSE)) {
@@ -118,23 +108,29 @@ import javax.crypto.spec.SecretKeySpec;
} }
return false; return false;
} }
Mac mac = Mac.getInstance(MAC_ALGORITHM); return constantTimeArrayEquals(macArray, createMac(cipherArray, iv));
mac.init(mMacKeySpec);
mac.update(cipherArray);
return Arrays.equals(macArray, mac.doFinal());
} }
@NonNull @NonNull
private byte[] encrypt(@NonNull byte[] data) throws GeneralSecurityException { private byte[] createMac(@NonNull byte[] cipher, @NonNull byte[] iv) throws GeneralSecurityException {
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); Mac mac = Mac.getInstance(MAC_ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, mCipherKeySpec, mIv); mac.init(mMacKey);
return cipher.doFinal(data); mac.update(cipher);
mac.update(iv);
return mac.doFinal();
} }
@NonNull private static boolean constantTimeArrayEquals(byte[] a, byte[] b) {
private byte[] createMac(@NonNull byte[] cipher) throws GeneralSecurityException { if (a == null || b == null) {
Mac mac = Mac.getInstance(MAC_ALGORITHM); return a == b;
mac.init(mMacKeySpec); }
return mac.doFinal(cipher); if (a.length != b.length) {
return false;
}
boolean isEqual = true;
for (int i = 0; i < b.length; i++) {
isEqual &= (a[i] == b[i]);
}
return isEqual;
} }
} }