48

I decided to use new EncryptedSharedPreferences from AndroidX Security library. Since the app is supporting API 21 and higher, I decided to try out this new v1.1.0-alpha02 version, since it supports API 21+

So, I succeded to make the implementation for API 23+, but for older versions where Android KeyStore is not supported, I couldn't make it right, and there are no exact instructions how the master key should be created to make it work somehow.

The code for initializing SharedPrefs:

EncryptedSharedPreferences.create(
        "prefs_name",
        createMasterKey(),
        App.appContext,
        EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
        EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
    )

with this function for creating master key

   private fun createMasterKey(): String {
        return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC)
        } else {
            val alias = "my_alias"
            val start: Calendar = GregorianCalendar()
            val end: Calendar = GregorianCalendar()
            end.add(Calendar.YEAR, 30)

            val spec = KeyPairGeneratorSpec.Builder(App.appContext)
                .setAlias(alias)
                .setSubject(X500Principal("CN=$alias"))
                .setSerialNumber(BigInteger.valueOf(abs(alias.hashCode()).toLong()))
                .setStartDate(start.time).setEndDate(end.time)
                .build()

            val kpGenerator: KeyPairGenerator = KeyPairGenerator.getInstance(
                "RSA",
                "AndroidKeyStore"
            )
            kpGenerator.initialize(spec)
            val kp: KeyPair = kpGenerator.generateKeyPair()
            
            kp.public.toString()
        }
    }

I found this solution somewhere out there, but it's not verified (no confirmation that it actually works), but it seems it should work.

When using this code block for API 21 and 22, the error appears on creating EncryptedSharedPreferences, and it says: Method threw 'com.google.crypto.tink.shaded.protobuf.InvalidProtocolBufferException' exception. Protocol message contained an invalid tag (zero).

Did someone find the solution for this implementation, or do you know why is this happening? I think this would help a lot of people, since there is no exact explanation what should this master key contain.

Thanks in advance!

CoolMind
  • 26,736
  • 15
  • 188
  • 224
aherman
  • 900
  • 1
  • 8
  • 16
  • 1
    I experience the same error when using EncryptedFile of the androidx.security.crypto library. It only happens on one device _(Samsung S9+ (Android 10))_ tested directly so far and another device _(Pixel 2 (Android 11))_ according to the comment of another user. Please also refer to the issue: [Google Issue Tracker - Related Issue 168407869](https://issuetracker.google.com/issues/168407869) – goldensoju Jan 19 '21 at 06:16
  • @goldensoju in the issue tracker you concluded this wasn't a crypto issue--what was it, please? – Oded Mar 09 '21 at 06:20
  • @Oded I assume bad code that corrupted the data being sent to the library. – goldensoju Mar 09 '21 at 13:35
  • @goldensoju OK, thanks. In our case it just seems like random corruption for one user when creating the SharedPreferences file in the first place. A reinstall cleared the issue. – Oded Mar 09 '21 at 22:06
  • The code has been deprecated. – CoolMind Mar 22 '21 at 10:57
  • This problem is likely related to open [Android Issue 164901843](https://issuetracker.google.com/issues/164901843) which throws the exact same error. – mike47 Mar 03 '23 at 03:55

5 Answers5

27

Add to manifest android:allowBackup="false" android:fullBackupContent="false"

Because after uninstalling the application you still have backed up your crypto file which you definitely can't decrypt after installing a new version.

Timur Munatayev
  • 287
  • 2
  • 2
  • 2
    This will prevent backup of every possible file, not just of the secured shared preferences – android developer Oct 27 '20 at 08:56
  • 3
    Should we add `android:fullBackupContent="false"`? It refers to a file with storage settings, not a boolean value. – CoolMind Mar 22 '21 at 10:56
  • I think that this is the right solution if you encrypt your shared preferences while using an encryption key from the KeyStore. Android can automatically backup almost every file (like shared prefs) but cannot backup your keys, so your backup becomes useless. – Mariusz Wiazowski Mar 08 '22 at 10:46
12

Here is what I have done, it seems to have fixed the error. Instead of removing the auto-backup feature, just do this:

In AndroidManifest.xml

 android:allowBackup="true"
 android:fullBackupContent="@xml/backup_descriptor"

In backup_descriptor.xml

<full-backup-content>
    <exclude domain="sharedpref" path="keys.xml"/>
</full-backup-content>

keys.xml being the encrypted shared preference file name. Exclude all encrypted shared preference files this way.

Also, I am using implementation 'androidx.security:security-crypto:1.1.0-alpha02'

for now, seems like everything is working well with this setup.

Sean Blahovici
  • 5,350
  • 4
  • 28
  • 38
  • 1
    Your solution doesn't work on NOKIA 7 Plus with Android 10, be careful. I've only fixed it using version 1.1.0-alpha01. ;) – fesave May 05 '21 at 09:51
  • 3
    Same, only 1.1.0-alpha01 helped me. – es0329 May 19 '21 at 04:11
  • @es0329 have you had any compatibility issues when switching to alpha01 with not being able to read data encrypted with alpha03? – luxo Sep 21 '21 at 18:15
  • @luxo Sorry I really don't recall. I quickly reverted to stay on alpha01 when I saw the crashes during development. – es0329 Sep 24 '21 at 04:05
  • For my part, once data was encrypted on alpha02, it crashed if I tried reverting to alpha01. So Im stuck on alpha02, but I am currently crash free in production. – Sean Blahovici Sep 25 '21 at 16:12
  • @SeanBlahovici Interesting. I just want from alpha03 to alpha01 in a production app with quite a few users and have not run into any issues yet (knock on wood). I did a lot of testing switching between the two versions in testing (03 -> 01 and vice versa) and never experienced any crash related to decrypting. What were the crashes you were getting? – luxo Sep 29 '21 at 18:37
  • 1.1.0-alpha01 works for me. 02 crashes – DIRTY DAVE Apr 26 '22 at 20:20
11

I can fix the InvalidProtocolBufferException in two ways, though I don't like either of them:

Use an older version of security-crypto

implementation 'androidx.security:security-crypto:1.1.0-alpha01'

Use the latest (at the time of writing) version of security-crypto, but with a forced older version of tink:

implementation 'androidx.security:security-crypto:1.1.0-alpha03'
implementation('com.google.crypto.tink:tink-android') {
    version {
        strictly '1.4.0'
    }
}
Edmund Johnson
  • 709
  • 8
  • 15
6

UPDATE: Errors on different devices. The result isn't the solution.

I setup the android propreties "fullbackupcontent" this way in my Manifest

android:fullBackupContent="@xml/backup_descriptor"

Here is my backup_descriptor file

<?xml version="1.0" encoding="utf-8"?>
<full-backup-content>

    <!-- App data isn't included in user's backup unless client-side encryption is enabled. -->
    <include domain="file" path="." requireFlags="clientSideEncryption" />

<!-- Exclude specific shared preferences that contain GCM registration Id -->
<!--    <exclude domain=["file" | "database" | "sharedpref" | "external" | "root"]-->
<!--    path="string" />-->
</full-backup-content>

Now my app is working again & I can keep allowing backups.

Source : https://developer.android.com/guide/topics/data/autobackup#define-device-conditions

https://developer.android.com/guide/topics/data/autobackup#IncludingFiles

skywall
  • 3,956
  • 1
  • 34
  • 52
OPF
  • 69
  • 2
-1

Here is a different solution that I found on google issue tracker.

As google document stated, "Note: The methods in both the EncryptedFile class and the EncryptedSharedPreferences class aren't thread-safe." You need to wrap the calling in a synchronised method.

@synchronized
fun createSharedPreferences() {
    val keyGenParameterSpec = MasterKeys.AES256_GCM_SPEC
    val masterKeyAlias = MasterKeys.getOrCreate(keyGenParameterSpec)
    prefs = EncryptedSharedPreferences.create(
        "file-name",
        masterKeyAlias,
        context,
        PrefKeyEncryptionScheme.AES256_SIV,
        PrefValueEncryptionScheme.AES256_GCM)
}

src. https://issuetracker.google.com/issues/147480931#comment19

madlymad
  • 6,367
  • 6
  • 37
  • 68