You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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);
}

问题根源分析

  1. 二进制转字符串的编码错误:SHA256哈希后的二进制数据包含大量不可打印字符,Android端直接用默认字符集(如UTF-8)转成String时,无法映射的字节会被替换为占位符(比如�),导致后续CharArray和原始ByteArray完全不符。而iOS端直接传入二进制字节作为PBKDF2输入,两者输入本质不同。
  2. 参数单位不统一:iOS的digestLength是密钥字节数,Android的PBEKeySpec中keyLength是比特数,若未做字节×8=比特的转换,会导致密钥长度不同,结果自然不一致。
  3. 输入数据类型不匹配: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一致:
    // 将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));
    
    两者Hex字符串必须完全相同,才能保证PBKDF2输入一致。

4. 统一SHA256的输入编码

确保两端SHA256哈希时,原始password和salt都使用相同的字符编码(比如UTF-8),避免因编码不同导致哈希后的ByteArray不一致。

内容的提问来源于stack exchange,提问作者yoonwoo4429

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 21:33:19