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)
D4rWiNS
  • 2,585
  • 5
  • 32
  • 54
  • 1
    I faced the same issue earlier. Changing encryption mode then solved the issue. – Abhishek Agarwal Dec 22 '16 at 10:30
  • me too having same issue, is there any option other than changing encryption mode ? – Bincy Baby Dec 22 '16 at 10:53
  • one of my colleague also had the same issue – M14 Dec 22 '16 at 11:04
  • 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 it – D4rWiNS Dec 22 '16 at 15:04
  • allowBackup is false in my case – Bincy Baby Dec 22 '16 at 19:56
  • what is the value of `UTF8`?? – petey Dec 28 '16 at 15:36
  • @petey "utf-8", the code is from this answer: stackoverflow.com/a/6393502/1445119. 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. – P1nGu1n Dec 29 '16 at 09:42
  • Hi @D4rWiNS did find a solution to your issue? – Kailash Chivhe Jun 12 '20 at 16:07
  • 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 false – D4rWiNS Jun 17 '20 at 11:58

2 Answers2

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?

Community
  • 1
  • 1
Gary99
  • 1,750
  • 1
  • 19
  • 33
  • I will try changing to a different default value, however the problem is that instead of return the null value, it return a random string – D4rWiNS Dec 27 '16 at 08:56
  • 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 trick – D4rWiNS Dec 30 '16 at 08:47
  • 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? – Gary99 Dec 30 '16 at 14:49
  • Upgrade the Game 2 is the name of my app, api of my nexus is 23 – D4rWiNS Jan 03 '17 at 12:07
  • 1
    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". – Gary99 Jan 03 '17 at 14:47
  • Thanks anyway @Gary99 , It seems to be in a very specific devices with an special condition, You was right on the typo – D4rWiNS Jan 04 '17 at 08:07
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/

Simon J. Liu
  • 787
  • 4
  • 11
  • 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 before – D4rWiNS Dec 27 '16 at 08:51
  • I have log of the crash detailed, I attach it – D4rWiNS Dec 27 '16 at 08:58
  • 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. – juanlugm Dec 27 '16 at 10:14
  • 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 exploit – D4rWiNS Dec 28 '16 at 08:37