iOS与macOS无外部库验签失败问题(含-9809等错误)
跨平台RSA验签排坑指南
看起来你在iOS/macOS和C#的RSA验签对接上踩了不少实打实的坑,我帮你梳理几个最可能出问题的点,一个个来排查修复:
1. 公钥创建的核心问题:格式不对
你提到用「去除头信息的Pem文件」生成公钥,但SecKeyCreateWithData要求的是DER编码的二进制公钥,而不是直接去掉头尾的Base64字符串。很多人都栽在这里:
- 正确步骤:把PEM去掉
-----BEGIN PUBLIC KEY-----和-----END PUBLIC KEY-----后,先做Base64解码得到DER格式的NSData,再传给SecKeyCreateWithData。 - 另外,你的attributes里设置了
kSecAttrIsPermanent:@YES,如果只是临时用公钥验签,完全没必要存到钥匙串,改成@NO还能避免旧公钥残留的干扰;同时要确保kSecAttrKeySizeInBits和C#那边生成的公钥长度完全一致(比如确实是2048位)。
修复后的公钥创建代码示例:
// 假设pemString是去除头尾的Base64字符串 NSData *derData = [[NSData alloc] initWithBase64EncodedString:pemString options:NSDataBase64DecodingIgnoreUnknownCharacters]; NSDictionary* attributes = @{ (id)kSecAttrKeyType: (id)kSecAttrKeyTypeRSA, (id)kSecAttrKeySizeInBits: @2048, (id)kSecPublicKeyAttrs: @{ (id)kSecAttrIsPermanent: @NO, // 不需要持久化就设为NO (id)kSecAttrApplicationTag: tag1 }, (id)kSecAttrCanEncrypt:@YES, (id)kSecAttrCanVerify:@YES, (id)kSecAttrKeyClass: (id)kSecAttrKeyClassPublic }; CFErrorRef error = NULL; SecKeyRef keyRef = SecKeyCreateWithData((__bridge CFDataRef)derData, (__bridge CFDictionaryRef)attributes, &error);
2. iOS验签的两个关键错误
编码不匹配:UTF-16字节序问题
C#里的Unicode编码是UTF-16 Little-Endian,而iOS的NSUTF16StringEncoding会跟随系统字节序,这会导致两边生成的原始数据哈希完全不一样。你需要明确指定小端编码:
NSData *plainData = [dataToSign dataUsingEncoding:NSUTF16LittleEndianStringEncoding];
SecKeyRawVerify的哈希包装问题
C#的RSACryptoProvider.SignData默认会把SHA256哈希值用ASN.1结构包装后再做PKCS#1填充,但你现在直接把CC_SHA256的原始哈希传进去,验签肯定失败。更推荐用SecKeyVerifySignature,它会自动处理ASN.1包装,代码更简洁不易错:
NSData *plainData = [dataToSign dataUsingEncoding:NSUTF16LittleEndianStringEncoding]; SecKeyAlgorithm algorithm = kSecKeyAlgorithmRSASignaturePKCS1v15SHA256; OSStatus status = SecKeyVerifySignature(keyRef, algorithm, (__bridge CFDataRef)plainData, (__bridge CFDataRef)signature, NULL); if (status == errSecSuccess) { NSLog(@"验签成功"); } else { NSLog(@"验签失败,错误码:%d", (int)status); }
3. macOS验签的参数顺序错误
你的SecVerifyTransformCreate调用完全搞反了参数!第二个参数应该传签名数据,而不是你传的self.digest(哈希值),这直接导致验签逻辑错误。修复后的代码:
CFErrorRef errorCF = NULL; // 第二个参数传签名数据,不是digest! SecTransformRef verifier = SecVerifyTransformCreate(keyRef, (__bridge CFDataRef)signatureData, &errorCF); if (!verifier) { CFShow(errorCF); CFRelease(errorCF); return NO; } // 设置原始待验签数据 SecTransformSetAttribute(verifier, kSecTransformInputAttributeName, (__bridge CFTypeRef)plainData, &errorCF); if (errorCF) { CFShow(errorCF); CFRelease(errorCF); CFRelease(verifier); return NO; } // 设置哈希算法 SecTransformSetAttribute(verifier, kSecDigestTypeAttribute, kSecDigestSHA2, &errorCF); SecTransformSetAttribute(verifier, kSecDigestLengthAttribute, (__bridge CFNumberRef)@256, &errorCF); if (errorCF) { CFShow(errorCF); CFRelease(errorCF); CFRelease(verifier); return NO; } // 执行验签 CFBooleanRef result = SecTransformExecute(verifier, &errorCF); BOOL success = (result != NULL && CFBooleanGetValue(result)); CFRelease(verifier); if (errorCF) { CFShow(errorCF); CFRelease(errorCF); } return success;
最后几个额外检查点
- 确认C#那边用的是
SHA256WithRSA签名:比如rsa.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1),确保padding和哈希算法和移动端一致。 - 签名数据传递要完整:C#生成的签名是Base64字符串的话,移动端解码时要用
NSDataBase64DecodingIgnoreUnknownCharacters选项,避免换行符、大小写等问题干扰。
内容的提问来源于stack exchange,提问作者D12sd
相关产品推荐
相关产品推荐

