PBKDF2WithHmacSHA256在Swift与Android平台结果不一致求助
Android与iOS平台PBKDF2算法执行结果不一致问题排查
问题背景
跨平台使用PBKDF2算法时,已确认算法执行前password和salt的Hex字符串、ByteArray值在Android和iOS两端完全一致,但最终生成的派生密钥却不相同。怀疑Android端将ByteArray转换为String再转为CharArray的过程中数据损坏,需要验证参数一致性并解决问题。
两端代码实现
iOS 代码
func generateKeyWithPBKDF2(password: String, salt: String, iterationCount: Int, digestLength: Int) -> Data? { let passwordHashData = sha256(string: password) let saltHashData = sha256(string: salt) var derivedKey = [UInt8](repeating: 0, count: digestLength) let result = passwordHashData.withUnsafeBytes { passwordPointer in saltHashData.withUnsafeBytes { saltPointer in CCKeyDerivationPBKDF( CCPBKDFAlgorithm(kCCPBKDF2), passwordPointer.baseAddress, passwordHashData.count, saltPointer.baseAddress, saltHashData.count, CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA256), UInt32(iterationCount), &derivedKey, digestLength ) } } return result == kCCSuccess ? Data(derivedKey) : nil } // 配套的SHA256实现(基于UTF-8编码) func sha256(string: String) -> Data { guard let data = string.data(using: .utf8) else { return Data() } var hash = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH)) data.withUnsafeBytes { CC_SHA256($0.baseAddress, CC_LONG(data.count), &hash) } return Data(hash) }
Android 代码片段(原错误逻辑)
byte[] passwordHashBytes = sha256(password.getBytes(StandardCharsets.UTF_8)); // 此处直接将二进制哈希转String会导致数据损坏 String passwordHashStr = new String(passwordHashBytes); char[] passwordCharArray = passwordHashStr.toCharArray(); byte[] saltHashBytes = sha256(salt.getBytes(StandardCharsets.UTF_8)); // 注意keyLength单位:Android是比特,iOS是字节 PBEKeySpec keySpec = new PBEKeySpec(passwordCharArray, saltHashBytes, iterationCount, 256); SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256"); byte[] derivedKey = factory.generateSecret(keySpec).getEncoded(); // 配套的SHA256实现 private byte[] sha256(byte[] input) throws NoSuchAlgorithmException { MessageDigest digest = MessageDigest.getInstance("SHA-256"); return digest.digest(input); }
问题根源分析
- 二进制转字符串的编码错误:SHA256哈希后的二进制数据包含大量不可打印字符,Android端直接用默认字符集(如UTF-8)转成String时,无法映射的字节会被替换为占位符(比如
�),导致后续CharArray和原始ByteArray完全不符。而iOS端直接传入二进制字节作为PBKDF2输入,两者输入本质不同。 - 参数单位不统一:iOS的
digestLength是密钥字节数,Android的PBEKeySpec中keyLength是比特数,若未做字节×8=比特的转换,会导致密钥长度不同,结果自然不一致。 - 输入数据类型不匹配:iOS的PBKDF2输入是SHA256哈希后的二进制字节,而Android错误地将二进制哈希转成字符串编码后的CharArray,输入数据完全不是同一回事。
解决方案
1. 正确转换ByteArray到CharArray
不要通过字符编码转换,直接将每个字节的数值映射到char(处理符号位避免负数偏差):
byte[] passwordHashBytes = sha256(password.getBytes(StandardCharsets.UTF_8)); char[] passwordCharArray = new char[passwordHashBytes.length]; for (int i = 0; i < passwordHashBytes.length; i++) { // 按无符号字节转char,避免符号位导致的数值偏差 passwordCharArray[i] = (char) (passwordHashBytes[i] & 0xFF); }
2. 统一参数单位
确保iOS的digestLength(字节)和Android的keyLength(比特)对应,比如iOS要生成32字节密钥,Android需传入32 * 8 = 256作为keyLength参数。
3. 验证输入一致性
在两端分别打印PBKDF2输入的password和salt的Hex字符串,确认完全一致:
- iOS端打印Hex:
let passwordHex = passwordHashData.map { String(format: "%02hhx", $0) }.joined() print("iOS password hash hex: \(passwordHex)") - Android端验证转换后的CharArray是否和原始ByteArray一致:
两者Hex字符串必须完全相同,才能保证PBKDF2输入一致。// 将CharArray转回ByteArray并打印Hex byte[] verifyBytes = new byte[passwordCharArray.length]; for (int i = 0; i < passwordCharArray.length; i++) { verifyBytes[i] = (byte) passwordCharArray[i]; } System.out.println("Android verify password hash hex: " + bytesToHex(verifyBytes));
4. 统一SHA256的输入编码
确保两端SHA256哈希时,原始password和salt都使用相同的字符编码(比如UTF-8),避免因编码不同导致哈希后的ByteArray不一致。
内容的提问来源于stack exchange,提问作者yoonwoo4429
相关产品推荐
相关产品推荐

