Updates to support minSdkVersion=21
This creates a new "MasterKey" class that holds onto a key alias even
when that key may not exist in Android keystore.
Methods in EncryptedFile and EncryptedSharedPreferences also accept a
new MasterKey instance, in addition to the String "masterKeyAlias".
Relnote: "Add support for Android Lollipop (API 21+) via a new MasterKey
class. Because the Android keystore is not used prior to Android M (API
23+), developers should be aware that the keystore will _not_ be used
on Android L (API 21 and 22)."
Test: Existing and new tests pass
Bug: 132325342
Change-Id: I7c12d205273e4b652271865e53ff6c406632f407
diff --git a/security/crypto/api/1.1.0-alpha01.txt b/security/crypto/api/1.1.0-alpha01.txt
index 3fb36a8..b6fa8f0 100644
--- a/security/crypto/api/1.1.0-alpha01.txt
+++ b/security/crypto/api/1.1.0-alpha01.txt
@@ -7,7 +7,8 @@
}
public static final class EncryptedFile.Builder {
- ctor public EncryptedFile.Builder(java.io.File, android.content.Context, String, androidx.security.crypto.EncryptedFile.FileEncryptionScheme);
+ ctor @Deprecated public EncryptedFile.Builder(java.io.File, android.content.Context, String, androidx.security.crypto.EncryptedFile.FileEncryptionScheme);
+ ctor public EncryptedFile.Builder(android.content.Context, java.io.File, androidx.security.crypto.MasterKey, androidx.security.crypto.EncryptedFile.FileEncryptionScheme);
method public androidx.security.crypto.EncryptedFile build() throws java.security.GeneralSecurityException, java.io.IOException;
method public androidx.security.crypto.EncryptedFile.Builder setKeysetAlias(String);
method public androidx.security.crypto.EncryptedFile.Builder setKeysetPrefName(String);
@@ -19,7 +20,8 @@
public final class EncryptedSharedPreferences implements android.content.SharedPreferences {
method public boolean contains(String?);
- method public static android.content.SharedPreferences create(String, String, android.content.Context, androidx.security.crypto.EncryptedSharedPreferences.PrefKeyEncryptionScheme, androidx.security.crypto.EncryptedSharedPreferences.PrefValueEncryptionScheme) throws java.security.GeneralSecurityException, java.io.IOException;
+ method public static android.content.SharedPreferences create(android.content.Context, String, androidx.security.crypto.MasterKey, androidx.security.crypto.EncryptedSharedPreferences.PrefKeyEncryptionScheme, androidx.security.crypto.EncryptedSharedPreferences.PrefValueEncryptionScheme) throws java.security.GeneralSecurityException, java.io.IOException;
+ method @Deprecated public static android.content.SharedPreferences create(String, String, android.content.Context, androidx.security.crypto.EncryptedSharedPreferences.PrefKeyEncryptionScheme, androidx.security.crypto.EncryptedSharedPreferences.PrefValueEncryptionScheme) throws java.security.GeneralSecurityException, java.io.IOException;
method public android.content.SharedPreferences.Editor edit();
method public java.util.Map<java.lang.String!,?> getAll();
method public boolean getBoolean(String?, boolean);
@@ -40,9 +42,34 @@
enum_constant public static final androidx.security.crypto.EncryptedSharedPreferences.PrefValueEncryptionScheme AES256_GCM;
}
- public final class MasterKeys {
- method public static String getOrCreate(android.security.keystore.KeyGenParameterSpec) throws java.security.GeneralSecurityException, java.io.IOException;
- field public static final android.security.keystore.KeyGenParameterSpec AES256_GCM_SPEC;
+ public final class MasterKey {
+ method public static int getDefaultAuthenticationValidityDurationSeconds();
+ method public int getUserAuthenticationValidityDurationSeconds();
+ method public boolean isKeyStoreBacked();
+ method public boolean isStrongBoxBacked();
+ method public boolean isUserAuthenticationRequired();
+ field public static final int DEFAULT_AES_GCM_MASTER_KEY_SIZE = 256; // 0x100
+ field public static final String DEFAULT_MASTER_KEY_ALIAS = "_androidx_security_master_key_";
+ }
+
+ public static final class MasterKey.Builder {
+ ctor public MasterKey.Builder(android.content.Context);
+ ctor public MasterKey.Builder(android.content.Context, String);
+ method public androidx.security.crypto.MasterKey build() throws java.security.GeneralSecurityException, java.io.IOException;
+ method @RequiresApi(android.os.Build.VERSION_CODES.M) public androidx.security.crypto.MasterKey.Builder setKeyGenParameterSpec(android.security.keystore.KeyGenParameterSpec);
+ method public androidx.security.crypto.MasterKey.Builder setKeyScheme(androidx.security.crypto.MasterKey.KeyScheme);
+ method public androidx.security.crypto.MasterKey.Builder setTryToUseStrongBox(boolean);
+ method public androidx.security.crypto.MasterKey.Builder setUserAuthenticationRequired(boolean);
+ method public androidx.security.crypto.MasterKey.Builder setUserAuthenticationRequired(boolean, @IntRange(from=1) int);
+ }
+
+ public enum MasterKey.KeyScheme {
+ enum_constant public static final androidx.security.crypto.MasterKey.KeyScheme AES256_GCM;
+ }
+
+ @Deprecated public final class MasterKeys {
+ method @Deprecated @RequiresApi(android.os.Build.VERSION_CODES.M) public static String getOrCreate(android.security.keystore.KeyGenParameterSpec) throws java.security.GeneralSecurityException, java.io.IOException;
+ field @Deprecated @RequiresApi(android.os.Build.VERSION_CODES.M) public static final android.security.keystore.KeyGenParameterSpec AES256_GCM_SPEC;
}
}
diff --git a/security/crypto/api/current.txt b/security/crypto/api/current.txt
index 3fb36a8..b6fa8f0 100644
--- a/security/crypto/api/current.txt
+++ b/security/crypto/api/current.txt
@@ -7,7 +7,8 @@
}
public static final class EncryptedFile.Builder {
- ctor public EncryptedFile.Builder(java.io.File, android.content.Context, String, androidx.security.crypto.EncryptedFile.FileEncryptionScheme);
+ ctor @Deprecated public EncryptedFile.Builder(java.io.File, android.content.Context, String, androidx.security.crypto.EncryptedFile.FileEncryptionScheme);
+ ctor public EncryptedFile.Builder(android.content.Context, java.io.File, androidx.security.crypto.MasterKey, androidx.security.crypto.EncryptedFile.FileEncryptionScheme);
method public androidx.security.crypto.EncryptedFile build() throws java.security.GeneralSecurityException, java.io.IOException;
method public androidx.security.crypto.EncryptedFile.Builder setKeysetAlias(String);
method public androidx.security.crypto.EncryptedFile.Builder setKeysetPrefName(String);
@@ -19,7 +20,8 @@
public final class EncryptedSharedPreferences implements android.content.SharedPreferences {
method public boolean contains(String?);
- method public static android.content.SharedPreferences create(String, String, android.content.Context, androidx.security.crypto.EncryptedSharedPreferences.PrefKeyEncryptionScheme, androidx.security.crypto.EncryptedSharedPreferences.PrefValueEncryptionScheme) throws java.security.GeneralSecurityException, java.io.IOException;
+ method public static android.content.SharedPreferences create(android.content.Context, String, androidx.security.crypto.MasterKey, androidx.security.crypto.EncryptedSharedPreferences.PrefKeyEncryptionScheme, androidx.security.crypto.EncryptedSharedPreferences.PrefValueEncryptionScheme) throws java.security.GeneralSecurityException, java.io.IOException;
+ method @Deprecated public static android.content.SharedPreferences create(String, String, android.content.Context, androidx.security.crypto.EncryptedSharedPreferences.PrefKeyEncryptionScheme, androidx.security.crypto.EncryptedSharedPreferences.PrefValueEncryptionScheme) throws java.security.GeneralSecurityException, java.io.IOException;
method public android.content.SharedPreferences.Editor edit();
method public java.util.Map<java.lang.String!,?> getAll();
method public boolean getBoolean(String?, boolean);
@@ -40,9 +42,34 @@
enum_constant public static final androidx.security.crypto.EncryptedSharedPreferences.PrefValueEncryptionScheme AES256_GCM;
}
- public final class MasterKeys {
- method public static String getOrCreate(android.security.keystore.KeyGenParameterSpec) throws java.security.GeneralSecurityException, java.io.IOException;
- field public static final android.security.keystore.KeyGenParameterSpec AES256_GCM_SPEC;
+ public final class MasterKey {
+ method public static int getDefaultAuthenticationValidityDurationSeconds();
+ method public int getUserAuthenticationValidityDurationSeconds();
+ method public boolean isKeyStoreBacked();
+ method public boolean isStrongBoxBacked();
+ method public boolean isUserAuthenticationRequired();
+ field public static final int DEFAULT_AES_GCM_MASTER_KEY_SIZE = 256; // 0x100
+ field public static final String DEFAULT_MASTER_KEY_ALIAS = "_androidx_security_master_key_";
+ }
+
+ public static final class MasterKey.Builder {
+ ctor public MasterKey.Builder(android.content.Context);
+ ctor public MasterKey.Builder(android.content.Context, String);
+ method public androidx.security.crypto.MasterKey build() throws java.security.GeneralSecurityException, java.io.IOException;
+ method @RequiresApi(android.os.Build.VERSION_CODES.M) public androidx.security.crypto.MasterKey.Builder setKeyGenParameterSpec(android.security.keystore.KeyGenParameterSpec);
+ method public androidx.security.crypto.MasterKey.Builder setKeyScheme(androidx.security.crypto.MasterKey.KeyScheme);
+ method public androidx.security.crypto.MasterKey.Builder setTryToUseStrongBox(boolean);
+ method public androidx.security.crypto.MasterKey.Builder setUserAuthenticationRequired(boolean);
+ method public androidx.security.crypto.MasterKey.Builder setUserAuthenticationRequired(boolean, @IntRange(from=1) int);
+ }
+
+ public enum MasterKey.KeyScheme {
+ enum_constant public static final androidx.security.crypto.MasterKey.KeyScheme AES256_GCM;
+ }
+
+ @Deprecated public final class MasterKeys {
+ method @Deprecated @RequiresApi(android.os.Build.VERSION_CODES.M) public static String getOrCreate(android.security.keystore.KeyGenParameterSpec) throws java.security.GeneralSecurityException, java.io.IOException;
+ field @Deprecated @RequiresApi(android.os.Build.VERSION_CODES.M) public static final android.security.keystore.KeyGenParameterSpec AES256_GCM_SPEC;
}
}
diff --git a/security/crypto/api/public_plus_experimental_1.1.0-alpha01.txt b/security/crypto/api/public_plus_experimental_1.1.0-alpha01.txt
index 3fb36a8..b6fa8f0 100644
--- a/security/crypto/api/public_plus_experimental_1.1.0-alpha01.txt
+++ b/security/crypto/api/public_plus_experimental_1.1.0-alpha01.txt
@@ -7,7 +7,8 @@
}
public static final class EncryptedFile.Builder {
- ctor public EncryptedFile.Builder(java.io.File, android.content.Context, String, androidx.security.crypto.EncryptedFile.FileEncryptionScheme);
+ ctor @Deprecated public EncryptedFile.Builder(java.io.File, android.content.Context, String, androidx.security.crypto.EncryptedFile.FileEncryptionScheme);
+ ctor public EncryptedFile.Builder(android.content.Context, java.io.File, androidx.security.crypto.MasterKey, androidx.security.crypto.EncryptedFile.FileEncryptionScheme);
method public androidx.security.crypto.EncryptedFile build() throws java.security.GeneralSecurityException, java.io.IOException;
method public androidx.security.crypto.EncryptedFile.Builder setKeysetAlias(String);
method public androidx.security.crypto.EncryptedFile.Builder setKeysetPrefName(String);
@@ -19,7 +20,8 @@
public final class EncryptedSharedPreferences implements android.content.SharedPreferences {
method public boolean contains(String?);
- method public static android.content.SharedPreferences create(String, String, android.content.Context, androidx.security.crypto.EncryptedSharedPreferences.PrefKeyEncryptionScheme, androidx.security.crypto.EncryptedSharedPreferences.PrefValueEncryptionScheme) throws java.security.GeneralSecurityException, java.io.IOException;
+ method public static android.content.SharedPreferences create(android.content.Context, String, androidx.security.crypto.MasterKey, androidx.security.crypto.EncryptedSharedPreferences.PrefKeyEncryptionScheme, androidx.security.crypto.EncryptedSharedPreferences.PrefValueEncryptionScheme) throws java.security.GeneralSecurityException, java.io.IOException;
+ method @Deprecated public static android.content.SharedPreferences create(String, String, android.content.Context, androidx.security.crypto.EncryptedSharedPreferences.PrefKeyEncryptionScheme, androidx.security.crypto.EncryptedSharedPreferences.PrefValueEncryptionScheme) throws java.security.GeneralSecurityException, java.io.IOException;
method public android.content.SharedPreferences.Editor edit();
method public java.util.Map<java.lang.String!,?> getAll();
method public boolean getBoolean(String?, boolean);
@@ -40,9 +42,34 @@
enum_constant public static final androidx.security.crypto.EncryptedSharedPreferences.PrefValueEncryptionScheme AES256_GCM;
}
- public final class MasterKeys {
- method public static String getOrCreate(android.security.keystore.KeyGenParameterSpec) throws java.security.GeneralSecurityException, java.io.IOException;
- field public static final android.security.keystore.KeyGenParameterSpec AES256_GCM_SPEC;
+ public final class MasterKey {
+ method public static int getDefaultAuthenticationValidityDurationSeconds();
+ method public int getUserAuthenticationValidityDurationSeconds();
+ method public boolean isKeyStoreBacked();
+ method public boolean isStrongBoxBacked();
+ method public boolean isUserAuthenticationRequired();
+ field public static final int DEFAULT_AES_GCM_MASTER_KEY_SIZE = 256; // 0x100
+ field public static final String DEFAULT_MASTER_KEY_ALIAS = "_androidx_security_master_key_";
+ }
+
+ public static final class MasterKey.Builder {
+ ctor public MasterKey.Builder(android.content.Context);
+ ctor public MasterKey.Builder(android.content.Context, String);
+ method public androidx.security.crypto.MasterKey build() throws java.security.GeneralSecurityException, java.io.IOException;
+ method @RequiresApi(android.os.Build.VERSION_CODES.M) public androidx.security.crypto.MasterKey.Builder setKeyGenParameterSpec(android.security.keystore.KeyGenParameterSpec);
+ method public androidx.security.crypto.MasterKey.Builder setKeyScheme(androidx.security.crypto.MasterKey.KeyScheme);
+ method public androidx.security.crypto.MasterKey.Builder setTryToUseStrongBox(boolean);
+ method public androidx.security.crypto.MasterKey.Builder setUserAuthenticationRequired(boolean);
+ method public androidx.security.crypto.MasterKey.Builder setUserAuthenticationRequired(boolean, @IntRange(from=1) int);
+ }
+
+ public enum MasterKey.KeyScheme {
+ enum_constant public static final androidx.security.crypto.MasterKey.KeyScheme AES256_GCM;
+ }
+
+ @Deprecated public final class MasterKeys {
+ method @Deprecated @RequiresApi(android.os.Build.VERSION_CODES.M) public static String getOrCreate(android.security.keystore.KeyGenParameterSpec) throws java.security.GeneralSecurityException, java.io.IOException;
+ field @Deprecated @RequiresApi(android.os.Build.VERSION_CODES.M) public static final android.security.keystore.KeyGenParameterSpec AES256_GCM_SPEC;
}
}
diff --git a/security/crypto/api/public_plus_experimental_current.txt b/security/crypto/api/public_plus_experimental_current.txt
index 3fb36a8..b6fa8f0 100644
--- a/security/crypto/api/public_plus_experimental_current.txt
+++ b/security/crypto/api/public_plus_experimental_current.txt
@@ -7,7 +7,8 @@
}
public static final class EncryptedFile.Builder {
- ctor public EncryptedFile.Builder(java.io.File, android.content.Context, String, androidx.security.crypto.EncryptedFile.FileEncryptionScheme);
+ ctor @Deprecated public EncryptedFile.Builder(java.io.File, android.content.Context, String, androidx.security.crypto.EncryptedFile.FileEncryptionScheme);
+ ctor public EncryptedFile.Builder(android.content.Context, java.io.File, androidx.security.crypto.MasterKey, androidx.security.crypto.EncryptedFile.FileEncryptionScheme);
method public androidx.security.crypto.EncryptedFile build() throws java.security.GeneralSecurityException, java.io.IOException;
method public androidx.security.crypto.EncryptedFile.Builder setKeysetAlias(String);
method public androidx.security.crypto.EncryptedFile.Builder setKeysetPrefName(String);
@@ -19,7 +20,8 @@
public final class EncryptedSharedPreferences implements android.content.SharedPreferences {
method public boolean contains(String?);
- method public static android.content.SharedPreferences create(String, String, android.content.Context, androidx.security.crypto.EncryptedSharedPreferences.PrefKeyEncryptionScheme, androidx.security.crypto.EncryptedSharedPreferences.PrefValueEncryptionScheme) throws java.security.GeneralSecurityException, java.io.IOException;
+ method public static android.content.SharedPreferences create(android.content.Context, String, androidx.security.crypto.MasterKey, androidx.security.crypto.EncryptedSharedPreferences.PrefKeyEncryptionScheme, androidx.security.crypto.EncryptedSharedPreferences.PrefValueEncryptionScheme) throws java.security.GeneralSecurityException, java.io.IOException;
+ method @Deprecated public static android.content.SharedPreferences create(String, String, android.content.Context, androidx.security.crypto.EncryptedSharedPreferences.PrefKeyEncryptionScheme, androidx.security.crypto.EncryptedSharedPreferences.PrefValueEncryptionScheme) throws java.security.GeneralSecurityException, java.io.IOException;
method public android.content.SharedPreferences.Editor edit();
method public java.util.Map<java.lang.String!,?> getAll();
method public boolean getBoolean(String?, boolean);
@@ -40,9 +42,34 @@
enum_constant public static final androidx.security.crypto.EncryptedSharedPreferences.PrefValueEncryptionScheme AES256_GCM;
}
- public final class MasterKeys {
- method public static String getOrCreate(android.security.keystore.KeyGenParameterSpec) throws java.security.GeneralSecurityException, java.io.IOException;
- field public static final android.security.keystore.KeyGenParameterSpec AES256_GCM_SPEC;
+ public final class MasterKey {
+ method public static int getDefaultAuthenticationValidityDurationSeconds();
+ method public int getUserAuthenticationValidityDurationSeconds();
+ method public boolean isKeyStoreBacked();
+ method public boolean isStrongBoxBacked();
+ method public boolean isUserAuthenticationRequired();
+ field public static final int DEFAULT_AES_GCM_MASTER_KEY_SIZE = 256; // 0x100
+ field public static final String DEFAULT_MASTER_KEY_ALIAS = "_androidx_security_master_key_";
+ }
+
+ public static final class MasterKey.Builder {
+ ctor public MasterKey.Builder(android.content.Context);
+ ctor public MasterKey.Builder(android.content.Context, String);
+ method public androidx.security.crypto.MasterKey build() throws java.security.GeneralSecurityException, java.io.IOException;
+ method @RequiresApi(android.os.Build.VERSION_CODES.M) public androidx.security.crypto.MasterKey.Builder setKeyGenParameterSpec(android.security.keystore.KeyGenParameterSpec);
+ method public androidx.security.crypto.MasterKey.Builder setKeyScheme(androidx.security.crypto.MasterKey.KeyScheme);
+ method public androidx.security.crypto.MasterKey.Builder setTryToUseStrongBox(boolean);
+ method public androidx.security.crypto.MasterKey.Builder setUserAuthenticationRequired(boolean);
+ method public androidx.security.crypto.MasterKey.Builder setUserAuthenticationRequired(boolean, @IntRange(from=1) int);
+ }
+
+ public enum MasterKey.KeyScheme {
+ enum_constant public static final androidx.security.crypto.MasterKey.KeyScheme AES256_GCM;
+ }
+
+ @Deprecated public final class MasterKeys {
+ method @Deprecated @RequiresApi(android.os.Build.VERSION_CODES.M) public static String getOrCreate(android.security.keystore.KeyGenParameterSpec) throws java.security.GeneralSecurityException, java.io.IOException;
+ field @Deprecated @RequiresApi(android.os.Build.VERSION_CODES.M) public static final android.security.keystore.KeyGenParameterSpec AES256_GCM_SPEC;
}
}
diff --git a/security/crypto/api/restricted_1.1.0-alpha01.txt b/security/crypto/api/restricted_1.1.0-alpha01.txt
index 3fb36a8..b6fa8f0 100644
--- a/security/crypto/api/restricted_1.1.0-alpha01.txt
+++ b/security/crypto/api/restricted_1.1.0-alpha01.txt
@@ -7,7 +7,8 @@
}
public static final class EncryptedFile.Builder {
- ctor public EncryptedFile.Builder(java.io.File, android.content.Context, String, androidx.security.crypto.EncryptedFile.FileEncryptionScheme);
+ ctor @Deprecated public EncryptedFile.Builder(java.io.File, android.content.Context, String, androidx.security.crypto.EncryptedFile.FileEncryptionScheme);
+ ctor public EncryptedFile.Builder(android.content.Context, java.io.File, androidx.security.crypto.MasterKey, androidx.security.crypto.EncryptedFile.FileEncryptionScheme);
method public androidx.security.crypto.EncryptedFile build() throws java.security.GeneralSecurityException, java.io.IOException;
method public androidx.security.crypto.EncryptedFile.Builder setKeysetAlias(String);
method public androidx.security.crypto.EncryptedFile.Builder setKeysetPrefName(String);
@@ -19,7 +20,8 @@
public final class EncryptedSharedPreferences implements android.content.SharedPreferences {
method public boolean contains(String?);
- method public static android.content.SharedPreferences create(String, String, android.content.Context, androidx.security.crypto.EncryptedSharedPreferences.PrefKeyEncryptionScheme, androidx.security.crypto.EncryptedSharedPreferences.PrefValueEncryptionScheme) throws java.security.GeneralSecurityException, java.io.IOException;
+ method public static android.content.SharedPreferences create(android.content.Context, String, androidx.security.crypto.MasterKey, androidx.security.crypto.EncryptedSharedPreferences.PrefKeyEncryptionScheme, androidx.security.crypto.EncryptedSharedPreferences.PrefValueEncryptionScheme) throws java.security.GeneralSecurityException, java.io.IOException;
+ method @Deprecated public static android.content.SharedPreferences create(String, String, android.content.Context, androidx.security.crypto.EncryptedSharedPreferences.PrefKeyEncryptionScheme, androidx.security.crypto.EncryptedSharedPreferences.PrefValueEncryptionScheme) throws java.security.GeneralSecurityException, java.io.IOException;
method public android.content.SharedPreferences.Editor edit();
method public java.util.Map<java.lang.String!,?> getAll();
method public boolean getBoolean(String?, boolean);
@@ -40,9 +42,34 @@
enum_constant public static final androidx.security.crypto.EncryptedSharedPreferences.PrefValueEncryptionScheme AES256_GCM;
}
- public final class MasterKeys {
- method public static String getOrCreate(android.security.keystore.KeyGenParameterSpec) throws java.security.GeneralSecurityException, java.io.IOException;
- field public static final android.security.keystore.KeyGenParameterSpec AES256_GCM_SPEC;
+ public final class MasterKey {
+ method public static int getDefaultAuthenticationValidityDurationSeconds();
+ method public int getUserAuthenticationValidityDurationSeconds();
+ method public boolean isKeyStoreBacked();
+ method public boolean isStrongBoxBacked();
+ method public boolean isUserAuthenticationRequired();
+ field public static final int DEFAULT_AES_GCM_MASTER_KEY_SIZE = 256; // 0x100
+ field public static final String DEFAULT_MASTER_KEY_ALIAS = "_androidx_security_master_key_";
+ }
+
+ public static final class MasterKey.Builder {
+ ctor public MasterKey.Builder(android.content.Context);
+ ctor public MasterKey.Builder(android.content.Context, String);
+ method public androidx.security.crypto.MasterKey build() throws java.security.GeneralSecurityException, java.io.IOException;
+ method @RequiresApi(android.os.Build.VERSION_CODES.M) public androidx.security.crypto.MasterKey.Builder setKeyGenParameterSpec(android.security.keystore.KeyGenParameterSpec);
+ method public androidx.security.crypto.MasterKey.Builder setKeyScheme(androidx.security.crypto.MasterKey.KeyScheme);
+ method public androidx.security.crypto.MasterKey.Builder setTryToUseStrongBox(boolean);
+ method public androidx.security.crypto.MasterKey.Builder setUserAuthenticationRequired(boolean);
+ method public androidx.security.crypto.MasterKey.Builder setUserAuthenticationRequired(boolean, @IntRange(from=1) int);
+ }
+
+ public enum MasterKey.KeyScheme {
+ enum_constant public static final androidx.security.crypto.MasterKey.KeyScheme AES256_GCM;
+ }
+
+ @Deprecated public final class MasterKeys {
+ method @Deprecated @RequiresApi(android.os.Build.VERSION_CODES.M) public static String getOrCreate(android.security.keystore.KeyGenParameterSpec) throws java.security.GeneralSecurityException, java.io.IOException;
+ field @Deprecated @RequiresApi(android.os.Build.VERSION_CODES.M) public static final android.security.keystore.KeyGenParameterSpec AES256_GCM_SPEC;
}
}
diff --git a/security/crypto/api/restricted_current.txt b/security/crypto/api/restricted_current.txt
index 3fb36a8..b6fa8f0 100644
--- a/security/crypto/api/restricted_current.txt
+++ b/security/crypto/api/restricted_current.txt
@@ -7,7 +7,8 @@
}
public static final class EncryptedFile.Builder {
- ctor public EncryptedFile.Builder(java.io.File, android.content.Context, String, androidx.security.crypto.EncryptedFile.FileEncryptionScheme);
+ ctor @Deprecated public EncryptedFile.Builder(java.io.File, android.content.Context, String, androidx.security.crypto.EncryptedFile.FileEncryptionScheme);
+ ctor public EncryptedFile.Builder(android.content.Context, java.io.File, androidx.security.crypto.MasterKey, androidx.security.crypto.EncryptedFile.FileEncryptionScheme);
method public androidx.security.crypto.EncryptedFile build() throws java.security.GeneralSecurityException, java.io.IOException;
method public androidx.security.crypto.EncryptedFile.Builder setKeysetAlias(String);
method public androidx.security.crypto.EncryptedFile.Builder setKeysetPrefName(String);
@@ -19,7 +20,8 @@
public final class EncryptedSharedPreferences implements android.content.SharedPreferences {
method public boolean contains(String?);
- method public static android.content.SharedPreferences create(String, String, android.content.Context, androidx.security.crypto.EncryptedSharedPreferences.PrefKeyEncryptionScheme, androidx.security.crypto.EncryptedSharedPreferences.PrefValueEncryptionScheme) throws java.security.GeneralSecurityException, java.io.IOException;
+ method public static android.content.SharedPreferences create(android.content.Context, String, androidx.security.crypto.MasterKey, androidx.security.crypto.EncryptedSharedPreferences.PrefKeyEncryptionScheme, androidx.security.crypto.EncryptedSharedPreferences.PrefValueEncryptionScheme) throws java.security.GeneralSecurityException, java.io.IOException;
+ method @Deprecated public static android.content.SharedPreferences create(String, String, android.content.Context, androidx.security.crypto.EncryptedSharedPreferences.PrefKeyEncryptionScheme, androidx.security.crypto.EncryptedSharedPreferences.PrefValueEncryptionScheme) throws java.security.GeneralSecurityException, java.io.IOException;
method public android.content.SharedPreferences.Editor edit();
method public java.util.Map<java.lang.String!,?> getAll();
method public boolean getBoolean(String?, boolean);
@@ -40,9 +42,34 @@
enum_constant public static final androidx.security.crypto.EncryptedSharedPreferences.PrefValueEncryptionScheme AES256_GCM;
}
- public final class MasterKeys {
- method public static String getOrCreate(android.security.keystore.KeyGenParameterSpec) throws java.security.GeneralSecurityException, java.io.IOException;
- field public static final android.security.keystore.KeyGenParameterSpec AES256_GCM_SPEC;
+ public final class MasterKey {
+ method public static int getDefaultAuthenticationValidityDurationSeconds();
+ method public int getUserAuthenticationValidityDurationSeconds();
+ method public boolean isKeyStoreBacked();
+ method public boolean isStrongBoxBacked();
+ method public boolean isUserAuthenticationRequired();
+ field public static final int DEFAULT_AES_GCM_MASTER_KEY_SIZE = 256; // 0x100
+ field public static final String DEFAULT_MASTER_KEY_ALIAS = "_androidx_security_master_key_";
+ }
+
+ public static final class MasterKey.Builder {
+ ctor public MasterKey.Builder(android.content.Context);
+ ctor public MasterKey.Builder(android.content.Context, String);
+ method public androidx.security.crypto.MasterKey build() throws java.security.GeneralSecurityException, java.io.IOException;
+ method @RequiresApi(android.os.Build.VERSION_CODES.M) public androidx.security.crypto.MasterKey.Builder setKeyGenParameterSpec(android.security.keystore.KeyGenParameterSpec);
+ method public androidx.security.crypto.MasterKey.Builder setKeyScheme(androidx.security.crypto.MasterKey.KeyScheme);
+ method public androidx.security.crypto.MasterKey.Builder setTryToUseStrongBox(boolean);
+ method public androidx.security.crypto.MasterKey.Builder setUserAuthenticationRequired(boolean);
+ method public androidx.security.crypto.MasterKey.Builder setUserAuthenticationRequired(boolean, @IntRange(from=1) int);
+ }
+
+ public enum MasterKey.KeyScheme {
+ enum_constant public static final androidx.security.crypto.MasterKey.KeyScheme AES256_GCM;
+ }
+
+ @Deprecated public final class MasterKeys {
+ method @Deprecated @RequiresApi(android.os.Build.VERSION_CODES.M) public static String getOrCreate(android.security.keystore.KeyGenParameterSpec) throws java.security.GeneralSecurityException, java.io.IOException;
+ field @Deprecated @RequiresApi(android.os.Build.VERSION_CODES.M) public static final android.security.keystore.KeyGenParameterSpec AES256_GCM_SPEC;
}
}
diff --git a/security/crypto/build.gradle b/security/crypto/build.gradle
index 2bd04a6..5ec42a0 100644
--- a/security/crypto/build.gradle
+++ b/security/crypto/build.gradle
@@ -29,6 +29,7 @@
api("androidx.annotation:annotation:1.1.0")
implementation("com.google.crypto.tink:tink-android:1.4.0-rc2")
+ implementation(project(":collection:collection"))
androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
androidTestImplementation(ANDROIDX_TEST_CORE)
@@ -40,7 +41,7 @@
android {
defaultConfig {
- minSdkVersion 23
+ minSdkVersion 21
}
}
diff --git a/security/crypto/src/androidTest/java/androidx/security/crypto/EncryptedFileTest.java b/security/crypto/src/androidTest/java/androidx/security/crypto/EncryptedFileTest.java
index 301a4d2..e22f961 100644
--- a/security/crypto/src/androidTest/java/androidx/security/crypto/EncryptedFileTest.java
+++ b/security/crypto/src/androidTest/java/androidx/security/crypto/EncryptedFileTest.java
@@ -16,7 +16,7 @@
package androidx.security.crypto;
-import static androidx.security.crypto.MasterKeys.KEYSTORE_PATH_URI;
+import static androidx.security.crypto.MasterKey.KEYSTORE_PATH_URI;
import static java.nio.charset.StandardCharsets.UTF_8;
@@ -52,7 +52,7 @@
public class EncryptedFileTest {
private Context mContext;
- private String mMasterKeyAlias;
+ private MasterKey mMasterKey;
@Before
public void setup() throws Exception {
@@ -87,7 +87,9 @@
keyStore.load(null);
keyStore.deleteEntry(MasterKeys.MASTER_KEY_ALIAS);
- mMasterKeyAlias = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC);
+ mMasterKey = new MasterKey.Builder(mContext)
+ .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
+ .build();
}
@Test
@@ -97,8 +99,83 @@
// Write
+ EncryptedFile encryptedFile = new EncryptedFile.Builder(mContext,
+ new File(mContext.getFilesDir(),
+ fileName), mMasterKey,
+ EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB)
+ .build();
+
+ OutputStream outputStream = encryptedFile.openFileOutput();
+ outputStream.write(fileContent.getBytes("UTF-8"));
+ outputStream.flush();
+ outputStream.close();
+
+ FileInputStream rawStream = mContext.openFileInput(fileName);
+ ByteArrayOutputStream rawByteArrayOutputStream = new ByteArrayOutputStream();
+ int rawNextByte = rawStream.read();
+ while (rawNextByte != -1) {
+ rawByteArrayOutputStream.write(rawNextByte);
+ rawNextByte = rawStream.read();
+ }
+ byte[] rawCipherText = rawByteArrayOutputStream.toByteArray();
+ System.out.println("Raw CipherText = " + new String(rawCipherText,
+ UTF_8));
+ rawStream.close();
+
+ InputStream inputStream = encryptedFile.openFileInput();
+ ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+ int nextByte = inputStream.read();
+ while (nextByte != -1) {
+ byteArrayOutputStream.write(nextByte);
+ nextByte = inputStream.read();
+ }
+
+ byte[] plainText = byteArrayOutputStream.toByteArray();
+
+ System.out.println("Decrypted Data: " + new String(plainText,
+ UTF_8));
+
+ Assert.assertEquals(
+ "Contents should be equal, data was encrypted.",
+ fileContent, new String(plainText, "UTF-8"));
+ inputStream.close();
+
+
+ EncryptedFile existingFileInputCheck = new EncryptedFile.Builder(mContext,
+ new File(mContext.getFilesDir(), "FAKE_FILE"), mMasterKey,
+ EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB)
+ .build();
+ boolean inputFailed = false;
+ try {
+ existingFileInputCheck.openFileInput();
+ } catch (IOException ex) {
+ inputFailed = true;
+ }
+ Assert.assertTrue("File should have failed opening.", inputFailed);
+
+ EncryptedFile existingFileOutputCheck = new EncryptedFile.Builder(mContext,
+ new File(mContext.getFilesDir(), fileName), mMasterKey,
+ EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB)
+ .build();
+ boolean outputFailed = false;
+ try {
+ existingFileOutputCheck.openFileOutput();
+ } catch (IOException ex) {
+ outputFailed = true;
+ }
+ Assert.assertTrue("File should have failed writing.", outputFailed);
+
+ }
+
+ @Test
+ public void testWriteReadEncryptedFileWithAlias() throws Exception {
+ final String fileContent = "Don't tell anyone...";
+ final String fileName = "nothing_to_see_here";
+
+ // Write
+
EncryptedFile encryptedFile = new EncryptedFile.Builder(new File(mContext.getFilesDir(),
- fileName), mContext, mMasterKeyAlias,
+ fileName), mContext, mMasterKey.getKeyAlias(),
EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB)
.build();
@@ -139,8 +216,8 @@
EncryptedFile existingFileInputCheck = new EncryptedFile.Builder(
- new File(mContext.getFilesDir(), "FAKE_FILE"), mContext, mMasterKeyAlias,
- EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB)
+ new File(mContext.getFilesDir(), "FAKE_FILE"), mContext,
+ mMasterKey.getKeyAlias(), EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB)
.build();
boolean inputFailed = false;
try {
@@ -151,7 +228,7 @@
Assert.assertTrue("File should have failed opening.", inputFailed);
EncryptedFile existingFileOutputCheck = new EncryptedFile.Builder(
- new File(mContext.getFilesDir(), fileName), mContext, mMasterKeyAlias,
+ new File(mContext.getFilesDir(), fileName), mContext, mMasterKey.getKeyAlias(),
EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB)
.build();
boolean outputFailed = false;
@@ -170,8 +247,9 @@
final String fileName = "nothing_to_see_here_custom";
// Write
- EncryptedFile encryptedFile = new EncryptedFile.Builder(new File(mContext.getFilesDir(),
- fileName), mContext, mMasterKeyAlias,
+ EncryptedFile encryptedFile = new EncryptedFile.Builder(mContext,
+ new File(mContext.getFilesDir(),
+ fileName), mMasterKey,
EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB)
.setKeysetAlias("CustomKEYALIAS")
.setKeysetPrefName("CUSTOMPREFNAME")
@@ -225,7 +303,7 @@
File file = new File(mContext.getFilesDir(), fileName);
// Write
- EncryptedFile encryptedFile = new EncryptedFile.Builder(file, mContext, mMasterKeyAlias,
+ EncryptedFile encryptedFile = new EncryptedFile.Builder(mContext, file, mMasterKey,
EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB)
.build();
@@ -240,7 +318,7 @@
.withSharedPref(mContext,
"__androidx_security_crypto_encrypted_file_keyset__",
"__androidx_security_crypto_encrypted_file_pref__")
- .withMasterKeyUri(KEYSTORE_PATH_URI + mMasterKeyAlias)
+ .withMasterKeyUri(KEYSTORE_PATH_URI + mMasterKey.getKeyAlias())
.build().getKeysetHandle();
StreamingAead streamingAead = StreamingAeadFactory.getPrimitive(
diff --git a/security/crypto/src/androidTest/java/androidx/security/crypto/EncryptedSharedPreferencesTest.java b/security/crypto/src/androidTest/java/androidx/security/crypto/EncryptedSharedPreferencesTest.java
index 5e9f0ad..dc80a78 100644
--- a/security/crypto/src/androidTest/java/androidx/security/crypto/EncryptedSharedPreferencesTest.java
+++ b/security/crypto/src/androidTest/java/androidx/security/crypto/EncryptedSharedPreferencesTest.java
@@ -18,7 +18,7 @@
import static android.content.Context.MODE_PRIVATE;
-import static androidx.security.crypto.MasterKeys.KEYSTORE_PATH_URI;
+import static androidx.security.crypto.MasterKey.KEYSTORE_PATH_URI;
import static java.nio.charset.StandardCharsets.UTF_8;
@@ -57,7 +57,7 @@
public class EncryptedSharedPreferencesTest {
private Context mContext;
- private String mKeyAlias;
+ private MasterKey mMasterKey;
private static final String PREFS_FILE = "test_shared_prefs";
@@ -101,15 +101,18 @@
keyStore.load(null);
keyStore.deleteEntry("_androidx_security_master_key_");
- mKeyAlias = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC);
+ mMasterKey = new MasterKey.Builder(mContext)
+ .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
+ .build();
}
@Test
public void testWriteSharedPrefs() throws Exception {
SharedPreferences sharedPreferences = EncryptedSharedPreferences
- .create(PREFS_FILE,
- mKeyAlias, mContext,
+ .create(mContext,
+ PREFS_FILE,
+ mMasterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM);
@@ -341,8 +344,9 @@
String testValue = "TestValue";
SharedPreferences encryptedSharedPreferences = EncryptedSharedPreferences
- .create(tinkTestPrefs,
- mKeyAlias, mContext,
+ .create(mContext,
+ tinkTestPrefs,
+ mMasterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM);
diff --git a/security/crypto/src/androidTest/java/androidx/security/crypto/MasterKeySecureTest.java b/security/crypto/src/androidTest/java/androidx/security/crypto/MasterKeySecureTest.java
new file mode 100644
index 0000000..fc78abb
--- /dev/null
+++ b/security/crypto/src/androidTest/java/androidx/security/crypto/MasterKeySecureTest.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.security.crypto;
+
+import static android.content.Context.MODE_PRIVATE;
+
+import android.app.KeyguardManager;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.Build;
+
+import androidx.security.crypto.MasterKey.KeyScheme;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.MediumTest;
+import androidx.test.filters.SdkSuppress;
+
+import org.junit.Assert;
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.File;
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.security.KeyStore;
+
+/**
+ * These are tests that require the device to have a lockscreen enabled.
+ */
+@MediumTest
+@RunWith(JUnit4.class)
+public class MasterKeySecureTest {
+ private static final String PREFS_FILE = "test_shared_prefs";
+ private static final int KEY_SIZE = 256;
+
+ @Before
+ @SuppressWarnings("ResultOfMethodCallIgnored")
+ public void setup() throws Exception {
+ final Context context = ApplicationProvider.getApplicationContext();
+
+ KeyguardManager keyguardManager = context.getSystemService(KeyguardManager.class);
+ Assume.assumeTrue(keyguardManager != null && keyguardManager.isDeviceSecure());
+
+ // Delete all previous keys and shared preferences.
+
+ String filePath = context.getFilesDir().getParent() + "/shared_prefs/"
+ + "__androidx_security__crypto_encrypted_prefs__";
+ File deletePrefFile = new File(filePath);
+ deletePrefFile.delete();
+
+ SharedPreferences notEncryptedSharedPrefs = context.getSharedPreferences(PREFS_FILE,
+ MODE_PRIVATE);
+ notEncryptedSharedPrefs.edit().clear().commit();
+
+ filePath = context.getFilesDir().getParent() + "/shared_prefs/"
+ + PREFS_FILE;
+ deletePrefFile = new File(filePath);
+ deletePrefFile.delete();
+
+ SharedPreferences encryptedSharedPrefs = context.getSharedPreferences("TinkTestPrefs",
+ MODE_PRIVATE);
+ encryptedSharedPrefs.edit().clear().commit();
+
+ filePath = context.getFilesDir().getParent() + "/shared_prefs/"
+ + "TinkTestPrefs";
+ deletePrefFile = new File(filePath);
+ deletePrefFile.delete();
+
+ // Delete MasterKeys
+ KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
+ keyStore.load(null);
+ keyStore.deleteEntry("_androidx_security_master_key_");
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.M)
+ @Test
+ public void testCreateKeyWithAuthenicationRequired() throws GeneralSecurityException,
+ IOException {
+ MasterKey masterKey = new MasterKey.Builder(ApplicationProvider.getApplicationContext())
+ .setKeyScheme(KeyScheme.AES256_GCM)
+ .setUserAuthenticationRequired(true, 10)
+ .build();
+ MasterKeyTest.assertKeyExists(masterKey.getKeyAlias());
+ Assert.assertTrue(masterKey.isUserAuthenticationRequired());
+ Assert.assertEquals(masterKey.getUserAuthenticationValidityDurationSeconds(), 10);
+ }
+}
diff --git a/security/crypto/src/androidTest/java/androidx/security/crypto/MasterKeyTest.java b/security/crypto/src/androidTest/java/androidx/security/crypto/MasterKeyTest.java
new file mode 100644
index 0000000..ee67c2f
--- /dev/null
+++ b/security/crypto/src/androidTest/java/androidx/security/crypto/MasterKeyTest.java
@@ -0,0 +1,223 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.security.crypto;
+
+import static android.content.Context.MODE_PRIVATE;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.Build;
+import android.security.keystore.KeyGenParameterSpec;
+import android.security.keystore.KeyProperties;
+
+import androidx.security.crypto.MasterKey.KeyScheme;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.MediumTest;
+import androidx.test.filters.SdkSuppress;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.File;
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.security.KeyStore;
+
+@MediumTest
+@RunWith(JUnit4.class)
+public class MasterKeyTest {
+ private static final String PREFS_FILE = "test_shared_prefs";
+ private static final int KEY_SIZE = 256;
+
+ @Before
+ @SuppressWarnings("ResultOfMethodCallIgnored")
+ public void setup() throws Exception {
+
+ final Context context = ApplicationProvider.getApplicationContext();
+
+ // Delete all previous keys and shared preferences.
+
+ String filePath = context.getFilesDir().getParent() + "/shared_prefs/"
+ + "__androidx_security__crypto_encrypted_prefs__";
+ File deletePrefFile = new File(filePath);
+ deletePrefFile.delete();
+
+ SharedPreferences notEncryptedSharedPrefs = context.getSharedPreferences(PREFS_FILE,
+ MODE_PRIVATE);
+ notEncryptedSharedPrefs.edit().clear().commit();
+
+ filePath = context.getFilesDir().getParent() + "/shared_prefs/"
+ + PREFS_FILE;
+ deletePrefFile = new File(filePath);
+ deletePrefFile.delete();
+
+ SharedPreferences encryptedSharedPrefs = context.getSharedPreferences("TinkTestPrefs",
+ MODE_PRIVATE);
+ encryptedSharedPrefs.edit().clear().commit();
+
+ filePath = context.getFilesDir().getParent() + "/shared_prefs/"
+ + "TinkTestPrefs";
+ deletePrefFile = new File(filePath);
+ deletePrefFile.delete();
+
+ // Delete MasterKeys
+ KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
+ keyStore.load(null);
+ keyStore.deleteEntry("_androidx_security_master_key_");
+ }
+
+ @Test
+ public void testCreateDefaultKey() throws GeneralSecurityException, IOException {
+ MasterKey masterKey = new MasterKey.Builder(ApplicationProvider.getApplicationContext())
+ .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
+ .build();
+ assertKeyExists(masterKey.getKeyAlias());
+ }
+
+ @Test
+ public void testCreateRenamedKey() throws GeneralSecurityException, IOException {
+ final String testAlias = "TestKeyAlias";
+ MasterKey masterKey =
+ new MasterKey.Builder(ApplicationProvider.getApplicationContext(), testAlias)
+ .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
+ .build();
+ Assert.assertEquals(masterKey.getKeyAlias(), testAlias);
+ assertKeyExists(masterKey.getKeyAlias());
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.M)
+ @Test
+ public void testCreateKeyWithParamSpec() throws GeneralSecurityException, IOException {
+ KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder(
+ MasterKey.DEFAULT_MASTER_KEY_ALIAS,
+ KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
+ .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
+ .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
+ .setKeySize(KEY_SIZE)
+ .build();
+ MasterKey masterKey = new MasterKey.Builder(ApplicationProvider.getApplicationContext())
+ .setKeyGenParameterSpec(spec)
+ .build();
+ assertKeyExists(masterKey.getKeyAlias());
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.M)
+ @Test
+ public void testCreateKeyWithParamSpecAndAlias() throws GeneralSecurityException,
+ IOException {
+ KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder("test_key_alias",
+ KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
+ .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
+ .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
+ .setKeySize(KEY_SIZE)
+ .build();
+ MasterKey masterKey = new MasterKey.Builder(
+ ApplicationProvider.getApplicationContext(), "test_key_alias")
+ .setKeyGenParameterSpec(spec)
+ .build();
+ assertKeyExists(masterKey.getKeyAlias());
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.M)
+ @Test
+ public void testCreateKeyWithParamSpecWithDifferentAliasFails() throws GeneralSecurityException,
+ IOException {
+ KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder("test_key_alias",
+ KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
+ .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
+ .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
+ .setKeySize(KEY_SIZE)
+ .build();
+ try {
+ MasterKey masterKey = new MasterKey.Builder(ApplicationProvider.getApplicationContext())
+ .setKeyGenParameterSpec(spec)
+ .build();
+ Assert.fail("Could create key with inconsistent key alias");
+ } catch (IllegalArgumentException iae) {
+ // Pass
+ }
+ }
+
+ @Test
+ public void testCheckIfKeyIsKeyStoreBacked() throws GeneralSecurityException,
+ IOException {
+ MasterKey masterKey = new MasterKey.Builder(ApplicationProvider.getApplicationContext())
+ .setKeyScheme(KeyScheme.AES256_GCM)
+ .build();
+
+ KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
+ keyStore.load(null);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ Assert.assertTrue(masterKey.isKeyStoreBacked());
+ assertKeyExists(masterKey.getKeyAlias());
+ }
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.M)
+ @Test
+ public void testUseOfSchemeAndParamsFails() throws GeneralSecurityException,
+ IOException {
+ KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder(MasterKeys.MASTER_KEY_ALIAS,
+ KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
+ .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
+ .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
+ .setKeySize(KEY_SIZE)
+ .build();
+
+ try {
+ MasterKey masterKey = new MasterKey.Builder(ApplicationProvider.getApplicationContext())
+ .setKeyScheme(KeyScheme.AES256_GCM)
+ .setKeyGenParameterSpec(spec)
+ .build();
+ Assert.fail("Could create key with both scheme + KeyGenParameterSpec");
+ } catch (IllegalArgumentException iae) {
+ // Pass.
+ }
+ }
+
+ @Test
+ public void testCheckGettersAreCallable() throws GeneralSecurityException,
+ IOException {
+ MasterKey masterKey = new MasterKey.Builder(ApplicationProvider.getApplicationContext())
+ .setKeyScheme(KeyScheme.AES256_GCM)
+ .build();
+ Assert.assertFalse(masterKey.isUserAuthenticationRequired());
+ Assert.assertFalse(masterKey.isStrongBoxBacked());
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
+ Assert.assertEquals(masterKey.getUserAuthenticationValidityDurationSeconds(), 0);
+ }
+ }
+
+ static void assertKeyExists(String keyAlias) {
+ try {
+ KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
+ keyStore.load(null);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ Assert.assertTrue(keyStore.isKeyEntry(keyAlias));
+ } else {
+ // Key shouldn't exist on Lollipop =o
+ Assert.assertFalse(keyStore.isKeyEntry(keyAlias));
+ }
+ } catch (Exception e) {
+ Assert.fail("Exception checking for key: " + keyAlias);
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/security/crypto/src/androidTest/java/androidx/security/crypto/MasterKeysTest.java b/security/crypto/src/androidTest/java/androidx/security/crypto/MasterKeysTest.java
index 140619a..ed8e695 100644
--- a/security/crypto/src/androidTest/java/androidx/security/crypto/MasterKeysTest.java
+++ b/security/crypto/src/androidTest/java/androidx/security/crypto/MasterKeysTest.java
@@ -20,11 +20,13 @@
import android.content.Context;
import android.content.SharedPreferences;
+import android.os.Build;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.filters.MediumTest;
+import androidx.test.filters.SdkSuppress;
import org.junit.Assert;
import org.junit.Before;
@@ -38,6 +40,7 @@
import java.security.KeyStore;
@MediumTest
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.M)
@RunWith(JUnit4.class)
public class MasterKeysTest {
private static final String PREFS_FILE = "test_shared_prefs";
diff --git a/security/crypto/src/main/java/androidx/security/crypto/EncryptedFile.java b/security/crypto/src/main/java/androidx/security/crypto/EncryptedFile.java
index 76e886a..5fa72eb 100644
--- a/security/crypto/src/main/java/androidx/security/crypto/EncryptedFile.java
+++ b/security/crypto/src/main/java/androidx/security/crypto/EncryptedFile.java
@@ -16,10 +16,11 @@
package androidx.security.crypto;
-import static androidx.security.crypto.MasterKeys.KEYSTORE_PATH_URI;
+import static androidx.security.crypto.MasterKey.KEYSTORE_PATH_URI;
import static java.nio.charset.StandardCharsets.UTF_8;
+import android.annotation.SuppressLint;
import android.content.Context;
import androidx.annotation.NonNull;
@@ -61,7 +62,6 @@
* // read the encrypted file
* FileInputStream encryptedInputStream = encryptedFile.openFileInput();
* </pre>
- *
*/
public final class EncryptedFile {
@@ -75,7 +75,6 @@
final String mMasterKeyAlias;
final StreamingAead mStreamingAead;
-
EncryptedFile(
@NonNull File file,
@NonNull String masterKeyAlias,
@@ -118,16 +117,45 @@
*/
public static final class Builder {
+ /**
+ * Builder for an EncryptedFile.
+ *
+ * @deprecated Use {@link #Builder(Context, File, MasterKey, FileEncryptionScheme)} instead.
+ */
+ @Deprecated
public Builder(@NonNull File file,
@NonNull Context context,
@NonNull String masterKeyAlias,
@NonNull FileEncryptionScheme fileEncryptionScheme) {
mFile = file;
mFileEncryptionScheme = fileEncryptionScheme;
- mContext = context;
+ mContext = context.getApplicationContext();
mMasterKeyAlias = masterKeyAlias;
}
+ /**
+ * Builder for an EncryptedFile.
+ */
+ // [StreamFiles]: Because the contents of EncryptedFile are encrypted the use of
+ // a FileDescriptor or Streams are intentionally not supported for the following reasons:
+ // - The encrypted content is tightly coupled to the current installation of the app. If
+ // the app is uninstalled, even if the data remained (such as being stored in a public
+ // directory or another DocumentProvider) it would be (intentionally) unrecoverable.
+ // - If the API did accept either an already opened FileDescriptor or a stream, then it
+ // would be possible for the developer to inadvertently commingle encrypted and plain
+ // text data, which, due to the way the API is structured, could render both encrypted
+ // and unencrypted data irrecoverable.
+ @SuppressLint("StreamFiles")
+ public Builder(@NonNull Context context,
+ @NonNull File file,
+ @NonNull MasterKey masterKey,
+ @NonNull FileEncryptionScheme fileEncryptionScheme) {
+ mFile = file;
+ mFileEncryptionScheme = fileEncryptionScheme;
+ mContext = context.getApplicationContext();
+ mMasterKeyAlias = masterKey.getKeyAlias();
+ }
+
// Required parameters
File mFile;
final FileEncryptionScheme mFileEncryptionScheme;
@@ -187,7 +215,7 @@
*
* @return The FileOutputStream that encrypts all data.
* @throws GeneralSecurityException when a bad master key or keyset has been used
- * @throws IOException when the file already exists or is not available for writing
+ * @throws IOException when the file already exists or is not available for writing
*/
@NonNull
public FileOutputStream openFileOutput()
@@ -210,7 +238,7 @@
*
* @return The input stream to read previously encrypted data.
* @throws GeneralSecurityException when a bad master key or keyset has been used
- * @throws IOException when the file was not found
+ * @throws IOException when the file was not found
*/
@NonNull
public FileInputStream openFileInput()
@@ -226,7 +254,6 @@
/**
* Encrypted file output stream
- *
*/
private static final class EncryptedFileOutputStream extends FileOutputStream {
diff --git a/security/crypto/src/main/java/androidx/security/crypto/EncryptedSharedPreferences.java b/security/crypto/src/main/java/androidx/security/crypto/EncryptedSharedPreferences.java
index 5223548..80a1320 100644
--- a/security/crypto/src/main/java/androidx/security/crypto/EncryptedSharedPreferences.java
+++ b/security/crypto/src/main/java/androidx/security/crypto/EncryptedSharedPreferences.java
@@ -16,17 +16,17 @@
package androidx.security.crypto;
-import static androidx.security.crypto.MasterKeys.KEYSTORE_PATH_URI;
+import static androidx.security.crypto.MasterKey.KEYSTORE_PATH_URI;
import static java.nio.charset.StandardCharsets.UTF_8;
import android.content.Context;
import android.content.SharedPreferences;
-import android.util.ArraySet;
import android.util.Pair;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.collection.ArraySet;
import com.google.crypto.tink.Aead;
import com.google.crypto.tink.DeterministicAead;
@@ -100,12 +100,43 @@
/**
* Opens an instance of encrypted SharedPreferences
*
- * @param fileName The name of the file to open; can not contain path separators.
+ * @param fileName The name of the file to open; can not contain path
+ * separators.
+ * @param masterKey The master key to use.
+ * @param prefKeyEncryptionScheme The scheme to use for encrypting keys.
+ * @param prefValueEncryptionScheme The scheme to use for encrypting values.
* @return The SharedPreferences instance that encrypts all data.
* @throws GeneralSecurityException when a bad master key or keyset has been attempted
* @throws IOException when fileName can not be used
*/
@NonNull
+ public static SharedPreferences create(@NonNull Context context,
+ @NonNull String fileName,
+ @NonNull MasterKey masterKey,
+ @NonNull PrefKeyEncryptionScheme prefKeyEncryptionScheme,
+ @NonNull PrefValueEncryptionScheme prefValueEncryptionScheme)
+ throws GeneralSecurityException, IOException {
+ return create(fileName, masterKey.getKeyAlias(), context,
+ prefKeyEncryptionScheme, prefValueEncryptionScheme);
+ }
+
+ /**
+ * Opens an instance of encrypted SharedPreferences
+ *
+ * @param fileName The name of the file to open; can not contain path
+ * separators.
+ * @param masterKeyAlias The alias of the master key to use.
+ * @param context The context to use to open the preferences file.
+ * @param prefKeyEncryptionScheme The scheme to use for encrypting keys.
+ * @param prefValueEncryptionScheme The scheme to use for encrypting values.
+ * @return The SharedPreferences instance that encrypts all data.
+ * @throws GeneralSecurityException when a bad master key or keyset has been attempted
+ * @throws IOException when fileName can not be used
+ * @deprecated Use {@link #create(Context, String, MasterKey,
+ * PrefKeyEncryptionScheme, PrefValueEncryptionScheme)} instead.
+ */
+ @Deprecated
+ @NonNull
public static SharedPreferences create(@NonNull String fileName,
@NonNull String masterKeyAlias,
@NonNull Context context,
@@ -114,14 +145,15 @@
throws GeneralSecurityException, IOException {
TinkConfig.register();
+ final Context applicationContext = context.getApplicationContext();
KeysetHandle daeadKeysetHandle = new AndroidKeysetManager.Builder()
.withKeyTemplate(prefKeyEncryptionScheme.getKeyTemplate())
- .withSharedPref(context, KEY_KEYSET_ALIAS, fileName)
+ .withSharedPref(applicationContext, KEY_KEYSET_ALIAS, fileName)
.withMasterKeyUri(KEYSTORE_PATH_URI + masterKeyAlias)
.build().getKeysetHandle();
KeysetHandle aeadKeysetHandle = new AndroidKeysetManager.Builder()
.withKeyTemplate(prefValueEncryptionScheme.getKeyTemplate())
- .withSharedPref(context, VALUE_KEYSET_ALIAS, fileName)
+ .withSharedPref(applicationContext, VALUE_KEYSET_ALIAS, fileName)
.withMasterKeyUri(KEYSTORE_PATH_URI + masterKeyAlias)
.build().getKeysetHandle();
@@ -129,7 +161,8 @@
Aead aead = aeadKeysetHandle.getPrimitive(Aead.class);
return new EncryptedSharedPreferences(fileName, masterKeyAlias,
- context.getSharedPreferences(fileName, Context.MODE_PRIVATE), aead, daead);
+ applicationContext.getSharedPreferences(fileName, Context.MODE_PRIVATE), aead,
+ daead);
}
/**
diff --git a/security/crypto/src/main/java/androidx/security/crypto/MasterKey.java b/security/crypto/src/main/java/androidx/security/crypto/MasterKey.java
new file mode 100644
index 0000000..a3790ad
--- /dev/null
+++ b/security/crypto/src/main/java/androidx/security/crypto/MasterKey.java
@@ -0,0 +1,360 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.security.crypto;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.security.keystore.KeyGenParameterSpec;
+import android.security.keystore.KeyProperties;
+
+import androidx.annotation.IntRange;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateException;
+
+/**
+ * Wrapper for a master key used in the library.
+ *
+ * On Android M (API 23) and above, this is class references a key that's stored in the
+ * Android Keystore. On Android L (API 21, 22), there isn't a master key.
+ */
+public final class MasterKey {
+ static final String KEYSTORE_PATH_URI = "android-keystore://";
+
+ /**
+ * The default master key alias.
+ */
+ public static final String DEFAULT_MASTER_KEY_ALIAS = "_androidx_security_master_key_";
+
+ /**
+ * The default and recommended size for the master key.
+ */
+ public static final int DEFAULT_AES_GCM_MASTER_KEY_SIZE = 256;
+
+ private static final int DEFAULT_AUTHENTICATION_VALIDITY_DURATION_SECONDS = 5 * 60;
+
+ @NonNull
+ private final String mKeyAlias;
+ @Nullable
+ private final KeyGenParameterSpec mKeyGenParameterSpec;
+
+ /**
+ * Algorithm/Cipher choices used for the master key.
+ */
+ public enum KeyScheme {
+ AES256_GCM
+ }
+
+ /**
+ * The default validity period for authentication in seconds.
+ */
+ @SuppressLint("MethodNameUnits")
+ public static int getDefaultAuthenticationValidityDurationSeconds() {
+ return DEFAULT_AUTHENTICATION_VALIDITY_DURATION_SECONDS;
+ }
+
+ /* package */ MasterKey(@NonNull String keyAlias, @Nullable Object keyGenParameterSpec) {
+ mKeyAlias = keyAlias;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ mKeyGenParameterSpec = (KeyGenParameterSpec) keyGenParameterSpec;
+ } else {
+ mKeyGenParameterSpec = null;
+ }
+ }
+
+ /**
+ * Checks if this key is backed by the Android Keystore.
+ *
+ * @return {@code true} if the key is in Android Keystore, {@code false} otherwise. This
+ * method always returns false when called on Android Lollipop (API 21 and 22).
+ */
+ public boolean isKeyStoreBacked() {
+ // Keystore is not used prior to Android M (API 23)
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
+ return false;
+ }
+
+ try {
+ KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
+ keyStore.load(null);
+ return keyStore.containsAlias(mKeyAlias);
+ } catch (KeyStoreException | CertificateException
+ | NoSuchAlgorithmException | IOException ignored) {
+ return false;
+ }
+ }
+
+ /**
+ * Gets whether user authentication is required to use this key.
+ *
+ * This method always returns {@code false} on Android L (API 21 + 22).
+ */
+ public boolean isUserAuthenticationRequired() {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
+ return false;
+ }
+ return mKeyGenParameterSpec != null && mKeyGenParameterSpec.isUserAuthenticationRequired();
+ }
+
+ /**
+ * Gets the duration in seconds that the key is unlocked for following user authentication.
+ *
+ * The value returned for this method is only meaningful on Android M+ (API 23) when
+ * {@link #isUserAuthenticationRequired()} returns {@code true}.
+ *
+ * @return The duration the key is unlocked for in seconds.
+ */
+ @SuppressLint("MethodNameUnits")
+ public int getUserAuthenticationValidityDurationSeconds() {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
+ return 0;
+ }
+ return mKeyGenParameterSpec == null ? 0 :
+ mKeyGenParameterSpec.getUserAuthenticationValidityDurationSeconds();
+ }
+
+ /**
+ * Gets whether the key is backed by strong box.
+ */
+ public boolean isStrongBoxBacked() {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P || mKeyGenParameterSpec == null) {
+ return false;
+ }
+ return mKeyGenParameterSpec.isStrongBoxBacked();
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ return "MasterKey{keyAlias=" + mKeyAlias
+ + ", isKeyStoreBacked=" + isKeyStoreBacked()
+ + "}";
+ }
+
+ @NonNull/* package */ String getKeyAlias() {
+ return mKeyAlias;
+ }
+
+ /**
+ * Builder for generating a {@link MasterKey}.
+ */
+ public static final class Builder {
+ @NonNull
+ private final String mKeyAlias;
+
+ @Nullable
+ private KeyGenParameterSpec mKeyGenParameterSpec;
+ @Nullable
+ private KeyScheme mKeyScheme;
+
+ private boolean mAuthenticationRequired;
+ private int mUserAuthenticationValidityDurationSeconds;
+ private boolean mTryToUseStrongBox;
+
+ private final Context mContext;
+
+ /**
+ * Creates a builder for a {@link MasterKey} using the default alias of
+ * {@link #DEFAULT_MASTER_KEY_ALIAS}.
+ *
+ * @param context The context to use with this master key.
+ */
+ public Builder(@NonNull Context context) {
+ this(context, MasterKey.DEFAULT_MASTER_KEY_ALIAS);
+ }
+
+ /**
+ * Creates a builder for a {@link MasterKey}.
+ *
+ * @param context The context to use with this master key.
+ */
+ public Builder(@NonNull Context context, @NonNull String keyAlias) {
+ mContext = context.getApplicationContext();
+ mKeyAlias = keyAlias;
+ }
+
+ /**
+ * Sets a {@link KeyScheme} to be used for the master key.
+ * This uses a default {@link KeyGenParameterSpec} associated with the provided
+ * {@code KeyScheme}.
+ * NOTE: Either this method OR {@link #setKeyGenParameterSpec} should be used to set
+ * the parameters to use for building the master key. Calling either function after
+ * the other will throw an {@link IllegalArgumentException}.
+ *
+ * @param keyScheme The KeyScheme to use.
+ * @return This builder.
+ */
+ @NonNull
+ public Builder setKeyScheme(@NonNull KeyScheme keyScheme) {
+ switch (keyScheme) {
+ case AES256_GCM:
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ if (mKeyGenParameterSpec != null) {
+ throw new IllegalArgumentException("KeyScheme set after setting a "
+ + "KeyGenParamSpec");
+ }
+ }
+ break;
+ default:
+ throw new IllegalArgumentException("Unsupported scheme: " + keyScheme);
+ }
+ mKeyScheme = keyScheme;
+ return this;
+ }
+
+ /**
+ * When used with {@link #setKeyScheme(KeyScheme)}, sets that the built master key should
+ * require the user to authenticate before it's unlocked, probably using the
+ * androidx.biometric library.
+ *
+ * This method sets the validity duration of the key to
+ * {@link #getDefaultAuthenticationValidityDurationSeconds()}.
+ *
+ * @param authenticationRequired Whether user authentication should be required to use
+ * the key.
+ * @return This builder.
+ */
+ @NonNull
+ public Builder setUserAuthenticationRequired(boolean authenticationRequired) {
+ return setUserAuthenticationRequired(authenticationRequired,
+ getDefaultAuthenticationValidityDurationSeconds());
+ }
+
+ /**
+ * When used with {@link #setKeyScheme(KeyScheme)}, sets that the built master key should
+ * require the user to authenticate before it's unlocked, probably using the
+ * androidx.biometric library, and that the key should remain unlocked for the provided
+ * duration.
+ *
+ * @param authenticationRequired Whether user authentication should be
+ * required to use the key.
+ * @param userAuthenticationValidityDurationSeconds Duration in seconds that the key
+ * should remain unlocked following user
+ * authentication.
+ * @return This builder.
+ */
+ @NonNull
+ public Builder setUserAuthenticationRequired(boolean authenticationRequired,
+ @IntRange(from = 1) int userAuthenticationValidityDurationSeconds) {
+ mAuthenticationRequired = authenticationRequired;
+ mUserAuthenticationValidityDurationSeconds = userAuthenticationValidityDurationSeconds;
+ return this;
+ }
+
+ /**
+ * Sets whether or not to try to make this key strong box backed. This setting is only
+ * applicable on {@link Build.VERSION_CODES#P} and above, and only on devices that
+ * support Strongbox.
+ *
+ * @param tryToUseStrongBox Whether to try and use strongbox
+ * @return This builder.
+ */
+ @NonNull
+ public Builder setTryToUseStrongBox(boolean tryToUseStrongBox) {
+ mTryToUseStrongBox = tryToUseStrongBox;
+ return this;
+ }
+
+ /**
+ * Sets a custom {@link KeyGenParameterSpec} to use as the basis of the master key.
+ * NOTE: Either this method OR {@link #setKeyGenParameterSpec} should be used to set
+ * the parameters to use for building the master key. Calling either function after
+ * the other will throw an {@link IllegalArgumentException}.
+ *
+ * @param keyGenParameterSpec The key spec to use.
+ * @return This builder.
+ */
+ @NonNull
+ @RequiresApi(Build.VERSION_CODES.M)
+ public Builder setKeyGenParameterSpec(@NonNull KeyGenParameterSpec keyGenParameterSpec) {
+ if (mKeyScheme != null) {
+ throw new IllegalArgumentException("KeyGenParamSpec set after setting a "
+ + "KeyScheme");
+ }
+ if (!mKeyAlias.equals(keyGenParameterSpec.getKeystoreAlias())) {
+ throw new IllegalArgumentException("KeyGenParamSpec's key alias does not match "
+ + "provided alias (" + mKeyAlias + " vs "
+ + keyGenParameterSpec.getKeystoreAlias());
+ }
+ mKeyGenParameterSpec = keyGenParameterSpec;
+ return this;
+ }
+
+ /**
+ * Builds a {@link MasterKey} from this builder.
+ *
+ * @return The master key.
+ */
+ @NonNull
+ public MasterKey build() throws GeneralSecurityException, IOException {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ return buildOnM();
+ } else {
+ return new MasterKey(mKeyAlias, null);
+ }
+ }
+
+ @RequiresApi(Build.VERSION_CODES.M)
+ private MasterKey buildOnM() throws GeneralSecurityException, IOException {
+ if (mKeyScheme == null && mKeyGenParameterSpec == null) {
+ throw new IllegalArgumentException("build() called before "
+ + "setKeyGenParameterSpec or setKeyScheme.");
+ }
+
+ if (mKeyScheme == KeyScheme.AES256_GCM) {
+ KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec.Builder(
+ mKeyAlias,
+ KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
+ .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
+ .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
+ .setKeySize(DEFAULT_AES_GCM_MASTER_KEY_SIZE);
+
+ if (mAuthenticationRequired) {
+ builder.setUserAuthenticationRequired(true)
+ .setUserAuthenticationValidityDurationSeconds(
+ mUserAuthenticationValidityDurationSeconds);
+ }
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && mTryToUseStrongBox) {
+ if (mContext.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_STRONGBOX_KEYSTORE)) {
+ builder.setIsStrongBoxBacked(true);
+ }
+ }
+
+ mKeyGenParameterSpec = builder.build();
+ }
+ if (mKeyGenParameterSpec == null) {
+ // This really should not happen.
+ throw new NullPointerException("KeyGenParameterSpec was null after build() check");
+ }
+
+ @SuppressWarnings("deprecation")
+ String keyAlias = MasterKeys.getOrCreate(mKeyGenParameterSpec);
+ return new MasterKey(keyAlias, mKeyGenParameterSpec);
+ }
+ }
+}
diff --git a/security/crypto/src/main/java/androidx/security/crypto/MasterKeys.java b/security/crypto/src/main/java/androidx/security/crypto/MasterKeys.java
index 289a440..2590053 100644
--- a/security/crypto/src/main/java/androidx/security/crypto/MasterKeys.java
+++ b/security/crypto/src/main/java/androidx/security/crypto/MasterKeys.java
@@ -16,15 +16,18 @@
package androidx.security.crypto;
+import android.os.Build;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
import androidx.annotation.VisibleForTesting;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
+import java.security.ProviderException;
import java.util.Arrays;
import javax.crypto.KeyGenerator;
@@ -33,18 +36,21 @@
* Convenient methods to create and obtain master keys in Android Keystore.
*
* <p>The master keys are used to encrypt data encryption keys for encrypting files and preferences.
+ *
+ * @deprecated Use {@link MasterKey.Builder} to work with master keys.
*/
+@Deprecated
public final class MasterKeys {
private MasterKeys() {
}
- private static final int KEY_SIZE = 256;
+ static final String MASTER_KEY_ALIAS = MasterKey.DEFAULT_MASTER_KEY_ALIAS;
+ static final int KEY_SIZE = MasterKey.DEFAULT_AES_GCM_MASTER_KEY_SIZE;
private static final String ANDROID_KEYSTORE = "AndroidKeyStore";
- static final String KEYSTORE_PATH_URI = "android-keystore://";
- static final String MASTER_KEY_ALIAS = "_androidx_security_master_key_";
@NonNull
+ @RequiresApi(Build.VERSION_CODES.M)
public static final KeyGenParameterSpec AES256_GCM_SPEC =
createAES256GCMKeyGenParameterSpec(MASTER_KEY_ALIAS);
@@ -59,6 +65,8 @@
* @return The spec for the master key with the specified keyAlias
*/
@NonNull
+ @RequiresApi(Build.VERSION_CODES.M)
+ @SuppressWarnings("SameParameterValue")
private static KeyGenParameterSpec createAES256GCMKeyGenParameterSpec(
@NonNull String keyAlias) {
KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec.Builder(
@@ -80,6 +88,7 @@
* @return The key alias for the master key
*/
@NonNull
+ @RequiresApi(Build.VERSION_CODES.M)
public static String getOrCreate(
@NonNull KeyGenParameterSpec keyGenParameterSpec)
throws GeneralSecurityException, IOException {
@@ -91,6 +100,7 @@
}
@VisibleForTesting
+ @RequiresApi(Build.VERSION_CODES.M)
static void validate(KeyGenParameterSpec spec) {
if (spec.getKeySize() != KEY_SIZE) {
throw new IllegalArgumentException(
@@ -121,13 +131,20 @@
}
}
+ @RequiresApi(Build.VERSION_CODES.M)
private static void generateKey(@NonNull KeyGenParameterSpec keyGenParameterSpec)
throws GeneralSecurityException {
- KeyGenerator keyGenerator = KeyGenerator.getInstance(
- KeyProperties.KEY_ALGORITHM_AES,
- ANDROID_KEYSTORE);
- keyGenerator.init(keyGenParameterSpec);
- keyGenerator.generateKey();
+ try {
+ KeyGenerator keyGenerator = KeyGenerator.getInstance(
+ KeyProperties.KEY_ALGORITHM_AES,
+ ANDROID_KEYSTORE);
+ keyGenerator.init(keyGenParameterSpec);
+ keyGenerator.generateKey();
+ } catch (ProviderException providerException) {
+ // Android 10 (API 29) throws a ProviderException under certain circumstances. Wrap
+ // that as a GeneralSecurityException so it's more consistent across API levels.
+ throw new GeneralSecurityException(providerException.getMessage(), providerException);
+ }
}
private static boolean keyExists(@NonNull String keyAlias)