Shared preferences? javax.crypto.BadPaddingException: pad block corrupted only in some devices
Asked Answered
C

2

6

I am getting some errors from google play console where some users ( Pixel XL, nexus 5 and Xperia Z3+) are getting

Caused by: java.lang.RuntimeException: javax.crypto.BadPaddingException: pad block corrupted
at com.darwins.custom.ObscuredSharedPreferences.decrypt(ObscuredSharedPreferences.java:193)
at com.darwins.custom.ObscuredSharedPreferences.getInt(ObscuredSharedPreferences.java:134)

The app is working fine in the rest of devices ( even in some nexus 5 is working fine)

The problem come when the first time that the user open the app, It try to load the music volume from shared preferences. As they never entered in the options menu to change the default value, It should get the default value:

if(sp      == null) sp = new ObscuredSharedPreferences(ctx, ctx.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE));
if(musicVolume == -1) musicVolume = sp.getInt(KEY_SP_MUSIC_VOLUME,10);

If we enter in getInt from ObsucredSharedPreferences:

@Override
public int getInt(String key, int defValue) {
    final String v = delegate.getString(key, null);
    return v!=null ? Integer.parseInt(decrypt(v)) : defValue;
}

So Instead of getting the null value from getString I am getting a value like "ERKJFER89er" (I never write that value in the preferences, otherwise it should crash on every phone) so when It try to decryp the value it expect an int value and it throws a javax.crypto.BadPaddingException: pad block corrupted I don't know how to workaround this or how to fix this, any idea will be apreciate

Code of decrypt:

protected String decrypt(String value){
    try {
        final byte[] bytes = value!=null ? Base64.decode(value,Base64.DEFAULT) : new byte[0];
        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithMD5AndDES");
        SecretKey key = keyFactory.generateSecret(new PBEKeySpec(SEKRIT));
        Cipher pbeCipher = Cipher.getInstance("PBEWithMD5AndDES");
        pbeCipher.init(Cipher.DECRYPT_MODE, key, new PBEParameterSpec( Secure.getString(context.getContentResolver(), Secure.ANDROID_ID).getBytes(UTF8), 20));
        return new String(pbeCipher.doFinal(bytes),UTF8);

    } catch( Exception e) {
        throw new RuntimeException(e);
    }
}

1 user say that do a factory reset doesn't solve the problem but do a factory reset with wipe cache and wipe data solve it

Full stack strace for google pixel

java.lang.RuntimeException: Unable to start activity ComponentInfo{com.suduck.upgradethegame/com.darwins.cubegame.WelcomeActivity}: java.lang.RuntimeException: javax.crypto.BadPaddingException: pad block corrupted
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2665)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2726)
at android.app.ActivityThread.-wrap12(ActivityThread.java)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1477)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6119)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776)
Caused by: java.lang.RuntimeException: javax.crypto.BadPaddingException: pad block corrupted
at com.darwins.custom.ObscuredSharedPreferences.decrypt(ObscuredSharedPreferences.java:193)
at com.darwins.custom.ObscuredSharedPreferences.getInt(ObscuredSharedPreferences.java:134)
at com.darwins.clases.Logro.<init>(Logro.java:41)
at com.darwins.clases.LogrosManager.iniciar(LogrosManager.java:64)
at com.darwins.clases.LogrosManager.<init>(LogrosManager.java:48)
at com.darwins.motor.CEngine.Inicializar(CEngine.java:141)
at com.darwins.superclases.CActividad.onCreate(CActividad.java:47)
at com.darwins.cubegame.WelcomeActivity.onCreate(WelcomeActivity.java:32)
at android.app.Activity.performCreate(Activity.java:6679)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1118)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2618)
... 9 more
Caused by: javax.crypto.BadPaddingException: pad block corrupted
at com.android.org.bouncycastle.jcajce.provider.symmetric.util.BaseBlockCipher$BufferedGenericBlockCipher.doFinal(BaseBlockCipher.java:1267)
at com.android.org.bouncycastle.jcajce.provider.symmetric.util.BaseBlockCipher.engineDoFinal(BaseBlockCipher.java:1100)
at javax.crypto.Cipher.doFinal(Cipher.java:2056)
at com.darwins.custom.ObscuredSharedPreferences.decrypt(ObscuredSharedPreferences.java:190)
Calycle answered 25/10, 2016 at 10:14 Comment(9)
I faced the same issue earlier. Changing encryption mode then solved the issue.Stinson
me too having same issue, is there any option other than changing encryption mode ?Six
one of my colleague also had the same issueHorrorstruck
In my case I think that the problem is not the encryption, is shared preferences getting an string instead of an int, Did you guys have allowBackup = true in manifest? I do, And I am starting thinking that the problem come from itCalycle
allowBackup is false in my caseSix
what is the value of UTF8??Carpic
@Carpic "utf-8", the code is from this answer: https://mcmap.net/q/98778/-what-is-the-most-appropriate-way-to-store-user-settings-in-android-application. I'm facing the same issue, only occurs on a very few devices (about 0.05%). It occurs, for example, on the HTC One M8 and M9 according to Crashlytics. Remarkably, very few crashes on Samsung devices.Combings
Hi @Calycle did find a solution to your issue?Outgrowth
Not quite a solution, but the problem came with the restoration of the preferences on some devices, since the decrypt function changed. I recommend setting the allowBackups in the manifest to falseCalycle
L
2

I can't say this is much more than a guess but I'll give it a try.

I've seen others use a default of null for SharedPreferences but I like to use the actual default value as a string. Based on that, I would have something more like this (assuming there's an encryt() method to go with decrypt()).

@Override
public int getInt(String key, int defValue) {
    final String v = delegate.getString(key, encrypt(String.valueOf(defValue));
    return Integer.parseInt(decrypt(v));
}

If that doesn't fix the problem, have you tried using something other than null as the default value. How about an empty string? Some unicode character(s) that you're sure can't be a real encrypted stored value? EDIT: This is unlikely the problem. After looking at documentation, the defValue parameter for getString() is Nullable, meaning that the method is designed to handle a null parameter gracefully.

Hope this helps and good luck.

EDIT: I've tried to duplicate your problem but couldn't. I tried a Nexus 5 emulated device and a Nexus 5 real device, both using API 23. getString() always returned null. I used code based on this. I imagine it's more or less the same your code is based from.

Leaving out the unused methods...

public class ObscuredSharedPreferences implements SharedPreferences {

    private static final String TAG = "ObscuredSp";
    protected static final String UTF8 = "utf-8";
    private static final char[] SEKRIT = "abc".toCharArray() ; // INSERT A RANDOM PASSWORD HERE.

    protected SharedPreferences delegate;
    protected Context context;

    public ObscuredSharedPreferences(Context context, SharedPreferences delegate) {
        this.delegate = delegate;
        this.context = context;
    }

    @Override
    public int getInt(String key, int defValue) {
        final String v = delegate.getString(key, null);
        Log.d(TAG, "got int " + v);
        return v!=null ? Integer.parseInt(decrypt(v)) : defValue;
    }

    protected String decrypt(String value){
        try {
            final byte[] bytes = value!=null ? Base64.decode(value, Base64.DEFAULT) : new byte[0];
            SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithMD5AndDES");
            SecretKey key = keyFactory.generateSecret(new PBEKeySpec(SEKRIT));
            Cipher pbeCipher = Cipher.getInstance("PBEWithMD5AndDES");
            pbeCipher.init(Cipher.DECRYPT_MODE, key, new PBEParameterSpec(Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID).getBytes(UTF8), 20));
            return new String(pbeCipher.doFinal(bytes),UTF8);

        } catch( Exception e) {
            throw new RuntimeException(e);
        }
    }
}

In onCreate()...

private static final String MY_PREFS_FILE_NAME = "MyFile";
private static final String KEY_SP_MUSIC_VOLUME = "KeySpMusicVol";

final SharedPreferences prefs = new ObscuredSharedPreferences(
            this, this.getSharedPreferences(MY_PREFS_FILE_NAME, Context.MODE_PRIVATE));

int musicVolume;
musicVolume = prefs.getInt(KEY_SP_MUSIC_VOLUME, 10);
Log.d(TAG, "volume = " + musicVolume);

You could try simplifying your code to something like this. If this works and and yours doesn't, it's just a matter of adding & removing code until you determine what causes the problem. (I know it's not necessarily as easy as that makes it sound).

Other question, things to think about. Have your tried both debug and release build configs? Do you uninstall the app each time to make sure it's really the 1st time the app is run? Is volume the only preference that this happens with?

I've went back and re-read your question & I now notice that you write the problems are reported by Google Play. This makes the big question, can you duplicate this yourself such that you can try different things to determine the root cause?

Luminescence answered 25/12, 2016 at 14:14 Comment(6)
I will try changing to a different default value, however the problem is that instead of return the null value, it return a random stringCalycle
Most of the errors came from Google Play report or Firebase crash report, however, six month ago the same error was on my Nexus 5, I tried lot of things but only format with wipe cache do the trickCalycle
If you care to tell me the name of the app, I'll try to download it to see if it works on my Nexus 5. What API do you have on yours?Luminescence
Upgrade the Game 2 is the name of my app, api of my nexus is 23Calycle
I downloaded the game, opened it and cleared the 1st level without any problems. Unfortunately, it seems my Nexus 5 does not have the issue in your question. I'm not sure if I can be much help from here. Good luck. BTW, I think you have a typo in your app for "habitat".Luminescence
Thanks anyway @Luminescence , It seems to be in a very specific devices with an special condition, You was right on the typoCalycle
S
1

What's the return result of decrypt(null) ? It seems that your app read some incorrect data which was written incorrectly before.

Also I noticed that some device have different behavior on data folder path and probably caused this issue.

One possible solution for your issue is to log the detail crash context including the data that caused decryption error. You can try some online log service like Fabric or Logentries. Or you can implement your global ExceptionHandler, save the data and send the data to you when crash happened.

IMO, I prefer to save all the data in String and parse them at runtime in case of data format changing.

FYI. Here is my implementation for preference. It support plain/encoded/encrypted preference saved in SharedPreference. It also very easy to be extended to support online preferences.

https://github.com/passos/SimplePreferences/blob/master/library/src/main/java/com/ioenv/preferences/

Serous answered 23/12, 2016 at 8:43 Comment(4)
Yes, the data is incorrect , but the problem is that the app doesn't write the data before, This problem occur in some devices the FIRST time that the app try to read the preferences, instead of give the default value, It give a random String , But that String has been never write beforeCalycle
I have log of the crash detailed, I attach itCalycle
You can also give a try to detect if it is the first execution and then write the default preferences. It is not the best option but it should work.Hyehyena
I can't detect if it is the first execution since I can't use shared preferences, Is in the fuction of "isFirstExecution" where app exploitCalycle

© 2022 - 2024 — McMap. All rights reserved.