1 /* 2 * Copyright 2019 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 androidx.security.crypto; 18 19 import static java.nio.charset.StandardCharsets.UTF_8; 20 21 import android.content.Context; 22 import android.content.SharedPreferences; 23 import android.util.Pair; 24 25 import androidx.collection.ArraySet; 26 27 import com.google.crypto.tink.Aead; 28 import com.google.crypto.tink.DeterministicAead; 29 import com.google.crypto.tink.KeyTemplate; 30 import com.google.crypto.tink.KeyTemplates; 31 import com.google.crypto.tink.KeysetHandle; 32 import com.google.crypto.tink.aead.AeadConfig; 33 import com.google.crypto.tink.daead.DeterministicAeadConfig; 34 import com.google.crypto.tink.integration.android.AndroidKeysetManager; 35 import com.google.crypto.tink.subtle.Base64; 36 37 import org.jspecify.annotations.NonNull; 38 import org.jspecify.annotations.Nullable; 39 40 import java.io.IOException; 41 import java.nio.ByteBuffer; 42 import java.security.GeneralSecurityException; 43 import java.util.ArrayList; 44 import java.util.HashMap; 45 import java.util.List; 46 import java.util.Map; 47 import java.util.Set; 48 import java.util.concurrent.CopyOnWriteArrayList; 49 import java.util.concurrent.atomic.AtomicBoolean; 50 51 /** 52 * An implementation of {@link SharedPreferences} that encrypts keys and values. 53 * <br /> 54 * <br /> 55 * <b>WARNING</b>: The preference file should not be backed up with Auto Backup. When restoring the 56 * file it is likely the key used to encrypt it will no longer be present. You should exclude all 57 * <code>EncryptedSharedPreference</code>s from backup using 58 * <a href="https://developer.android.com/guide/topics/data/autobackup#IncludingFiles">backup rules</a>. 59 * <br /> 60 * <br /> 61 * Basic use of the class: 62 * 63 * <pre> 64 * MasterKey masterKey = new MasterKey.Builder(context) 65 * .setKeyScheme(MasterKey.KeyScheme.AES256_GCM) 66 * .build(); 67 * 68 * SharedPreferences sharedPreferences = EncryptedSharedPreferences.create( 69 * context, 70 * "secret_shared_prefs", 71 * masterKey, 72 * EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, 73 * EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM 74 * ); 75 * 76 * // use the shared preferences and editor as you normally would 77 * SharedPreferences.Editor editor = sharedPreferences.edit(); 78 * </pre> 79 * @deprecated Use {@link android.content.SharedPreferences} instead. 80 */ 81 @Deprecated 82 @SuppressWarnings("deprecation") 83 public final class EncryptedSharedPreferences implements SharedPreferences { 84 85 private static final String KEY_KEYSET_ALIAS = 86 "__androidx_security_crypto_encrypted_prefs_key_keyset__"; 87 private static final String VALUE_KEYSET_ALIAS = 88 "__androidx_security_crypto_encrypted_prefs_value_keyset__"; 89 90 private static final String NULL_VALUE = "__NULL__"; 91 92 final SharedPreferences mSharedPreferences; 93 final CopyOnWriteArrayList<OnSharedPreferenceChangeListener> mListeners; 94 final String mFileName; 95 final String mMasterKeyAlias; 96 97 final Aead mValueAead; 98 final DeterministicAead mKeyDeterministicAead; 99 EncryptedSharedPreferences(@onNull String name, @NonNull String masterKeyAlias, @NonNull SharedPreferences sharedPreferences, @NonNull Aead aead, @NonNull DeterministicAead deterministicAead)100 EncryptedSharedPreferences(@NonNull String name, 101 @NonNull String masterKeyAlias, 102 @NonNull SharedPreferences sharedPreferences, 103 @NonNull Aead aead, 104 @NonNull DeterministicAead deterministicAead) { 105 mFileName = name; 106 mSharedPreferences = sharedPreferences; 107 mMasterKeyAlias = masterKeyAlias; 108 mValueAead = aead; 109 mKeyDeterministicAead = deterministicAead; 110 mListeners = new CopyOnWriteArrayList<>(); 111 } 112 113 /** 114 * Opens an instance of encrypted SharedPreferences 115 * 116 * @param fileName The name of the file to open; can not contain path 117 * separators. 118 * @param masterKey The master key to use. 119 * @param prefKeyEncryptionScheme The scheme to use for encrypting keys. 120 * @param prefValueEncryptionScheme The scheme to use for encrypting values. 121 * @return The SharedPreferences instance that encrypts all data. 122 * @throws GeneralSecurityException when a bad master key or keyset has been attempted 123 * @throws IOException when fileName can not be used 124 */ create(@onNull Context context, @NonNull String fileName, @NonNull MasterKey masterKey, @NonNull PrefKeyEncryptionScheme prefKeyEncryptionScheme, @NonNull PrefValueEncryptionScheme prefValueEncryptionScheme)125 public static @NonNull SharedPreferences create(@NonNull Context context, 126 @NonNull String fileName, 127 @NonNull MasterKey masterKey, 128 @NonNull PrefKeyEncryptionScheme prefKeyEncryptionScheme, 129 @NonNull PrefValueEncryptionScheme prefValueEncryptionScheme) 130 throws GeneralSecurityException, IOException { 131 return create(fileName, masterKey.getKeyAlias(), context, 132 prefKeyEncryptionScheme, prefValueEncryptionScheme); 133 } 134 135 /** 136 * Opens an instance of encrypted SharedPreferences 137 * 138 * <p>If the <code>masterKeyAlias</code> used here is for a key that is not yet created, this 139 * method will not be thread safe. Use the alternate signature that is not deprecated for 140 * multi-threaded contexts. 141 * 142 * @deprecated Use {@link #create(Context, String, MasterKey, 143 * PrefKeyEncryptionScheme, PrefValueEncryptionScheme)} instead. 144 * @param fileName The name of the file to open; can not contain path 145 * separators. 146 * @param masterKeyAlias The alias of the master key to use. 147 * @param context The context to use to open the preferences file. 148 * @param prefKeyEncryptionScheme The scheme to use for encrypting keys. 149 * @param prefValueEncryptionScheme The scheme to use for encrypting values. 150 * @return The SharedPreferences instance that encrypts all data. 151 * @throws GeneralSecurityException when a bad master key or keyset has been attempted 152 * @throws IOException when fileName can not be used 153 */ 154 @Deprecated create(@onNull String fileName, @NonNull String masterKeyAlias, @NonNull Context context, @NonNull PrefKeyEncryptionScheme prefKeyEncryptionScheme, @NonNull PrefValueEncryptionScheme prefValueEncryptionScheme)155 public static @NonNull SharedPreferences create(@NonNull String fileName, 156 @NonNull String masterKeyAlias, 157 @NonNull Context context, 158 @NonNull PrefKeyEncryptionScheme prefKeyEncryptionScheme, 159 @NonNull PrefValueEncryptionScheme prefValueEncryptionScheme) 160 throws GeneralSecurityException, IOException { 161 DeterministicAeadConfig.register(); 162 AeadConfig.register(); 163 164 final Context applicationContext = context.getApplicationContext(); 165 KeysetHandle daeadKeysetHandle = new AndroidKeysetManager.Builder() 166 .withKeyTemplate(prefKeyEncryptionScheme.getKeyTemplate()) 167 .withSharedPref(applicationContext, KEY_KEYSET_ALIAS, fileName) 168 .withMasterKeyUri(MasterKey.KEYSTORE_PATH_URI + masterKeyAlias) 169 .build().getKeysetHandle(); 170 KeysetHandle aeadKeysetHandle = new AndroidKeysetManager.Builder() 171 .withKeyTemplate(prefValueEncryptionScheme.getKeyTemplate()) 172 .withSharedPref(applicationContext, VALUE_KEYSET_ALIAS, fileName) 173 .withMasterKeyUri(MasterKey.KEYSTORE_PATH_URI + masterKeyAlias) 174 .build().getKeysetHandle(); 175 176 DeterministicAead daead = daeadKeysetHandle.getPrimitive(DeterministicAead.class); 177 Aead aead = aeadKeysetHandle.getPrimitive(Aead.class); 178 179 return new EncryptedSharedPreferences(fileName, masterKeyAlias, 180 applicationContext.getSharedPreferences(fileName, Context.MODE_PRIVATE), aead, 181 daead); 182 } 183 184 /** 185 * The encryption scheme to encrypt keys. 186 * @deprecated Use {@link android.content.SharedPreferences} instead. 187 */ 188 @Deprecated 189 public enum PrefKeyEncryptionScheme { 190 /** 191 * Pref keys are encrypted deterministically with AES256-SIV-CMAC (RFC 5297). 192 * 193 * <p>For more information please see the Tink documentation: 194 * 195 * <p><a href="https://google.github.io/tink/javadoc/tink/1.7.0/com/google/crypto/tink/daead/AesSivKeyManager.html">AesSivKeyManager</a>.aes256SivTemplate() 196 */ 197 AES256_SIV("AES256_SIV"); 198 199 private final String mDeterministicAeadKeyTemplateName; 200 PrefKeyEncryptionScheme(String deterministicAeadKeyTemplateName)201 PrefKeyEncryptionScheme(String deterministicAeadKeyTemplateName) { 202 mDeterministicAeadKeyTemplateName = deterministicAeadKeyTemplateName; 203 } 204 getKeyTemplate()205 KeyTemplate getKeyTemplate() throws GeneralSecurityException { 206 return KeyTemplates.get(mDeterministicAeadKeyTemplateName); 207 } 208 } 209 210 /** 211 * The encryption scheme to encrypt values. 212 * @deprecated Use {@link android.content.SharedPreferences} instead. 213 */ 214 @Deprecated 215 public enum PrefValueEncryptionScheme { 216 /** 217 * Pref values are encrypted with AES256-GCM. The associated data is the encrypted pref key. 218 * 219 * <p>For more information please see the Tink documentation: 220 * 221 * <p><a href="https://google.github.io/tink/javadoc/tink/1.7.0/com/google/crypto/tink/aead/AesGcmKeyManager.html">AesGcmKeyManager</a>.aes256GcmTemplate() 222 */ 223 AES256_GCM("AES256_GCM"); 224 225 private final String mAeadKeyTemplateName; 226 PrefValueEncryptionScheme(String aeadKeyTemplateName)227 PrefValueEncryptionScheme(String aeadKeyTemplateName) { 228 mAeadKeyTemplateName = aeadKeyTemplateName; 229 } 230 getKeyTemplate()231 KeyTemplate getKeyTemplate() throws GeneralSecurityException { 232 return KeyTemplates.get(mAeadKeyTemplateName); 233 } 234 } 235 236 private static final class Editor implements SharedPreferences.Editor { 237 private final EncryptedSharedPreferences mEncryptedSharedPreferences; 238 private final SharedPreferences.Editor mEditor; 239 private final List<String> mKeysChanged; 240 private final AtomicBoolean mClearRequested = new AtomicBoolean(false); 241 Editor(EncryptedSharedPreferences encryptedSharedPreferences, SharedPreferences.Editor editor)242 Editor(EncryptedSharedPreferences encryptedSharedPreferences, 243 SharedPreferences.Editor editor) { 244 mEncryptedSharedPreferences = encryptedSharedPreferences; 245 mEditor = editor; 246 mKeysChanged = new CopyOnWriteArrayList<>(); 247 } 248 249 @Override putString(@ullable String key, @Nullable String value)250 public SharedPreferences.@NonNull Editor putString(@Nullable String key, 251 @Nullable String value) { 252 if (value == null) { 253 value = NULL_VALUE; 254 } 255 byte[] stringBytes = value.getBytes(UTF_8); 256 int stringByteLength = stringBytes.length; 257 ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES + Integer.BYTES 258 + stringByteLength); 259 buffer.putInt(EncryptedType.STRING.getId()); 260 buffer.putInt(stringByteLength); 261 buffer.put(stringBytes); 262 putEncryptedObject(key, buffer.array()); 263 return this; 264 } 265 266 @Override putStringSet(@ullable String key, @Nullable Set<String> values)267 public SharedPreferences.@NonNull Editor putStringSet(@Nullable String key, 268 @Nullable Set<String> values) { 269 if (values == null) { 270 values = new ArraySet<>(); 271 values.add(NULL_VALUE); 272 } 273 List<byte[]> byteValues = new ArrayList<>(values.size()); 274 int totalBytes = values.size() * Integer.BYTES; 275 for (String strValue : values) { 276 byte[] byteValue = strValue.getBytes(UTF_8); 277 byteValues.add(byteValue); 278 totalBytes += byteValue.length; 279 } 280 totalBytes += Integer.BYTES; 281 ByteBuffer buffer = ByteBuffer.allocate(totalBytes); 282 buffer.putInt(EncryptedType.STRING_SET.getId()); 283 for (byte[] bytes : byteValues) { 284 buffer.putInt(bytes.length); 285 buffer.put(bytes); 286 } 287 putEncryptedObject(key, buffer.array()); 288 return this; 289 } 290 291 @Override putInt(@ullable String key, int value)292 public SharedPreferences.@NonNull Editor putInt(@Nullable String key, int value) { 293 ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES + Integer.BYTES); 294 buffer.putInt(EncryptedType.INT.getId()); 295 buffer.putInt(value); 296 putEncryptedObject(key, buffer.array()); 297 return this; 298 } 299 300 @Override putLong(@ullable String key, long value)301 public SharedPreferences.@NonNull Editor putLong(@Nullable String key, long value) { 302 ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES + Long.BYTES); 303 buffer.putInt(EncryptedType.LONG.getId()); 304 buffer.putLong(value); 305 putEncryptedObject(key, buffer.array()); 306 return this; 307 } 308 309 @Override putFloat(@ullable String key, float value)310 public SharedPreferences.@NonNull Editor putFloat(@Nullable String key, float value) { 311 ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES + Float.BYTES); 312 buffer.putInt(EncryptedType.FLOAT.getId()); 313 buffer.putFloat(value); 314 putEncryptedObject(key, buffer.array()); 315 return this; 316 } 317 318 @Override putBoolean(@ullable String key, boolean value)319 public SharedPreferences.@NonNull Editor putBoolean(@Nullable String key, boolean value) { 320 ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES + Byte.BYTES); 321 buffer.putInt(EncryptedType.BOOLEAN.getId()); 322 buffer.put(value ? (byte) 1 : (byte) 0); 323 putEncryptedObject(key, buffer.array()); 324 return this; 325 } 326 327 @Override remove(@ullable String key)328 public SharedPreferences.@NonNull Editor remove(@Nullable String key) { 329 if (mEncryptedSharedPreferences.isReservedKey(key)) { 330 throw new SecurityException(key + " is a reserved key for the encryption keyset."); 331 } 332 mEditor.remove(mEncryptedSharedPreferences.encryptKey(key)); 333 mKeysChanged.add(key); 334 return this; 335 } 336 337 @Override clear()338 public SharedPreferences.@NonNull Editor clear() { 339 // Set the flag to clear on commit, this operation happens first on commit. 340 // Cannot use underlying clear operation, it will remove the keysets and 341 // break the editor. 342 mClearRequested.set(true); 343 return this; 344 } 345 346 @Override commit()347 public boolean commit() { 348 clearKeysIfNeeded(); 349 try { 350 return mEditor.commit(); 351 } finally { 352 notifyListeners(); 353 mKeysChanged.clear(); 354 } 355 } 356 357 @Override apply()358 public void apply() { 359 clearKeysIfNeeded(); 360 mEditor.apply(); 361 notifyListeners(); 362 mKeysChanged.clear(); 363 } 364 clearKeysIfNeeded()365 private void clearKeysIfNeeded() { 366 // Call "clear" first as per the documentation, remove all keys that haven't 367 // been modified in this editor. 368 if (mClearRequested.getAndSet(false)) { 369 for (String key : mEncryptedSharedPreferences.getAll().keySet()) { 370 if (!mKeysChanged.contains(key) 371 && !mEncryptedSharedPreferences.isReservedKey(key)) { 372 mEditor.remove(mEncryptedSharedPreferences.encryptKey(key)); 373 } 374 } 375 } 376 } 377 putEncryptedObject(String key, byte[] value)378 private void putEncryptedObject(String key, byte[] value) { 379 if (mEncryptedSharedPreferences.isReservedKey(key)) { 380 throw new SecurityException(key + " is a reserved key for the encryption keyset."); 381 } 382 mKeysChanged.add(key); 383 if (key == null) { 384 key = NULL_VALUE; 385 } 386 try { 387 Pair<String, String> encryptedPair = mEncryptedSharedPreferences 388 .encryptKeyValuePair(key, value); 389 mEditor.putString(encryptedPair.first, encryptedPair.second); 390 } catch (GeneralSecurityException ex) { 391 throw new SecurityException("Could not encrypt data: " + ex.getMessage(), ex); 392 } 393 } 394 notifyListeners()395 private void notifyListeners() { 396 for (OnSharedPreferenceChangeListener listener : 397 mEncryptedSharedPreferences.mListeners) { 398 for (String key : mKeysChanged) { 399 listener.onSharedPreferenceChanged(mEncryptedSharedPreferences, key); 400 } 401 } 402 } 403 } 404 405 // SharedPreferences methods 406 407 @Override getAll()408 public @NonNull Map<String, ?> getAll() { 409 Map<String, ? super Object> allEntries = new HashMap<>(); 410 for (Map.Entry<String, ?> entry : mSharedPreferences.getAll().entrySet()) { 411 if (!isReservedKey(entry.getKey())) { 412 String decryptedKey = decryptKey(entry.getKey()); 413 allEntries.put(decryptedKey, 414 getDecryptedObject(decryptedKey)); 415 } 416 } 417 return allEntries; 418 } 419 420 @Override getString(@ullable String key, @Nullable String defValue)421 public @Nullable String getString(@Nullable String key, @Nullable String defValue) { 422 Object value = getDecryptedObject(key); 423 return (value instanceof String ? (String) value : defValue); 424 } 425 426 @SuppressWarnings("unchecked") 427 @Override getStringSet(@ullable String key, @Nullable Set<String> defValues)428 public @Nullable Set<String> getStringSet(@Nullable String key, 429 @Nullable Set<String> defValues) { 430 Set<String> returnValues; 431 Object value = getDecryptedObject(key); 432 if (value instanceof Set) { 433 returnValues = (Set<String>) value; 434 } else { 435 returnValues = new ArraySet<>(); 436 } 437 return returnValues.size() > 0 ? returnValues : defValues; 438 } 439 440 @Override getInt(@ullable String key, int defValue)441 public int getInt(@Nullable String key, int defValue) { 442 Object value = getDecryptedObject(key); 443 return (value instanceof Integer ? (Integer) value : defValue); 444 } 445 446 @Override getLong(@ullable String key, long defValue)447 public long getLong(@Nullable String key, long defValue) { 448 Object value = getDecryptedObject(key); 449 return (value instanceof Long ? (Long) value : defValue); 450 } 451 452 @Override getFloat(@ullable String key, float defValue)453 public float getFloat(@Nullable String key, float defValue) { 454 Object value = getDecryptedObject(key); 455 return (value instanceof Float ? (Float) value : defValue); 456 } 457 458 @Override getBoolean(@ullable String key, boolean defValue)459 public boolean getBoolean(@Nullable String key, boolean defValue) { 460 Object value = getDecryptedObject(key); 461 return (value instanceof Boolean ? (Boolean) value : defValue); 462 } 463 464 @Override contains(@ullable String key)465 public boolean contains(@Nullable String key) { 466 if (isReservedKey(key)) { 467 throw new SecurityException(key + " is a reserved key for the encryption keyset."); 468 } 469 String encryptedKey = encryptKey(key); 470 return mSharedPreferences.contains(encryptedKey); 471 } 472 473 @Override edit()474 public SharedPreferences.@NonNull Editor edit() { 475 return new Editor(this, mSharedPreferences.edit()); 476 } 477 478 @Override registerOnSharedPreferenceChangeListener( @onNull OnSharedPreferenceChangeListener listener)479 public void registerOnSharedPreferenceChangeListener( 480 @NonNull OnSharedPreferenceChangeListener listener) { 481 mListeners.add(listener); 482 } 483 484 @Override unregisterOnSharedPreferenceChangeListener( @onNull OnSharedPreferenceChangeListener listener)485 public void unregisterOnSharedPreferenceChangeListener( 486 @NonNull OnSharedPreferenceChangeListener listener) { 487 mListeners.remove(listener); 488 } 489 490 /** 491 * Internal enum to set the type of encrypted data. 492 */ 493 private enum EncryptedType { 494 STRING(0), 495 STRING_SET(1), 496 INT(2), 497 LONG(3), 498 FLOAT(4), 499 BOOLEAN(5); 500 501 private final int mId; 502 EncryptedType(int id)503 EncryptedType(int id) { 504 mId = id; 505 } 506 getId()507 public int getId() { 508 return mId; 509 } 510 fromId(int id)511 public static @Nullable EncryptedType fromId(int id) { 512 switch (id) { 513 case 0: 514 return STRING; 515 case 1: 516 return STRING_SET; 517 case 2: 518 return INT; 519 case 3: 520 return LONG; 521 case 4: 522 return FLOAT; 523 case 5: 524 return BOOLEAN; 525 } 526 return null; 527 } 528 } 529 getDecryptedObject(String key)530 private Object getDecryptedObject(String key) throws SecurityException { 531 if (isReservedKey(key)) { 532 throw new SecurityException(key + " is a reserved key for the encryption keyset."); 533 } 534 if (key == null) { 535 key = NULL_VALUE; 536 } 537 538 try { 539 String encryptedKey = encryptKey(key); 540 String encryptedValue = mSharedPreferences.getString(encryptedKey, null); 541 if (encryptedValue == null) { 542 return null; 543 } 544 545 byte[] cipherText = Base64.decode(encryptedValue, Base64.DEFAULT); 546 byte[] value = mValueAead.decrypt(cipherText, encryptedKey.getBytes(UTF_8)); 547 ByteBuffer buffer = ByteBuffer.wrap(value); 548 buffer.position(0); 549 int typeId = buffer.getInt(); 550 EncryptedType type = EncryptedType.fromId(typeId); 551 if (type == null) { 552 throw new SecurityException("Unknown type ID for encrypted pref value: " + typeId); 553 } 554 555 switch (type) { 556 case STRING: 557 int stringLength = buffer.getInt(); 558 ByteBuffer stringSlice = buffer.slice(); 559 buffer.limit(stringLength); 560 561 String stringValue = UTF_8.decode(stringSlice).toString(); 562 if (stringValue.equals(NULL_VALUE)) { 563 return null; 564 } 565 566 return stringValue; 567 case INT: 568 return buffer.getInt(); 569 case LONG: 570 return buffer.getLong(); 571 case FLOAT: 572 return buffer.getFloat(); 573 case BOOLEAN: 574 return buffer.get() != (byte) 0; 575 case STRING_SET: 576 ArraySet<String> stringSet = new ArraySet<>(); 577 578 while (buffer.hasRemaining()) { 579 int subStringLength = buffer.getInt(); 580 ByteBuffer subStringSlice = buffer.slice(); 581 subStringSlice.limit(subStringLength); 582 buffer.position(buffer.position() + subStringLength); 583 stringSet.add(UTF_8.decode(subStringSlice).toString()); 584 } 585 586 if (stringSet.size() == 1 && NULL_VALUE.equals(stringSet.valueAt(0))) { 587 return null; 588 } 589 590 return stringSet; 591 default: 592 throw new SecurityException("Unhandled type for encrypted pref value: " + type); 593 } 594 } catch (GeneralSecurityException ex) { 595 throw new SecurityException("Could not decrypt value. " + ex.getMessage(), ex); 596 } 597 } 598 encryptKey(String key)599 String encryptKey(String key) { 600 if (key == null) { 601 key = NULL_VALUE; 602 } 603 try { 604 byte[] encryptedKeyBytes = mKeyDeterministicAead.encryptDeterministically( 605 key.getBytes(UTF_8), 606 mFileName.getBytes()); 607 return Base64.encode(encryptedKeyBytes); 608 } catch (GeneralSecurityException ex) { 609 throw new SecurityException("Could not encrypt key. " + ex.getMessage(), ex); 610 } 611 } 612 decryptKey(String encryptedKey)613 String decryptKey(String encryptedKey) { 614 try { 615 byte[] clearText = mKeyDeterministicAead.decryptDeterministically( 616 Base64.decode(encryptedKey, Base64.DEFAULT), 617 mFileName.getBytes()); 618 String key = new String(clearText, UTF_8); 619 if (key.equals(NULL_VALUE)) { 620 key = null; 621 } 622 return key; 623 } catch (GeneralSecurityException ex) { 624 throw new SecurityException("Could not decrypt key. " + ex.getMessage(), ex); 625 } 626 } 627 628 629 /** 630 * Check usage of the key and value keysets. 631 * 632 * @param key the plain text key 633 */ isReservedKey(String key)634 boolean isReservedKey(String key) { 635 return KEY_KEYSET_ALIAS.equals(key) || VALUE_KEYSET_ALIAS.equals(key); 636 } 637 encryptKeyValuePair(String key, byte[] value)638 Pair<String, String> encryptKeyValuePair(String key, byte[] value) 639 throws GeneralSecurityException { 640 String encryptedKey = encryptKey(key); 641 byte[] cipherText = mValueAead.encrypt(value, encryptedKey.getBytes(UTF_8)); 642 return new Pair<>(encryptedKey, Base64.encode(cipherText)); 643 } 644 645 } 646