Swift 5.0实现Steam RSA登录加密返回密码错误问题求助
Steam登录RSA加密后返回密码错误排查
问题描述
开发Steam登录功能时,需使用平台下发的RSA公钥对登录密码进行加密,调用SecKeyEncryptedData完成加密后提交认证请求,接口始终返回密码错误。初步排查方向锁定为加密格式问题,但一直未定位到故障点。
已尝试的实现方案
方案1:基于系统Security框架实现
核心代码如下:
static func encrypt(string: String, mod: String, exp: String) -> String? { let keyString = self.rsaPublicKeyder(mod: mod, exp: exp) guard let data = Data(base64Encoded: keyString) else { return nil } var attributes: CFDictionary { return [kSecAttrKeyType : kSecAttrKeyTypeRSA, kSecAttrKeyClass : kSecAttrKeyClassPublic, kSecAttrKeySizeInBits : 2048, kSecReturnPersistentRef : kCFBooleanTrue!] as CFDictionary } var error: Unmanaged<CFError>? = nil guard let secKey = SecKeyCreateWithData(data as CFData, attributes, &error) else { print(error.debugDescription) return nil } guard let result = SecKeyCreateEncryptedData(secKey, SecKeyAlgorithm.rsaEncryptionPKCS1, string.data(using: .utf8)! as CFData, &error) else{ print(error.debugDescription) return nil } return (result as Data).base64EncodedString() } }
代码说明:
self.rsaPublicKeyder方法用于将传入的模数mod、指数exp转换为可通过SecKeyCreateWithData接口导入的PKCS#8 DER格式公钥,已在CyberChef中验证生成的DER公钥数据无异常。
方案2:基于BigInt库自实现RSA逻辑
引入BigInt库手动实现加密、填充逻辑后依然认证失败,核心代码如下:
class RSA{ static func encrypt(string: String, mod: String, exp: String) -> String { let secret = pkcs1pad2(data: string.data(using: .utf8)!, keySize: mod.count / 2)! return secret.power(BigUInt(exp, radix: 16)!, modulus: BigUInt(mod, radix: 16)!).serialize().base64EncodedString() } static func pkcs1pad2(data: Data, keySize: Int) -> BigUInt?{ if (keySize < data.count + 11){ return nil; } var rndData: [UInt8] = [UInt8](repeating: 0, count: keySize - 3 - data.count) let status = SecRandomCopyBytes(kSecRandomDefault, rndData.count, &rndData) for i in 0..<rndData.count{ if rndData[i] == 0{ rndData[i] = UInt8(i+1) } } guard status == errSecSuccess else{ return nil } return BigUInt(Data([0x00, 0x02]) + Data(rndData) + Data([0x00]) + data) } }
可直接落地的排查修正点
- 修正公钥导入属性:
SecKeyCreateWithData的属性字典中移除多余的kSecReturnPersistentRef字段,该字段仅用于需要将密钥持久化到钥匙串的场景,创建内存中临时使用的加密公钥时传入该字段,可能导致公钥参数解析异常。修正后的属性字典参考:var attributes: CFDictionary { return [kSecAttrKeyType : kSecAttrKeyTypeRSA, kSecAttrKeyClass : kSecAttrKeyClassPublic, kSecAttrKeySizeInBits : 2048] as CFDictionary } - 修正自实现填充逻辑漏洞:现有填充代码中,
SecRandomCopyBytes调用在零值修正逻辑之前,且零值替换逻辑存在溢出风险(当索引i>=255时i+1超出UInt8取值范围),同时替换后的随机值分布不符合PKCS#1填充要求。如果使用自实现方案,随机填充段生成逻辑需要循环生成字节直到所有字节非零,不能事后替换。 - 校验密文长度:2048位RSA密钥加密后的密文固定为256字节,Base64编码前如果长度不符,直接说明加密或公钥导入环节出错。
- 确认请求参数匹配:提交加密密码时,需要和获取公钥接口返回的
rsa_timestamp、timestamp参数一同提交,时间戳不匹配、参数名拼写错误(Steam要求密文字段为encrypted_password)也会返回密码错误。 - 确认预处理逻辑:加密前的明文密码不要做URL编码、特殊字符转义、Base64编码,直接将原始明文字符串转UTF-8编码的
Data传入加密函数即可;加密后的二进制密文直接做标准Base64编码提交,不要做十六进制转码或追加额外字符。
内容的提问来源于stack exchange,提问作者Dominic Socular
相关产品推荐
相关产品推荐

