基于Room与Keystore的加密密码存储:离线认证解密-1异常排查及方案
异常解决与离线认证优化方案
一、解密-1异常的核心原因与修复
1. 流读取逻辑缺陷
你的decryptText方法存在两个致命问题:
inputStream.read()返回-1代表流已耗尽,直接将此值作为ivSize会创建长度为-1的数组,触发异常;read(iv)和read(encryptedBytes)无法保证一次性读取完整数据,InputStream.read(byte[])仅返回实际读取的字节数,必须循环读取直到填满目标数组。
修复后的decryptText代码:
fun decryptText(inputStream: InputStream): ByteArray { return inputStream.use { // 读取IV长度,先校验有效性 val ivSize = it.read() if (ivSize == -1) throw IOException("无效加密数据:缺失IV长度") val iv = ByteArray(ivSize) var bytesRead = 0 // 循环读取IV至完整 while (bytesRead < ivSize) { val read = it.read(iv, bytesRead, ivSize - bytesRead) if (read == -1) throw IOException("无效加密数据:IV不完整") bytesRead += read } // 读取加密数据长度 val encryptedBytesSize = it.read() if (encryptedBytesSize == -1) throw IOException("无效加密数据:缺失加密内容长度") val encryptedBytes = ByteArray(encryptedBytesSize) bytesRead = 0 // 循环读取加密数据至完整 while (bytesRead < encryptedBytesSize) { val read = it.read(encryptedBytes, bytesRead, encryptedBytesSize - bytesRead) if (read == -1) throw IOException("无效加密数据:加密内容不完整") bytesRead += read } getDecryptCipherForIv(iv).doFinal(encryptedBytes) } }
2. 加密逻辑冗余问题
你提到要存储哈希后的密码,但当前代码对哈希后的密码再次进行AES加密——哈希是不可逆操作,根本不需要解密。认证时只需将用户输入的密码重新哈希,再与存储的哈希值对比即可,这能直接避免加密解密环节的所有异常。
如果坚持要加密存储哈希值,需确保encryptCipher与getDecryptCipherForIv(iv)的算法参数完全匹配(如模式GCM/CBC、填充方式NoPadding/PKCS7Padding等),且动态盐值必须与加密时的盐一致(盐值需随加密数据一同存储,不能每次生成不同值)。
二、离线认证流程优化方案
1. 密码存储逻辑修正
- 只哈希,不加密:使用PBKDF2WithHmacSHA256或Argon2等慢哈希算法处理密码,搭配用户唯一的盐值(盐值需与哈希值一起存储)。示例代码:
fun hashPassword(password: String, salt: ByteArray): ByteArray { val spec = PBEKeySpec(password.toCharArray(), salt, 100000, 256) val factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256") return factory.generateSecret(spec).encoded } // 认证校验 fun verifyPassword(inputPassword: String, storedHash: ByteArray, storedSalt: ByteArray): Boolean { val inputHash = hashPassword(inputPassword, storedSalt) return MessageDigest.isEqual(inputHash, storedHash) }
- 邮箱可加密存储(避免明文泄露),但无需哈希——认证时先解密邮箱,再与用户输入的邮箱直接对比。
2. Room数据库存储优化
- 将盐值单独作为字段存储(若使用PBKDF2),无需将哈希值、邮箱存为BLOB,可转为Base64字符串存储(更易调试,规避BLOB操作的潜在问题):
@Entity(tableName = "account_credentials") data class AccountCredentials( @PrimaryKey val uuid: String, val firebaseId: String?, val hashedPassword: String, // Base64编码的哈希值 val email: String, // 加密后Base64编码的邮箱 val salt: String // Base64编码的盐值 )
3. Keystore使用优化
- 确保Keystore中生成的AES密钥持久化,且算法参数正确(如AES/GCM/NoPadding,GCM模式需配套IV和标签);
- 避免每次加密都创建新的
CryptographicManager实例,复用实例减少资源消耗。
内容的提问来源于stack exchange,提问作者DMC_1903
相关产品推荐
相关产品推荐

