使用.p12文件签名并通过Node Crypto验证iOS API调用合法性
嘿,看你已经在搭建iOS和Node.js之间的API签名验证体系了,我来帮你把现有流程理清楚,再补上.p12相关的关键细节,让整个验证链路跑通:
现有密钥生成与后端逻辑梳理
1. RSA密钥对生成
你已经用OpenSSL生成了加密的私钥和对应的公钥,我把命令整理成更规范的格式(去掉多余的转义符):
# 生成带AES256密码保护的2048位RSA私钥 openssl genrsa -aes256 -passout pass:pass -out signature_private_key.pem 2048 # 从加密私钥导出公钥(后端用于验证签名) openssl rsa -in signature_private_key.pem -passin pass:pass -out signature_public_key.pem -outform PEM -pubout
这里要注意:私钥最终会打包成.p12文件给iOS端使用,公钥留在Node.js后端做签名验证。
2. Node.js后端签名字符串生成与验证逻辑
你写的createSignatureString方法是生成待签名字符串的核心,我帮你补全完整的验证流程:
const crypto = require('crypto'); const fs = require('fs'); class ApiSignatureValidator { // 生成待签名的原始字符串(和iOS端规则必须完全一致) static createSignatureString(bodyString, apiKey, timestamp) { // 对请求体做SHA256哈希并转base64 const bodyHash = crypto .createHash("sha256") .update(bodyString) .digest("base64"); // 按约定拼接关键参数:apiKey + 时间戳 + 请求体哈希 // 这里的分隔符和顺序要和iOS端严格对齐! return `${apiKey}.${timestamp}.${bodyHash}`; } // 用公钥验证iOS端发来的签名 static validateSignature(signatureRaw, clientSignature, publicKeyPath) { try { const publicKey = fs.readFileSync(publicKeyPath, 'utf8'); const verifier = crypto.createVerify('SHA256'); verifier.update(signatureRaw); // 验证base64格式的签名,算法要和iOS端匹配 return verifier.verify(publicKey, clientSignature, 'base64'); } catch (err) { console.error('签名验证失败:', err); return false; } } }
关键点:后端必须用原始请求体字符串计算哈希,不能用解析后的JSON对象(否则可能因为键顺序、空格差异导致哈希不一致)。
iOS端使用.p12文件签名的核心步骤
.p12是包含私钥和证书的加密容器,iOS端需要从中提取私钥来生成签名,以下是Swift实现的关键代码:
1. 从.p12文件提取私钥
import Security func extractPrivateKey(fromP12Data p12Data: Data, password: String) -> SecKey? { let importOptions: [String: Any] = [ kSecImportExportPassphrase as String: password ] var importedItems: CFArray? let status = SecPKCS12Import(p12Data as CFData, importOptions as CFDictionary, &importedItems) guard status == errSecSuccess, let items = importedItems as? [[String: Any]], let identityDict = items.first, let identity = identityDict[kSecImportItemIdentity as String] as? SecIdentity else { return nil } var privateKey: SecKey? let keyStatus = SecIdentityCopyPrivateKey(identity, &privateKey) return keyStatus == errSecSuccess ? privateKey : nil }
2. 生成签名并随请求发送
func generateRequestSignature(body: Data, apiKey: String, timestamp: String, privateKey: SecKey) -> String? { // 计算请求体的SHA256哈希(和后端逻辑一致) let bodyHash = body.sha256().base64EncodedString() // 拼接待签名字符串(和后端createSignatureString的规则完全相同) let signatureRaw = "\(apiKey).\(timestamp).\(bodyHash)" guard let rawData = signatureRaw.data(using: .utf8) else { return nil } var error: Unmanaged<CFError>? // 使用RSA PKCS#1 v1.5 + SHA256签名,和后端验证算法匹配 guard let signatureData = SecKeyCreateSignature( privateKey, .rsaSignatureMessagePKCS1v15SHA256, rawData as CFData, &error ) as Data? else { print("签名生成失败: \(error?.takeRetainedValue() ?? CFError())") return nil } return signatureData.base64EncodedString() } // 给Data扩展SHA256计算方法 extension Data { func sha256() -> Data { var hash = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH)) self.withUnsafeBytes { buffer in _ = CC_SHA256(buffer.baseAddress, CC_LONG(self.count), &hash) } return Data(hash) } }
iOS端发送请求时,要把生成的签名、apiKey、timestamp一起放在请求头或参数里,供后端验证。
端到端验证的重要注意事项
- 时间戳防重放:后端要检查timestamp是否在有效时间窗口内(比如5分钟),防止攻击者重复使用旧的合法请求。
- 参数一致性:两端的待签名字符串拼接规则、哈希算法、签名算法必须完全一致,差一个字符都会导致验证失败。
- .p12安全保护:iOS端不要硬编码.p12的密码,建议用Keychain存储;同时.p12文件要避免被逆向工具提取,可以考虑做二进制混淆。
内容的提问来源于stack exchange,提问作者huge-iguana




