[Loophole] Is it really secure?
So, the package's Android code contains key prefix which is completely static. This increases the chances of key decryption very high. Also, if you are just storing it in shared_preference, it's way risky. Can someone explain how this was not detected by anyone and how can this be secure?
Code Reference

So, the package's Android code contains key prefix which is completely static. This increases the chances of key decryption very high. Also, if you are just storing it in shared_preference, it's way risky. Can someone explain how this was not detected by anyone and how can this be secure? Code Reference
if I'm not wrong the key (key/value pair key. /me is not talking about encryption keys) is never encoded or encrypted anyway (in shared preference), the prefix is just to find the flutter secure storage keys in shared prefrences (prior to 5.0, now encryptedSharedPrefrences is used) and value is not affected by this
@Techno-Disaster encryptedSharedPrefrences seems to have some problem if true when the application pushed in google play store
I think issue is that there's a implementation for method channels for method readAll and contains .
These method shouldn't even exist. You should never be able to read all encrypted shared prefs keys in one method. You need to know the key and fetch one entry.
So this package saves the keys on the side in shared prefs to be able to do that.
Exactly @itsJoKr , we shouldn't even know the prefix like this. Only the programmer should be knowing the entire key.
I think issue is that there's a implementation for method channels for method
readAllandcontainswhich are not available in flutter side.
Quick question what do you mean by not available on the flutter side? I see that you can call storage.readlAll() from dart code as well?
These method shouldn't even exist. You should never be able to read all encrypted shared prefs keys in one method. You need to know the key and fetch one entry.
Could you explain a bit more on why getting all keys at once bad? There probably are use cases, I can see a developer adding something and then checking if it is in prefs at a later time with containsKey (there probably are better use cases)
So this package saves the keys on the side in shared prefs to be able to do that. This two methods and whole prefix thing should be deleted.
If I'm not wrong prefix thing is just so that if there are other keys kn shared prefs we only get the fluttersecurestorage keys
Exactly @itsJoKr , we shouldn't even know the prefix like this. Only the programmer should be knowing the entire key.
Could you explain how a non programmer/malacious actorcould exploit this?
Quick question what do you mean by not available on the flutter side? I see that you can call storage.readlAll() from dart code as well?
Sorry, I see now that methods are available in the flutter/dart side. OK, so supposedly it's a valid use case, then there should be supported way to save read all keys without saving them on the side (which doesn't look right). EncryptedSharedPrefrences have getAll() method, and keystore have keyStore.aliases(), maybe that's the way.
Could you explain how a non programmer/malacious actorcould exploit this?
Ehem, normally malicious actors are indeed programmers (black hat hackers to be precise). Anyway, I took a look at the code and from what I can see, you are storing the generated encryption key in the shared preferences encoded using Base64, then you use that key to encrypt other data, but the key itself is stored in plain text on the device (unless using the encrypted variant).
If the key is not encrypted, an attacker with physical access to the device could just connect the device via ADB and download the sharedpreferences file, I think you can even do it without root access, if you know the full path, but then again rooting a phone is not that difficult for a tech savvy individual.
Without physical access, you are relying on the Android framework to ensure that no other application can access the SharedPreferences (by using Context.MODE_PRIVATE). There might be some (not yet publicly known) unpatched vulnerability there that could be used to bypass this restriction. ~Nevertheless, storing the keys in plain text should never be considered truly "secure" by modern standards. Maybe you should take a look at Android's Keystore~
Update: Upon further inspection I realized the encryption key is indeed stored in encrypted form. First an RSA private/public keypair is generated and stored in Android's Keystore. Then, an AES key is generated and encrypted using the RSA public key. So, what's stored in the shared preferences is actually an encrypted key.
you are storing the generated encryption key in the shared preferences encoded using Base64, then you use that key to encrypt other data, but the key itself is stored in plain text on the device (unless using the encrypted variant).
afaik we only store key value pair keys in shared prefs, could you show where the encrypted key part is stored? 🤔 afaik if you are not using encrypedSharedPrefs, the key should just be prefix_{actuaKeyForValue}
afaik we only store key value pair keys in shared prefs, could you show where the encrypted key part is stored? 🤔 afaik if you are not using encrypedSharedPrefs, the key should just be
prefix_{actuaKeyForValue}
We are getting confused about different types of keys here. Let's identify all the "keys" that are involved in the process.
- An AES key generated here on first launch, and then stored in the preferences just a few lines below.
- The Shared/EncryptedPreferences key. The key or name that you use to set or get a value from the preferences file. The plugin is using the preferences to store two things. The value passed to the write function here, for that it adds a prefix before the actual key. And it also stores the AES key mentioned earlier.
- A private/public RSA key pair generated here. Which is stored in Android's Keystore and used to encrypt the AES key here.
So, to sum up, all the data stored in the preferences file is encrypted. That includes the AES key and the values requested by the application (via read and write methods). The AES key is used to encrypt/decrypt those values, and the AES key itself is encrypted with the RSA key, which is stored using Android's Keystore.
Therefore, for an attacker to read any data, it would require them to somehow bypass Android's restrictions to use the RSA keys stored in the Keystore (otherwise all they will get from the preferences files is encrypted data). Android's framework shouldn't allow other apps to use the same keys. However, according to the official docs:
If the app's process is compromised, the attacker may be able to use the app's keys but cannot extract their key material (for example, to be used outside of the Android device).
So, it's not 100% secure, but nothing is 100% secure anyway. If anything, the cipher algorithms used may be changed, as proposed in the PR #400
Besides the algorithms issue, the library is pretty secure so far in the Android side. So, after reviewing it, I certainly don't see any loophole here.
PD: One thing that isn't encrypted is the "keys" used to identify the values stored in the shared preferences (the second item in the list up there). So, don't store anything sensitive in the key parameter. Maybe it's too obvious, but I'm going to leave this here in case any amateur dev ends up here.
// DON'T do this
String dbUrl = 'mysql://somehost':
String dbPassword = 'MySuperSecretPassword';
await storage.write(key: dbUrl, value: dbPassword ); // You are exposing here the database host
Always store sensitive values in the value parameter.
String dbUrl = 'mysql://somehost':
String dbPassword = 'MySuperSecretPassword';
await storage.write(key: 'DB_HOST', value: dbUrl );
await storage.write(key: 'DB_PASS', value: dbPassword );
// Alternatively you could use some less obvious name for the keys.
const DB_HOST_KEY = 'KEY_1';
const DB_PASS_KEY = 'KEY_2';
await storage.write(key: DB_HOST_KEY, value: dbUrl );
await storage.write(key: DB_PASS_KEY, value: dbPassword );