BadPaddingException when storing JWT in Flutter Secure Storage on Android
Describe the bug
After a successful login, writing the new JWT (and other keys) to flutter_secure_storage sometimes throws a BadPaddingException deep inside Android’s KeyStore/EncryptedSharedPreferences, causing:
PlatformException(Exception encountered, read, javax.crypto.BadPaddingException: error:le000065:Cipher functions:OPENSSL_internal:BAD_DECRYPT at com.android.org.conscrypt.NativeCrypto.EVP_CipherFinal_ex(Native Method) … ,null)
This makes all subsequent reads/writes to that key fail until the user clears app data.
To Reproduce
- Use
flutter_secure_storage(v9.2.4) withAndroidOptions(encryptedSharedPreferences: true). - Log in and store a JWT (via
secureStorage.write('jwt_token', token)). - Occasionally you’ll see the above exception when writing or reading the token.
- Clearing app data (Settings → Apps → YourApp → Storage → Clear data) “fixes” it once, but the issue recurs.
Minimal repro code snippet (in your login controller):
try {
await secureStorage.write("jwt_token", token);
} catch (e) {
// ← BadPaddingException here
debugPrint("Failed to store token: $e");
}
Even when trying to make a workaround it didnt help
Catch PlatformException on read/write, delete the bad key, then retry. E.g. in your SecureStorage wrapper:
@override
Future<void> write(String key, String value) async {
try {
await _secureStorage.write(key: key, value: value);
} on PlatformException catch (e) {
if (e.message?.contains('BadPaddingException') == true) {
await _secureStorage.delete(key: key);
await _secureStorage.write(key: key, value: value);
return;
}
rethrow;
}
}
Environment
Flutter 3.x
flutter_secure_storage: 9.2.4
Android emulator / device API 23+
encryptedSharedPreferences = true
Did you solve this?
Did you solve this?
Yes, but not through exception handling. For some reason, even if you try to catch and handle this particular exception, it doesn't work. After checking my code, I found that I was unintentionally overwriting a key with the same name. Interestingly, this issue doesn't occur with all keys. Without an official explanation, I can’t say for sure why, but for example: if I overwrite a key named "test", it doesn’t throw an exception it just updates the value. However, if I overwrite a key named "TEST", the exception is triggered. I'm glad I figured it out, but I don't have time to investigate further.
Getting the same exception on trying to read on some Android devices and I'm not sure what could be causing this. Using default configurations.
final FlutterSecureStorage _secureStorage = const FlutterSecureStorage();
@override
Future<Either<AppError, String?>> get(String key) async {
try {
String? value = await _secureStorage.read(key: key);
return Right(value);
} catch (e, st) {
Sentry.captureException(e, stackTrace: st);
return Left(
AppError(
title: 'Get item from Secure Storage Failed',
description: e.toString(),
),
);
}
}
@override
Future<Either<AppError, void>> set(String key, String? value) async {
try {
await _secureStorage.write(key: key, value: value);
return const Right(null);
} catch (e, st) {
Sentry.captureException(e, stackTrace: st);
return Left(
AppError(
title: 'Set item in Secure Storage Failed',
description: e.toString(),
),
);
}
}
Getting same exception on trying to read on some Android devices and I'm not sure what could be causing this. Using default configurations.
final FlutterSecureStorage _secureStorage = const FlutterSecureStorage();
@override Future<Either<AppError, String?>> get(String key) async { try { String? value = await _secureStorage.read(key: key); return Right(value); } catch (e, st) { Sentry.captureException(e, stackTrace: st); return Left( AppError( title: 'Get item from Secure Storage Failed', description: e.toString(), ), ); } }
@override Future<Either<AppError, void>> set(String key, String? value) async { try { await _secureStorage.write(key: key, value: value); return const Right(null); } catch (e, st) { Sentry.captureException(e, stackTrace: st); return Left( AppError( title: 'Set item in Secure Storage Failed', description: e.toString(), ), ); } }
You reminded me of something I tested it on a friend's Samsung A15 and it didn't trigger the exception, but on my Pixel 8 (physical device) it did.
In my case, it's working on Pixel 7 (both real device and emulator) and not working on itel rs4 (real device)
Any solution for the issue?
This is what resolved the issue for me -
If you get this exception when reading a value, assume it’s unreadable → delete the key.
Future<Either<AppError, String?>> get(String key) async {
try {
String? value = await _secureStorage.read(key: key);
return Right(value);
} catch (e, st) {
Sentry.captureException(e, stackTrace: st);
// If decrypt fails, wipe seucre storage.
final result = await deleteAll();
return result.fold(
(l) => Left(l),
(r) => Right(null),
);
}
}
Future<Either<AppError, void>> deleteAll() async {
try {
await _secureStorage.deleteAll();
return const Right(null);
} catch (e, st) {
Sentry.captureException(e, stackTrace: st);
return Left(
AppError(
title: 'Delete all items in Secure Storage Failed',
description: e.toString(),
),
);
}
}