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

Java端AES加密结果稳定,Android端结果各异的Android侧解决方案咨询

Android端适配Java AES加密结果一致的解决方案

问题背景

我有一段Java端的AES加密工具类,在Java环境中每次加密相同内容和密码得到的结果完全一致,但移植到Android端后,每次加密结果都不同。由于Java端代码涉及其他应用无法修改,需要仅在Android端调整以匹配Java端的加密行为。

Java端原始代码:

import javax.crypto.*;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;

/*
 * AES对称加密和解密
 */
public class AESUtil {
    public static byte[] encrypt(String content, String password) {
        try {
            KeyGenerator kgen = KeyGenerator.getInstance( "AES" );
            SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG" );
            secureRandom.setSeed(password.getBytes());
            kgen.init(128,secureRandom);
            SecretKey secretKey = kgen.generateKey();
            byte[] enCodeFormat = secretKey.getEncoded();
            SecretKeySpec key = new SecretKeySpec(enCodeFormat, "AES");
            Cipher cipher = Cipher.getInstance("AES");
            byte[] byteContent = content.getBytes("utf-8");
            cipher.init(Cipher.ENCRYPT_MODE, key);
            byte[] result = cipher.doFinal(byteContent);
            return result;

        } catch (NoSuchPaddingException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        } catch (IllegalBlockSizeException e) {
            e.printStackTrace();
        } catch (BadPaddingException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 解密AES加密过的字符串
     *
     * @param content
     *            AES加密过的内容
     * @param password
     *            加密时的密码
     * @return 明文
     */
    public static byte[] decrypt(byte[] content, String password) {
        try {
            KeyGenerator kgen = KeyGenerator.getInstance( "AES" );
            SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG" );
            secureRandom.setSeed(password.getBytes());
            kgen.init(128,secureRandom);
            SecretKey secretKey = kgen.generateKey();
            byte[] enCodeFormat = secretKey.getEncoded();
            SecretKeySpec key = new SecretKeySpec(enCodeFormat, "AES");
            Cipher cipher = Cipher.getInstance("AES");
            cipher.init(Cipher.DECRYPT_MODE, key);
            byte[] result = cipher.doFinal(content);
            return result;

        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (NoSuchPaddingException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        } catch (IllegalBlockSizeException e) {
            e.printStackTrace();
        } catch (BadPaddingException e) {
            e.printStackTrace();
        }
        return null;
    }
    /**将二进制转换成16进制
     * @param buf
     * @return
     */
    public static String parseByte2HexStr(byte buf[]) {
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < buf.length; i++) {
            String hex = Integer.toHexString(buf[i] & 0xFF);
            if (hex.length() == 1) {
                hex = '0' + hex;
            }
            sb.append(hex.toUpperCase());
        }
        return sb.toString();
    }

    /**将16进制转换为二进制
     * @param hexStr
     * @return
     */
    public static byte[] parseHexStr2Byte(String hexStr) {
        if (hexStr.length() < 1) {
            return null;
        }
        byte[] result = new byte[hexStr.length()/2];
        for (int i = 0;i< hexStr.length()/2; i++) {
            int high = Integer.parseInt(hexStr.substring(i*2, i*2+1), 16);
            int low = Integer.parseInt(hexStr.substring(i*2+1, i*2+2), 16);
            result[i] = (byte) (high * 16 + low);
        }
        return result;
    }

    public static String AesEncryption(String data, String password){
        byte[] shellEncrypt = encrypt(data, password);
        return parseByte2HexStr(shellEncrypt);
    }
    public static String AesDecrypt(String data, String password){
        byte[] twoStrResult = parseHexStr2Byte(data);
        byte[] decrypt = decrypt(twoStrResult, password);
        return new String(decrypt);
    }


    public static void main(String[] args) throws Exception {

        String password = "27c8fa1a46baabda88d2b977de272c1f";
        String s = AesEncryption("sdasdasd", password);

        System.out.println(s);

    }

}

问题原因

核心差异在于SHA1PRNG的跨平台实现:

  • 标准JDK中,SecureRandom.getInstance("SHA1PRNG")调用setSeed(password.getBytes())后,会完全基于传入的种子生成随机序列,因此每次生成的AES密钥固定,加密结果一致。
  • Android 7.0+平台中,SHA1PRNG的实现被修改,即使手动设置种子,底层仍会混合系统随机熵源,导致每次生成的密钥不同,最终加密结果不一致。

解决方案

修改Android端的密钥生成逻辑,强制对齐Java端的行为,以下是适配后的代码:

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;

public class AESUtil {
    // 显式指定AES模式和填充,避免跨平台默认值差异
    private static final String AES_ALGORITHM = "AES/ECB/PKCS5Padding";

    public static byte[] encrypt(String content, String password) {
        try {
            byte[] keyBytes = generateJavaCompatibleKey(password.getBytes());
            SecretKeySpec key = new SecretKeySpec(keyBytes, "AES");
            Cipher cipher = Cipher.getInstance(AES_ALGORITHM);
            byte[] byteContent = content.getBytes("utf-8");
            cipher.init(Cipher.ENCRYPT_MODE, key);
            return cipher.doFinal(byteContent);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public static byte[] decrypt(byte[] content, String password) {
        try {
            byte[] keyBytes = generateJavaCompatibleKey(password.getBytes());
            SecretKeySpec key = new SecretKeySpec(keyBytes, "AES");
            Cipher cipher = Cipher.getInstance(AES_ALGORITHM);
            cipher.init(Cipher.DECRYPT_MODE, key);
            return cipher.doFinal(content);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 生成与Java端SHA1PRNG逻辑一致的AES密钥
     */
    private static byte[] generateJavaCompatibleKey(byte[] seed) throws NoSuchAlgorithmException {
        SecureRandom sr = SecureRandom.getInstance("SHA1PRNG");
        // 优先通过反射强制重置随机源(适配Android平台)
        try {
            java.lang.reflect.Method method = sr.getClass().getDeclaredMethod("setSeedInternal", byte[].class);
            method.setAccessible(true);
            method.invoke(sr, seed);
        } catch (Exception e) {
            // 反射失败时,退化为SHA1哈希取前16字节(AES-128密钥长度)
            MessageDigest md = MessageDigest.getInstance("SHA-1");
            byte[] digest = md.digest(seed);
            byte[] key = new byte[16];
            System.arraycopy(digest, 0, key, 0, 16);
            return key;
        }
        byte[] keyBytes = new byte[16];
        sr.nextBytes(keyBytes);
        return keyBytes;
    }

    // 以下辅助方法与Java端保持一致
    public static String parseByte2HexStr(byte buf[]) {
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < buf.length; i++) {
            String hex = Integer.toHexString(buf[i] & 0xFF);
            if (hex.length() == 1) {
                hex = '0' + hex;
            }
            sb.append(hex.toUpperCase());
        }
        return sb.toString();
    }

    public static byte[] parseHexStr2Byte(String hexStr) {
        if (hexStr.length() < 1) {
            return null;
        }
        byte[] result = new byte[hexStr.length()/2];
        for (int i = 0;i< hexStr.length()/2; i++) {
            int high = Integer.parseInt(hexStr.substring(i*2, i*2+1), 16);
            int low = Integer.parseInt(hexStr.substring(i*2+1, i*2+2), 16);
            result[i] = (byte) (high * 16 + low);
        }
        return result;
    }

    public static String AesEncryption(String data, String password){
        byte[] shellEncrypt = encrypt(data, password);
        return parseByte2HexStr(shellEncrypt);
    }
    public static String AesDecrypt(String data, String password){
        byte[] twoStrResult = parseHexStr2Byte(data);
        byte[] decrypt = decrypt(twoStrResult, password);
        try {
            return new String(decrypt, "utf-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
            return new String(decrypt);
        }
    }
}

关键调整说明

  1. 显式指定加密参数:将Cipher.getInstance("AES")改为Cipher.getInstance("AES/ECB/PKCS5Padding"),避免跨平台默认模式/填充的差异。
  2. 对齐密钥生成逻辑:通过反射调用Android平台SecureRandom的setSeedInternal方法,强制重置随机源;反射失败时,用SHA1哈希密码取前16字节作为密钥,确保和Java端生成的密钥一致。
  3. 保持辅助方法不变:二进制与十六进制转换、加密解密入口方法完全复用Java端逻辑,确保流程一致。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 09:24:54