Android Java开发如何使用自定义密钥实现AES-256加密?
认知错误修正
- AES-256要求的密钥是32字节,你提到的32/64位为字符长度,若为ASCII字符32位刚好对应32字节,但需要注意字符编码统一,避免密钥不匹配
- 不使用IV的方案仅对应AES的ECB模式,该模式安全性极低,相同明文加密后输出完全一致,很容易被破解,完全不符合密码管理器的高安全需求
- 不使用salt存在彩虹表攻击风险,加盐是低成本高收益的安全加固手段,高安全场景必须使用
- 教程中的随机密钥适用于临时加密场景,密码管理器的密钥是从用户主密码派生而来,解密时使用相同主密码、相同派生参数即可得到一致密钥完成解密,不需要存储密钥
安全实现方案
采用PBKDF2从主密码派生AES密钥 + AES-CBC/PKCS7Padding模式实现,随机生成的salt和IV随密文拼接存储,不需要额外维护其他参数,完全适配密码管理器场景。
import android.util.Base64; import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.PBEKeySpec; import javax.crypto.spec.SecretKeySpec; import java.security.SecureRandom; import java.security.spec.KeySpec; // 加密方法,入参为用户输入的主密码、待加密的账号密码明文 public static String encrypt(String masterPassword, String plaintext) throws Exception { // 生成16字节随机salt SecureRandom random = new SecureRandom(); byte[] salt = new byte[16]; random.nextBytes(salt); // 生成16字节随机IV byte[] iv = new byte[16]; random.nextBytes(iv); IvParameterSpec ivSpec = new IvParameterSpec(iv); // PBKDF2派生32字节AES-256密钥,迭代次数可根据设备性能调高提升安全性 SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256"); KeySpec spec = new PBEKeySpec(masterPassword.toCharArray(), salt, 10000, 256); SecretKey tmp = factory.generateSecret(spec); SecretKeySpec secretKey = new SecretKeySpec(tmp.getEncoded(), "AES"); // AES加密 Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding"); cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec); byte[] ciphertext = cipher.doFinal(plaintext.getBytes("UTF-8")); // 拼接salt+IV+密文转Base64返回,直接存储该字符串即可 byte[] result = new byte[salt.length + iv.length + ciphertext.length]; System.arraycopy(salt, 0, result, 0, salt.length); System.arraycopy(iv, 0, result, salt.length, iv.length); System.arraycopy(ciphertext, 0, result, salt.length + iv.length, ciphertext.length); return Base64.encodeToString(result, Base64.DEFAULT); } // 解密方法,入参为用户输入的主密码、存储的加密字符串 public static String decrypt(String masterPassword, String encryptedStr) throws Exception { byte[] result = Base64.decode(encryptedStr, Base64.DEFAULT); // 拆分salt、IV、密文 byte[] salt = new byte[16]; System.arraycopy(result, 0, salt, 0, 16); byte[] iv = new byte[16]; System.arraycopy(result, 16, iv, 0, 16); byte[] ciphertext = new byte[result.length - 32]; System.arraycopy(result, 32, ciphertext, 0, ciphertext.length); IvParameterSpec ivSpec = new IvParameterSpec(iv); // 相同参数派生密钥 SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256"); KeySpec spec = new PBEKeySpec(masterPassword.toCharArray(), salt, 10000, 256); SecretKey tmp = factory.generateSecret(spec); SecretKeySpec secretKey = new SecretKeySpec(tmp.getEncoded(), "AES"); // 解密返回明文 Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding"); cipher.init(Cipher.DECRYPT_MODE, secretKey, ivSpec); byte[] plaintext = cipher.doFinal(ciphertext); return new String(plaintext, "UTF-8"); }
注意事项
以下代码仅作演示,极度不推荐用于生产环境,如果你坚持使用无salt无IV的方案,可使用ECB模式实现:
public static String unsafeEncrypt(String masterPassword, String plaintext) throws Exception { // 假设主密码为32位ASCII字符,刚好对应32字节密钥 SecretKeySpec secretKey = new SecretKeySpec(masterPassword.getBytes("UTF-8"), "AES"); Cipher cipher = Cipher.getInstance("AES/ECB/PKCS7Padding"); cipher.init(Cipher.ENCRYPT_MODE, secretKey); byte[] ciphertext = cipher.doFinal(plaintext.getBytes("UTF-8")); return Base64.encodeToString(ciphertext, Base64.DEFAULT); } public static String unsafeDecrypt(String masterPassword, String encryptedStr) throws Exception { SecretKeySpec secretKey = new SecretKeySpec(masterPassword.getBytes("UTF-8"), "AES"); Cipher cipher = Cipher.getInstance("AES/ECB/PKCS7Padding"); cipher.init(Cipher.DECRYPT_MODE, secretKey); byte[] plaintext = cipher.doFinal(Base64.decode(encryptedStr, Base64.DEFAULT)); return new String(plaintext, "UTF-8"); }
- 上述安全方案兼容Android 6.0及以上系统,不需要额外引入依赖
- 主密码长度不足32字节时不要自行补0填充,使用PBKDF2派生是最安全的处理方式
内容的提问来源于stack exchange,提问作者user15084425
相关产品推荐
相关产品推荐

