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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 21:24:19