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); } } }
关键调整说明
- 显式指定加密参数:将
Cipher.getInstance("AES")改为Cipher.getInstance("AES/ECB/PKCS5Padding"),避免跨平台默认模式/填充的差异。 - 对齐密钥生成逻辑:通过反射调用Android平台
SecureRandom的setSeedInternal方法,强制重置随机源;反射失败时,用SHA1哈希密码取前16字节作为密钥,确保和Java端生成的密钥一致。 - 保持辅助方法不变:二进制与十六进制转换、加密解密入口方法完全复用Java端逻辑,确保流程一致。
内容的提问来源于stack exchange,提问作者nabi
相关产品推荐
相关产品推荐

