content: package com.android.server.accounts;
  
  import android.annotation.NonNull;
  import android.annotation.Nullable;
  import android.os.Bundle;
  import android.os.Parcel;
  import android.util.Log;
  
  import com.android.internal.util.Preconditions;
  
  import java.security.GeneralSecurityException;
  import java.security.NoSuchAlgorithmException;
  
  import javax.crypto.Cipher;
  import javax.crypto.KeyGenerator;
  import javax.crypto.Mac;
  import javax.crypto.SecretKey;
  import javax.crypto.spec.IvParameterSpec;
  
  /**
   * A crypto helper for encrypting and decrypting bundle with in-memory symmetric
   * key for {@link AccountManagerService}.
   */
  /* default */ class CryptoHelper {
      private static final String TAG = "Account";
  
      private static final String KEY_CIPHER = "cipher";
      private static final String KEY_MAC = "mac";
      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 MAC_ALGORITHM = "HMACSHA256";
      private static final int IV_LENGTH = 16;
  
      private static CryptoHelper sInstance;
      // Keys used for encrypting and decrypting data returned in a Bundle.
      private final SecretKey mEncryptionKey;
      private final SecretKey mMacKey;
  
      /* default */ synchronized static CryptoHelper getInstance() throws NoSuchAlgorithmException {
          if (sInstance == null) {
              sInstance = new CryptoHelper();
          }
          return sInstance;
      }
  
      private CryptoHelper() throws NoSuchAlgorithmException {
          KeyGenerator kgen = KeyGenerator.getInstance(KEY_ALGORITHM);
          mEncryptionKey = kgen.generateKey();
          // Use a different key for mac-ing than encryption/decryption.
          kgen = KeyGenerator.getInstance(MAC_ALGORITHM);
          mMacKey = kgen.generateKey();
      }
  
      @NonNull
      /* default */ Bundle encryptBundle(@NonNull Bundle bundle) throws GeneralSecurityException {
          Preconditions.checkNotNull(bundle, "Cannot encrypt null bundle.");
          Parcel parcel = Parcel.obtain();
          bundle.writeToParcel(parcel, 0);
          byte[] clearBytes = parcel.marshall();
          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();
          encryptedBundle.putByteArray(KEY_CIPHER, encryptedBytes);
          encryptedBundle.putByteArray(KEY_MAC, mac);
          encryptedBundle.putByteArray(KEY_IV, iv);
  
          return encryptedBundle;
      }
  
      @Nullable
      /* default */ Bundle decryptBundle(@NonNull Bundle bundle) throws GeneralSecurityException {
          Preconditions.checkNotNull(bundle, "Cannot decrypt null bundle.");
          byte[] iv = bundle.getByteArray(KEY_IV);
          byte[] encryptedBytes = bundle.getByteArray(KEY_CIPHER);
          byte[] mac = bundle.getByteArray(KEY_MAC);
          if (!verifyMac(encryptedBytes, iv, mac)) {
              Log.w(TAG, "Escrow mac mismatched!");
              return null;
          }
  
          IvParameterSpec ivSpec = new IvParameterSpec(iv);
          Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
          cipher.init(Cipher.DECRYPT_MODE, mEncryptionKey, ivSpec);
          byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
  
          Parcel decryptedParcel = Parcel.obtain();
          decryptedParcel.unmarshall(decryptedBytes, 0, decryptedBytes.length);
          decryptedParcel.setDataPosition(0);
          Bundle decryptedBundle = new Bundle();
          decryptedBundle.readFromParcel(decryptedParcel);
          decryptedParcel.recycle();
          return decryptedBundle;
      }
  
      private boolean verifyMac(@Nullable byte[] cipherArray, @Nullable byte[] iv, @Nullable byte[] macArray)
              throws GeneralSecurityException {
          if (cipherArray == null || cipherArray.length == 0 || macArray == null
                  || macArray.length == 0) {
              if (Log.isLoggable(TAG, Log.VERBOSE)) {
                  Log.v(TAG, "Cipher or MAC is empty!");
              }
              return false;
          }
          return constantTimeArrayEquals(macArray, createMac(cipherArray, iv));
      }
  
      @NonNull
      private byte[] createMac(@NonNull byte[] cipher, @NonNull byte[] iv) throws GeneralSecurityException {
          Mac mac = Mac.getInstance(MAC_ALGORITHM);
          mac.init(mMacKey);
          mac.update(cipher);
          mac.update(iv);
          return mac.doFinal();
      }
  
      private static boolean constantTimeArrayEquals(byte[] a, byte[] b) {
          if (a == null || b == null) {
              return a == b;
          }
          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;
      }
  }
  