用随机密钥加密的字符串如何解密?求安卓Java AES-256加密方案
问题解答与认知纠正
- 随机密钥解密逻辑:使用随机密钥加密后,解密时必须使用完全相同的密钥才能完成解密,因此随机密钥需要和密文分开妥善存储。你的密码管理器场景不需要使用随机密钥,密钥由用户主密码派生即可,无需存储密钥。
- 你要求的无salt、无IV的AES-256代码存在严重安全缺陷:
- 无IV意味着只能使用ECB加密模式,该模式下相同明文会生成完全相同的密文,攻击者可以直接通过密文特征推断明文规律,完全不适合存储敏感的账号密码。
- 无salt直接使用用户主密码作为密钥,会大幅降低暴力破解的难度,即使主密码长度有32位,也存在彩虹表攻击的风险。
- 用户输入的32/64位字符属于字符串,直接转字节作为AES密钥不符合规范,会出现各类加解密失败的问题,这也是你找到的网上代码无法运行的常见原因。
密码管理器场景的正确实现逻辑
你的场景的加解密流程不需要存储主密码和密钥,符合你的需求:
- 首次设置主密码阶段:
- 生成16字节随机salt、12字节随机IV,二者不属于敏感数据,可直接明文存储在本地
- 使用PBKDF2密钥派生算法,传入用户主密码、salt、10000次以上迭代次数,派生得到32字节的AES-256密钥
- 使用AES-GCM认证加密模式,传入密钥、IV加密用户要存储的账号密码,得到密文和16字节认证标签
- 将salt、IV、认证标签、密文拼接后存储到本地即可
- 解密阶段:
- 用户输入主密码,读取本地存储的salt,用相同的PBKDF2参数派生得到AES密钥
- 读取本地存储的IV、认证标签、密文,用密钥解密即可得到明文账号密码
代码实现
(强烈不推荐)无IV无salt的AES-256-ECB演示代码
警告:以下代码仅做技术演示,ECB模式安全性极低,禁止在生产环境使用
import javax.crypto.Cipher; import javax.crypto.spec.SecretKeySpec; import java.nio.charset.StandardCharsets; import java.util.Base64; public class InsecureAES { // 加密,key需为32位字符串对应AES-256 public static String encrypt(String plainText, String key) throws Exception { SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES"); Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); cipher.init(Cipher.ENCRYPT_MODE, secretKey); byte[] encrypted = cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8)); return Base64.getEncoder().encodeToString(encrypted); } // 解密 public static String decrypt(String cipherText, String key) throws Exception { SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES"); Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); cipher.init(Cipher.DECRYPT_MODE, secretKey); byte[] decrypted = cipher.doFinal(Base64.getDecoder().decode(cipherText)); return new String(decrypted, StandardCharsets.UTF_8); } }
(推荐生产使用)AES-256-GCM安全实现代码
import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.GCMParameterSpec; import javax.crypto.spec.PBEKeySpec; import javax.crypto.spec.SecretKeySpec; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.security.SecureRandom; import java.security.spec.KeySpec; import java.util.Base64; public class SecureAES { private static final int SALT_LENGTH = 16; private static final int IV_LENGTH = 12; private static final int TAG_LENGTH = 16; private static final int ITERATION_COUNT = 10000; private static final int KEY_LENGTH = 256; // 加密,返回可直接存储的Base64字符串 public static String encrypt(String plainText, String masterPassword) throws Exception { // 生成随机salt和IV SecureRandom random = new SecureRandom(); byte[] salt = new byte[SALT_LENGTH]; random.nextBytes(salt); byte[] iv = new byte[IV_LENGTH]; random.nextBytes(iv); // 派生AES密钥 SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256"); KeySpec spec = new PBEKeySpec(masterPassword.toCharArray(), salt, ITERATION_COUNT, KEY_LENGTH); SecretKey tmp = factory.generateSecret(spec); SecretKey secretKey = new SecretKeySpec(tmp.getEncoded(), "AES"); // GCM模式加密 Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); cipher.init(Cipher.ENCRYPT_MODE, secretKey, new GCMParameterSpec(TAG_LENGTH * 8, iv)); byte[] cipherText = cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8)); // 拼接salt + iv + 密文,转Base64存储 ByteBuffer byteBuffer = ByteBuffer.allocate(salt.length + iv.length + cipherText.length); byteBuffer.put(salt); byteBuffer.put(iv); byteBuffer.put(cipherText); return Base64.getEncoder().encodeToString(byteBuffer.array()); } // 解密,传入存储的Base64字符串和用户输入的主密码 public static String decrypt(String storedCipherText, String masterPassword) throws Exception { byte[] decoded = Base64.getDecoder().decode(storedCipherText); ByteBuffer byteBuffer = ByteBuffer.wrap(decoded); // 拆分salt、iv、密文 byte[] salt = new byte[SALT_LENGTH]; byteBuffer.get(salt); byte[] iv = new byte[IV_LENGTH]; byteBuffer.get(iv); byte[] cipherText = new byte[byteBuffer.remaining()]; byteBuffer.get(cipherText); // 派生相同的密钥 SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256"); KeySpec spec = new PBEKeySpec(masterPassword.toCharArray(), salt, ITERATION_COUNT, KEY_LENGTH); SecretKey tmp = factory.generateSecret(spec); SecretKey secretKey = new SecretKeySpec(tmp.getEncoded(), "AES"); // 解密 Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); cipher.init(Cipher.DECRYPT_MODE, secretKey, new GCMParameterSpec(TAG_LENGTH * 8, iv)); byte[] plainText = cipher.doFinal(cipherText); return new String(plainText, StandardCharsets.UTF_8); } }
内容的提问来源于stack exchange,提问作者user15084425
相关产品推荐
相关产品推荐

