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