Giter Club home page Giter Club logo

patrickfav / armadillo Goto Github PK

View Code? Open in Web Editor NEW
276.0 10.0 52.0 2.37 MB

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.

Home Page: https://favr.dev/opensource/armadillo

License: Apache License 2.0

Java 100.00%
android sharedpreferences cryptography aes-encryption bcrypt hkdf aes-gcm authenticated-encryption security crypto

armadillo's People

Contributors

davidgarciaanton avatar davidmigloz avatar erlangp avatar iqbalhood avatar markvillacampa avatar marukami avatar patrickfav avatar shisheng-1 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

armadillo's Issues

Java 8 / Springboot version

Is there a version of this project for Java 8 and/or springboot?

Really good project and would love to use it in a server/side project I'm working on.

Check password during initialisation

Currently, if you are using a user-provided password you don't know if the password is correct until you try to decrypt some value and you get an exception. It would be nice to know it when you are getting the Armadillo instance.

We could store some dummy value and try to decrypt it during the initialisation to check that the password is correct. What do you think?

Logo Proposal For armadillo

Hello Sir. I'm a UI/UX and Graphics Designer.
I want propose a logo for your github project.
Would you mind if I propose logo for your application as my Open Source Contribution?

Thanks before.
Regards,
@iqbalhood

Password is not being used for derivating encryption key

The user provided password is supposed to be used to derivate the encryption key. However, it seems that currently is not being used.

How to reproduce:

  1. Instantiate Armadillo with password A.
  2. Save some data
  3. Instantiate Armadillo with password B (without deleting data).
  4. Try to retrieved data stored with password A.
    -> You are able to retrieve the plain data

Change password feature does not support empty passwords

Currently, it's not possible to change from a "empty" password to a user define password.

Stacktrace password null:

2018-07-18 11:20:21.333 19121-19121/at.favre.lib.securesharedpreferences E/AndroidRuntime: FATAL EXCEPTION: main
    Process: at.favre.lib.securesharedpreferences, PID: 19121
    java.lang.IllegalStateException: Could not execute method for android:onClick
        at android.support.v7.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:389)
        at android.view.View.performClick(View.java:6294)
        at android.view.View$PerformClick.run(View.java:24770)
        at android.os.Handler.handleCallback(Handler.java:790)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:164)
        at android.app.ActivityThread.main(ActivityThread.java:6494)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)
     Caused by: java.lang.reflect.InvocationTargetException
        at java.lang.reflect.Method.invoke(Native Method)
        at android.support.v7.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:384)
        at android.view.View.performClick(View.java:6294) 
        at android.view.View$PerformClick.run(View.java:24770) 
        at android.os.Handler.handleCallback(Handler.java:790) 
        at android.os.Handler.dispatchMessage(Handler.java:99) 
        at android.os.Looper.loop(Looper.java:164) 
        at android.app.ActivityThread.main(ActivityThread.java:6494) 
        at java.lang.reflect.Method.invoke(Native Method) 
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807) 
     Caused by: java.lang.NullPointerException: Attempt to get length of null array
        at java.util.Arrays.fill(Arrays.java:2879)
        at at.favre.lib.armadillo.SecureSharedPreferences.changePassword(SecureSharedPreferences.java:252)
        at at.favre.lib.armadillo.SecureSharedPreferences.changePassword(SecureSharedPreferences.java:230)
        at at.favre.lib.securesharedpreferences.ChangePasswordActivity.onChangePasswordClicked(ChangePasswordActivity.java:60)

Stacktrace password empty:

2018-07-18 11:16:52.676 18954-18954/at.favre.lib.securesharedpreferences E/AndroidRuntime: FATAL EXCEPTION: main
    Process: at.favre.lib.securesharedpreferences, PID: 18954
    java.lang.IllegalStateException: Could not execute method for android:onClick
        at android.support.v7.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:389)
        at android.view.View.performClick(View.java:6294)
        at android.view.View$PerformClick.run(View.java:24770)
        at android.os.Handler.handleCallback(Handler.java:790)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:164)
        at android.app.ActivityThread.main(ActivityThread.java:6494)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)
     Caused by: java.lang.reflect.InvocationTargetException
        at java.lang.reflect.Method.invoke(Native Method)
        at android.support.v7.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:384)
        at android.view.View.performClick(View.java:6294) 
        at android.view.View$PerformClick.run(View.java:24770) 
        at android.os.Handler.handleCallback(Handler.java:790) 
        at android.os.Handler.dispatchMessage(Handler.java:99) 
        at android.os.Looper.loop(Looper.java:164) 
        at android.app.ActivityThread.main(ActivityThread.java:6494) 
        at java.lang.reflect.Method.invoke(Native Method) 
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807) 
     Caused by: java.lang.IllegalStateException: could not stretch with bcrypt
        at at.favre.lib.armadillo.FixedBcryptKeyStretcher.stretch(FixedBcryptKeyStretcher.java:59)
        at at.favre.lib.armadillo.DefaultEncryptionProtocol.keyDerivationFunction(DefaultEncryptionProtocol.java:178)
        at at.favre.lib.armadillo.DefaultEncryptionProtocol.decrypt(DefaultEncryptionProtocol.java:151)
        at at.favre.lib.armadillo.SecureSharedPreferences.decrypt(SecureSharedPreferences.java:463)
        at at.favre.lib.armadillo.SecureSharedPreferences.reencryptStringType(SecureSharedPreferences.java:273)
        at at.favre.lib.armadillo.SecureSharedPreferences.changePassword(SecureSharedPreferences.java:242)
        at at.favre.lib.armadillo.SecureSharedPreferences.changePassword(SecureSharedPreferences.java:230)
        at at.favre.lib.securesharedpreferences.ChangePasswordActivity.onChangePasswordClicked(ChangePasswordActivity.java:54)
        at java.lang.reflect.Method.invoke(Native Method) 
        at android.support.v7.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:384) 
        at android.view.View.performClick(View.java:6294) 
        at android.view.View$PerformClick.run(View.java:24770) 
        at android.os.Handler.handleCallback(Handler.java:790) 
        at android.os.Handler.dispatchMessage(Handler.java:99) 
        at android.os.Looper.loop(Looper.java:164) 
        at android.app.ActivityThread.main(ActivityThread.java:6494) 
        at java.lang.reflect.Method.invoke(Native Method) 
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807) 
     Caused by: java.lang.IllegalArgumentException: provided pseudoRandomKey must be at least of size 1 and not null
        at at.favre.lib.crypto.HKDF$Expander.execute(HKDF.java:230)
        at at.favre.lib.crypto.HKDF.expand(HKDF.java:151)
        at at.favre.lib.armadillo.FixedBcryptKeyStretcher.bcrypt(FixedBcryptKeyStretcher.java:78)
        at at.favre.lib.armadillo.FixedBcryptKeyStretcher.stretch(FixedBcryptKeyStretcher.java:57)

NullPointerException when closing armadillo initialised without user password

If you initialised Armadillo without a user password and then you try to close it a new NullPointerException is thrown.

Stacktrace:

2018-07-18 11:00:22.260 18226-18226/at.favre.lib.securesharedpreferences E/AndroidRuntime: FATAL EXCEPTION: main
    Process: at.favre.lib.securesharedpreferences, PID: 18226
    java.lang.IllegalStateException: Could not execute method for android:onClick
        at android.support.v7.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:389)
        at android.view.View.performClick(View.java:6294)
        at android.view.View$PerformClick.run(View.java:24770)
        at android.os.Handler.handleCallback(Handler.java:790)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:164)
        at android.app.ActivityThread.main(ActivityThread.java:6494)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)
     Caused by: java.lang.reflect.InvocationTargetException
        at java.lang.reflect.Method.invoke(Native Method)
        at android.support.v7.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:384)
        at android.view.View.performClick(View.java:6294) 
        at android.view.View$PerformClick.run(View.java:24770) 
        at android.os.Handler.handleCallback(Handler.java:790) 
        at android.os.Handler.dispatchMessage(Handler.java:99) 
        at android.os.Looper.loop(Looper.java:164) 
        at android.app.ActivityThread.main(ActivityThread.java:6494) 
        at java.lang.reflect.Method.invoke(Native Method) 
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807) 
     Caused by: java.lang.NullPointerException: Attempt to get length of null array
        at java.util.Arrays.fill(Arrays.java:2879)
        at at.favre.lib.armadillo.SecureSharedPreferences.close(SecureSharedPreferences.java:328)
        at at.favre.lib.securesharedpreferences.MainActivity.onCloseArmadilloClicked(MainActivity.java:90)

Library crashing in devices running KitKat

According to the Readme:

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.

But if you look into the build.gradle file, it declares:

minSdkVersion = 21

Are the docs not up to date? Or it is still safe to use the library with minSdkVersion = 19?

Thanks.

Avoid keeping cleartext password char[] in memory

Currently the sharedPreference implementation keeps the char[] array of the user password in memory until its closed. This makes it quite easy to read the cleartext password when instrumenting the device and reading the current memory (e.g. with Frida). There is a datastructure which obfuscates the content of a field, so it can be used until it is actually used.

It WOULD be way better to derive a byte array directly when passing the password (using a hash function or similar), then use this byte array when deriving the main key (so the clear pw is never in memory again), but that would entail making non-backwards compatible changes.

Library could not encrypt error on Android 9.0 with "BC" security provider

When running the following code to encrypt our SharedPreferences:

return Armadillo.create(context, prefsName) .encryptionFingerprint(context) .securityProvider(Security.getProvider("BC")) .keyStretchingFunction(new PBKDF2KeyStretcher()) .enableKitKatSupport(true) .build();

I get the following issues on Android 9.0+

Caused by: java.lang.IllegalStateException: at.favre.lib.armadillo.EncryptionProtocolException: at.favre.lib.armadillo.AuthenticatedEncryptionException: could not encrypt at at.favre.lib.armadillo.SecureSharedPreferences.encryptToBase64(SecureSharedPreferences.java:511) at at.favre.lib.armadillo.SecureSharedPreferences.access$400(SecureSharedPreferences.java:33) at at.favre.lib.armadillo.SecureSharedPreferences$Editor.putString(SecureSharedPreferences.java:412)

I know Android did some changes to cryptography in 9.0, I'm not sure if they are related or I've just missed a setting here:
https://android-developers.googleblog.com/2018/03/cryptography-changes-in-android-p.html

putString() cannot take null as a default value

Got this error:

java.lang.NullPointerException: provided string must not be null
 at java.util.Objects.requireNonNull(Objects.java:228)
 at at.favre.lib.bytes.Bytes.from(Bytes.java:432)
 at at.favre.lib.bytes.Bytes.from(Bytes.java:410)
  at at.favre.lib.armadillo.SecureSharedPreferences$Editor.putString(SecureSharedPreferences.java:241)

java.lang.NullPointerException

It's some problems with private static String getAndroidId(Context context) on some Oreo and Pie devices: ONEPLUS A6010 (9), Pixel 2 XL (10), Mi A1 (9) (from Crashlytics)

java.lang.NullPointerException: provided string must not be null
       at java.util.Objects.requireNonNull + 228(Objects.java:228)
       at at.favre.lib.bytes.Bytes.from + 502(Bytes.java:502)
       at at.favre.lib.bytes.Bytes.from + 480(Bytes.java:480)
       at at.favre.lib.armadillo.EncryptionFingerprintFactory.create + 49(EncryptionFingerprintFactory.java:49)
       at at.favre.lib.armadillo.Armadillo$Builder.encryptionFingerprint + 159(Armadillo.java:159)
       at at.favre.lib.armadillo.Armadillo$Builder.encryptionFingerprint + 111(Armadillo.java:111)

Please, fix it, if possible.

aar doesnt pull in required dependencies

armadillo depends on bytes and hkdf libraries. It would be nice if there were specified as transitive dependencies in the pom and pulled in automatically.

  implementation "at.favre.lib:bytes:0.4.6"
  implementation "at.favre.lib:hkdf:1.0.0"

Cheers

NegativeArraySizeException when getting value with parallel access

Any idea what could cause this exception to be raised?

Caused by java.lang.NegativeArraySizeException: -16
       at at.favre.lib.armadillo.AesGcmEncryption.decrypt(SourceFile:99)
       at at.favre.lib.armadillo.DefaultEncryptionProtocol.decrypt(SourceFile:152)
       at at.favre.lib.armadillo.SecureSharedPreferences.decrypt(SourceFile:518)
       at at.favre.lib.armadillo.SecureSharedPreferences.getString(SourceFile:149)

Improve RecoveryPolicy

When a value cannot be decrypted, the recovery policy currently only lets you delete and/or throw an exception.

The policy should be extended so a developer can, in more fine-grain detail choose what to do. This should not be a full blown migration feature (which is more a part of #31 ). But give the developer full control on what to do (e.g. call a crash reporting tool, etc.)

Bcrypt Key Stretcher used incorrectly

return Bytes.from(BCrypt.hashpw(String.valueOf(password) + Bytes.wrap(salt).encodeHex(), generateSalt(salt, logRounds))).array();

Unfortunately the jBcrypt API was used totally incorrectly (to be fair, the API does not have any fail safes to warn the user):

  1. The password is encoded with hex and the salt is appended:
    String.valueOf(password) + Bytes.wrap(salt).encodeHex()
    First of all, I dont't know why I appended the salt. Second, bcrypt only supports 72 bytes of PW by encoding the UTF-8 byte array as hex the PW length was additionally shortened and maybe results in premature truncated passwords. So the actually maximum length is 72/2 = 36 bytes (which is probably long enough for most practically used PW, but still bad)
  2. The salt was generated like this:
    saltBuilder.append(Bytes.wrap(HKDF.fromHmacSha256().expand(salt, "bcrypt".getBytes(), 16)).encodeHex());
    Due to the 'creative' way the jBcrypt API generates a salt, I implemented my own salt method to be able to pass a custom salt, but did it incorrectly. The salt SHOULD be 16 bytes encoded as Bcrypt Radix64 (= 22 bytes), but the 16 bytes where encoded to 32 characters hex. The jBcrypt impl then cuts 10 of those characters cutting 5 bytes of entropy making it a 11 byte hash

All this was discovered while trying to update the bcrypt impl with mine https://github.com/patrickfav/bcrypt.

Going forward:

  • I will replace the current Bcrypt stretcher with the new, correctly implemented one, that means that old encrypted preferences cannot be read anymore
  • The old bcrypt impl will still be in the code base, but deprecated, the data is still accessible
  • I will change the "change password" feature to also accept a new Keystrecht impl, so this issue can be migrated

GzipCompressor possible resource leaks

Hey, you are not using the try-catch correctly in the compress and decompress method. You should close the streams in a finally block, so they are closed even if there is an exception raised. The stream null check is not required for the compress method.

Example of a correct decompress method for the GzipCompressor class (the gzipInputStream != null should not happen, but theoretically new byte[2048] can throw an OutOfMemoryException before the gzipInputStream instance is created):

    @Override
    public byte[] decompress(byte[] compressed) {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        GZIPInputStream gzipInputStream = null;
        try {
            int len;
            byte[] buffer = new byte[2048];
            gzipInputStream = new GZIPInputStream(new ByteArrayInputStream(compressed));

            while ((len = gzipInputStream.read(buffer)) > 0) {
                bos.write(buffer, 0, len);
            }

            return bos.toByteArray();
        } catch (Exception e) {
            throw new IllegalStateException("could not decompress gzip", e);
        } finally {
            try {
                bos.close();
            } catch (IOException ignore) { }

            if (gzipInputStream != null) {
                try {
                    gzipInputStream.close();
                } catch (IOException ignore) { }
            }
        }
    }

I would prefer try-with-resources with auto-close, but to support older Java versions and android, this should do it.

EncryptionProtocolException: at.favre.lib.armadillo.AuthenticatedEncryptionException: could not encrypt

SharedPreferences preferences = Armadillo.create(context, prefName)
.password(Keys.UserProvidedPassword.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(getUniqueDeviceId(context)).array()) //use custom content key digest salt
.secureRandom(new SecureRandom()) //provide your own secure random for salt/iv generation
.encryptionFingerprint(context, (Keys.EncryptionFingerprint).getBytes(StandardCharsets.UTF_8)) //add the user id to fingerprint
.supportVerifyPassword(true) //enables optional password validation support .isValidPassword()
.enableDerivedPasswordCache(true) //enable caching for derived password making consecutive getters faster
.build();

Make CI Config Pull Request friendly

Currently the CI job fails because a PR does not have the secret env vars to decrypt the release keystore (which is good :)).

Create a config that only runs on PR CI jobs which only builds debug.

Use Android's Log instead of Timber

This issue is just a proposal.

Depending on Timber has some drawbacks:

  1. if an app doesn't use Timber, lint raises a warning for each Android's Log call by default since Timber implemented the lint check LogNotTimber
  2. it increases the APK size

The first issue is way more important than the second one.
Also because, if someone enables isWarningsAsErrors in the lintOptions, every Log's call is marked as an error.

Is it possible to depend on Android's Log instead of Timber?

Add derived password cache to speed up consecutive .get() calls

To make get calls faster, a cache, which caches the derived password should be implemented. This will not speed up put* operations since every time a new salt will be created making it impossible to cache.

The disadvantage is that the derived password stays in cache, therefor in memory for way longer, making it easier to read when the device is used with instrumentation tool like FRIDA (this is a more specific attack, since when the attacker has full access to the device, there is not much you can do).

Support changing password

It would be nice to be able to change the user-provided password easily.

This means that all the data has to be decrypted with the old password and re-encrypted with the new one.

It can be handy when you want to switch from a default password to a user-provided password. And for later when the user wants to change it.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.