Armadillo
A shared preference implementation for confidential data in Android. Per default uses AES-GCM, BCrypt and HKDF as cryptographic primitives. Uses the concept of device fingerprinting combined with optional user provided passwords and strong password hashes.
Install / Use
/learn @patrickfav/ArmadilloREADME
Armadillo - Encrypted Shared Preference
<img src="https://github.com/patrickfav/armadillo/blob/main/doc/logo/logo_ldpi.png?raw=true" align="right" alt="Armadillo Logo" width="170" height="207">
A shared preference implementation for secret data providing confidentiality, integrity and authenticity. Per default uses AES-GCM, BCrypt and HKDF as cryptographic primitives.
Important Notice: If you migrate to v0.6.0 and use a user password and default key stretching function migration is needed due to a security issue. See migration guide in the changelog for v0.6.0
Features
- No-Nonse State-of-the-Art Crypto: Authenticated Encryption with AES-GCM, key derivation functions Bcrypt and HKDF
- Flexible: Tons of nobs and switches while having sane defaults
- Modular: use your own implementation of symmetric cipher, key stretching, data obfuscation, etc.
- Lightweight: No massive dependencies required like BouncyCastle or Facebook Conceal
Security Summary
- Using it with a user provided password (and strong password hash, like the default BCrypt): your data is strongly encrypted
- Using it without a user provided password: your data is obfuscated and cannot be easily altered or read by an attacker with access to the device
- By using fingerprinting, it is not easily possible to just copy data over to another device and use it there
- Encryption is non-deterministic, which means even if you encrypt the same data it appears to be different
- All encrypted data is protected against modification by an outside attacker, so long as the encryption itself is not circumvented
- The Android Keystore System is not used, since it proved to be unreliable and hard to handle in production due to device fragmentation and poor driver support (read more below). This implementation is a good fallback solution for a system that uses the aforementioned.
Quick Start
Add the following to your dependencies (add jcenter to your repositories if you haven't)
compile 'at.favre.lib:armadillo:x.y.z'
A very minimal example
SharedPreferences preferences = Armadillo.create(context, "myPrefs")
.encryptionFingerprint(context)
.build();
preferences.edit().putString("key1", "stringValue").commit();
String s = preferences.getString("key1", null);
Advanced Example
The following example shows some of the configurations available to the developer:
String userId = ...
SharedPreferences preferences = Armadillo.create(context, "myCustomPreferences")
.password("mySuperSecretPassword".toCharArray()) //use user provided password
.securityProvider(Security.getProvider("BC")) //use bouncy-castle security provider
.keyStretchingFunction(new PBKDF2KeyStretcher()) //use PBKDF2 as user password kdf
.contentKeyDigest(Bytes.from(getAndroidId(context)).array()) //use custom content key digest salt
.secureRandom(new SecureRandom()) //provide your own secure random for salt/iv generation
.encryptionFingerprint(context, userId.getBytes(StandardCharsets.UTF_8)) //add the user id to fingerprint
.supportVerifyPassword(true) //enables optional password validation support `.isValidPassword()`
.enableKitKatSupport(true) //enable optional kitkat support
.enableDerivedPasswordCache(true) //enable caching for derived password making consecutive getters faster
.build();
A xml file named like f1a4e61ffb59c6e6a3d6ceae9a20cb5726aade06.xml will
be created with the resulting data looking something like that after the
first put operation:
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
<!-- storage random salt -->
<string name="585d6f0f415682ace841fb50d5980d60ed23a2ef">riIPjrL2WRfoh8QJXu7fWk4GGeAKlQoJl9ofABHZKlc=</string>
<!-- 'key1':'stringValue' -->
<string name="152b866fd2d63899678c21f247bb6df0d2e38072">AAAAABD/8an1zfovjJB/2MFOT9ncAAAAJaf+Z9xgzwXzp1BqTsVMnRZxR/HfRcO8lEhyKpL17QmZ5amwAYQ=</string>
</map>
KitKat Support
Unfortunately Android SDK 19 (KITKAT) does not fully support AES GCM mode. Therefore a backwards compatible implementation of AES using CBC with Encrypt-then-MAC can be used to support this library on older devices. This should provide the same security strength as the GCM version, however the support must be enabled manually:
SharedPreferences preferences = Armadillo.create(context, "myCustomPreferences")
.enableKitKatSupport(true)
...
.build();
In this mode, if on a KitKat device the backwards-compatible implementation is used, the default AES-GCM version otherwise. Upgrading to a newer OS version the content should still be decryptable, while newer content will then be encrypted with the AES-GCM version.
Description
Design Choices
- AES + GCM block mode: To make sure that the data is not only kept confidential, but it's integrity also preserved, the authenticated encryption AES+GCM is used. GCM can be implemented efficiently and fast and is the usually alternative to encrypt then mac with AES+CBC and HMAC. The authentication tag is appended to the message and is 16 byte long in this implementation. A downside of GCM is the requirement to never reuse a IV with the same key, which is avoided in this lib.
- Every put operation creates a different cipher text: Every put operation generates new salts, iv so the the resulting cipher text will be unrecognizably different even with the same underlying data. This makes it harder to check if the data actually has changed.
- KDFs with Key Stretching features for user passwords: Add brute-force protection to possibly weak user provided passwords (e.g. BCrypt).
- Minimum SDK 19 (Android 4.4): A way to increase security is to cap older implementation. SDK 19 seems to be a good compromise where most of the older security hack fixes are not necessary anymore, but still targeting most devices.
- Use of JCA as Provider for cryptographic primitives:
Various security frameworks exists in Java: BouncyCastle,
Conscrypt, Facebook Conceal.
The problem is that these libraries are usually huge and require manual
updates to have all the latest security fixes.
This library however depends on the default JCA provider (although the developer may choose a
different one). This puts trust in the device and it's implementation, while
expecting frequent security patches. Usually the default provider since KitKat is
AndroidOpenSSLprovider which is fast (probably hardware accelerated for e.g. AES) and heavily used by e.g. TLS implementation. - Android Keystore System is not used: In my humble opinion, the Android Keystore is the best possible way to secure data on an Android device. Unfortunately, due to the massive fragmentation in the ecosystem properly handling and using the Android Keystore System is not easy and has some major drawbacks. Due to working in a security relevant field I have a lot of experience with this technology, therefore the decision was made to not support it for this implementation i.e. to keep it simple.
- Use of data obfuscation: To disguise the actual data format and appear as a pseudo random byte array, obfuscation is used. This deliberately uses non standard ways to make it a bit harder to reverse engineer.
User provided Passwords
A high entropy value not known to any system but the user is a good and strong base for a cryptographic key. Unfortunately user-based passwords are often weak (low-entropy). To mitigate that fact and help preventing easy brute-forcing key derivation functions with key stretching properties are used. These functions calculate pseudo-random data from it's source material which requires mandatory work.
The follow
