You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

三星SM-G950F(Android8.0)上Obscured SharedPreference报BadPaddingException求助

解决三星SM-G950F(Android 8.0)上PBE加解密的BadPaddingException问题

你遇到的是典型的厂商定制ROM加密实现差异导致的padding错误,只修改异常返回值确实只是治标不治本。我来帮你拆解问题并给出可行的解决方案:

问题根源分析

你的代码在大部分设备正常,但在这款三星老设备上崩溃,主要可能有这几个原因:

  • Base64模式不统一:加密用Base64.NO_WRAP,解密却用Base64.DEFAULT,后者会自动处理换行符,但如果加密后的字符串因存储或传输引入了换行,就会导致解码后的字节数组和加密前不一致,触发padding错误。
  • Android ID的不确定性:三星部分老设备的Android ID可能为空、长度异常,或者编码处理有差异,导致加密和解密时使用的盐值不一致,直接引发解密失败。
  • 老旧算法的厂商实现差异:PBEWithMD5AndDES是非常老旧的加密算法,安全性低,且不同厂商的ROM对其实现细节(比如padding处理)可能存在差异,Android 8.0的三星定制ROM刚好踩中了这个坑。

分步解决方案

1. 先统一Base64编码/解码模式

这是最容易修复的点,把解密方法中的Base64解码模式改成和加密一致的NO_WRAP:

// 原解密代码中的这一行
final byte[] bytes = value != null ? Base64.decode(value, Base64.DEFAULT) : new byte[0];
// 修改为
final byte[] bytes = value != null ? Base64.decode(value, Base64.NO_WRAP) : new byte[0];

2. 确保盐值稳定且符合要求

Android ID并非绝对可靠(可能为空、变化),我们需要将其转换为固定长度的稳定盐值,避免因盐值异常导致的参数错误:

private byte[] getStableSalt() {
    String androidId = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID);
    
    // 兜底:如果Android ID为空,用默认盐值
    if (androidId == null || androidId.trim().isEmpty()) {
        return "my_secure_default_salt".getBytes(StandardCharsets.UTF_8);
    }
    
    // 将Android ID哈希为固定8字节的盐(适配DES的块大小)
    try {
        MessageDigest md = MessageDigest.getInstance("MD5");
        byte[] hashBytes = md.digest(androidId.getBytes(StandardCharsets.UTF_8));
        byte[] salt = new byte[8];
        System.arraycopy(hashBytes, 0, salt, 0, 8);
        return salt;
    } catch (NoSuchAlgorithmException e) {
        // 哈希失败时仍用默认盐
        return "my_secure_default_salt".getBytes(StandardCharsets.UTF_8);
    }
}

然后在加密和解密方法中替换盐的获取逻辑:

// 加密时
pbeCipher.init(Cipher.ENCRYPT_MODE, key, new PBEParameterSpec(getStableSalt(), 20));
// 解密时
pbeCipher.init(Cipher.DECRYPT_MODE, key, new PBEParameterSpec(getStableSalt(), 20));

3. 替换为更可靠的现代加密算法

PBEWithMD5AndDES已经被淘汰,建议换成Android系统支持更好、安全性更高的PBEWithHmacSHA256AndAES_256,这个算法在Android 8.0及以上版本原生支持,且厂商实现差异极小。

修改后的完整加解密代码示例:

import android.content.Context;
import android.provider.Settings;
import android.util.Base64;
import android.util.Log;

import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;

public class SecureEncryptor {
    private static final String TAG = "SecureEncryptor";
    private static final String ENCRYPTION_ALGORITHM = "PBEWithHmacSHA256AndAES_256";
    private static final int ITERATION_COUNT = 65536; // 更高的迭代次数提升安全性
    private static final int KEY_LENGTH = 256;
    private final Context context;
    private final String SEKRIT;

    public SecureEncryptor(Context context, String secretKey) {
        this.context = context;
        this.SEKRIT = secretKey;
    }

    private byte[] getStableSalt() {
        String androidId = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID);
        
        if (androidId == null || androidId.trim().isEmpty()) {
            return "my_secure_default_salt".getBytes(StandardCharsets.UTF_8);
        }
        
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            byte[] hashBytes = md.digest(androidId.getBytes(StandardCharsets.UTF_8));
            byte[] salt = new byte[8];
            System.arraycopy(hashBytes, 0, salt, 0, 8);
            return salt;
        } catch (NoSuchAlgorithmException e) {
            Log.e(TAG, "Failed to generate salt from Android ID", e);
            return "my_secure_default_salt".getBytes(StandardCharsets.UTF_8);
        }
    }

    public String encrypt(String value) {
        if (value == null || value.isEmpty()) {
            return value;
        }
        
        try {
            byte[] inputBytes = value.getBytes(StandardCharsets.UTF_8);
            byte[] salt = getStableSalt();
            
            PBEKeySpec keySpec = new PBEKeySpec(SEKRIT.toCharArray(), salt, ITERATION_COUNT, KEY_LENGTH);
            SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(ENCRYPTION_ALGORITHM);
            SecretKey secretKey = keyFactory.generateSecret(keySpec);
            
            Cipher cipher = Cipher.getInstance(ENCRYPTION_ALGORITHM);
            cipher.init(Cipher.ENCRYPT_MODE, secretKey);
            
            byte[] encryptedBytes = cipher.doFinal(inputBytes);
            return Base64.encodeToString(encryptedBytes, Base64.NO_WRAP);
        } catch (Exception e) {
            Log.e(TAG, "Encryption failed", e);
            return value;
        }
    }

    public String decrypt(String value) {
        if (value == null || value.isEmpty()) {
            return value;
        }
        
        try {
            byte[] inputBytes = Base64.decode(value, Base64.NO_WRAP);
            byte[] salt = getStableSalt();
            
            PBEKeySpec keySpec = new PBEKeySpec(SEKRIT.toCharArray(), salt, ITERATION_COUNT, KEY_LENGTH);
            SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(ENCRYPTION_ALGORITHM);
            SecretKey secretKey = keyFactory.generateSecret(keySpec);
            
            Cipher cipher = Cipher.getInstance(ENCRYPTION_ALGORITHM);
            cipher.init(Cipher.DECRYPT_MODE, secretKey);
            
            byte[] decryptedBytes = cipher.doFinal(inputBytes);
            return new String(decryptedBytes, StandardCharsets.UTF_8);
        } catch (Exception e) {
            Log.e(TAG, "Decryption failed", e);
            return value;
        }
    }
}

额外建议

  • 不要用自定义的UTF8常量,直接用StandardCharsets.UTF_8,避免编码不一致。
  • 异常处理中添加日志,方便后续排查问题,不要只静默返回原值。
  • 如果需要兼容更早的Android版本(比如低于8.0),可以考虑引入SpongyCastle作为加密Provider,但如果只针对8.0+,系统原生的Provider足够稳定。

内容的提问来源于stack exchange,提问作者Metin Ilhan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 06:46:49