【问题标题】:Exchanging RSA Public Keys Between iOS and Android在 iOS 和 Android 之间交换 RSA 公钥
【发布时间】:2021-11-28 18:48:27
【问题描述】:

我正在开发一个适用于 Android 和 iOS 的应用程序,它依赖 RSA 共享密钥来创建和验证数据签名。到目前为止,我已经成功地从上传到服务器的每个平台创建和导出了公钥。服务器能够从任一平台读取密钥。密钥导出为 X.509 编码的 PEM 文件。服务器使用 PKIX 编码读取 Android 创建的密钥。服务器使用 PKCS 编码从 iOS 读取密钥。 (这是通过反复试验发现的)。

Android 密钥是使用 RSA/OAEP/SHA-1 方案创建的。 Android 客户端创建的密钥可以被其他 Android 客户端、服务器和 iOS 客户端读取和加载。 Android 使用 Java 和 Bouncy Castle 的组合导出和加载密钥。 (下面用 C# 编写)。

Android 密钥导出

            X509EncodedKeySpec spec = new X509EncodedKeySpec(_pkey.GetEncoded());
            string pem = string.Empty;

            using (TextWriter writer = new StringWriter())
            {
                PemWriter pw = new PemWriter(writer);

                pw.WriteObject(new PemObject("PUBLIC KEY", spec.GetEncoded()));
                pem = writer.ToString();
            }

            return pem;

Android 密钥导入

            PemObject spki = new PemReader(new StringReader(cert)).ReadPemObject();
            X509EncodedKeySpec keySpec = new X509EncodedKeySpec(spki.Content);
            KeyFactory kf = KeyFactory.GetInstance(KeyProperties.KeyAlgorithmRsa);
            
            _pkey = kf.GeneratePublic(keySpec);

使用这些例程,Android 会生成一个 PEM 编码的公钥,类似于以下内容:

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAkmaJzUb1E3fl2kPgi/Vi
8wZHKDuGw6RAzO8F9S86qwtwPKFzksKJ8O9AXrRsxe0YCrWX9SGNVSuP4XUoKQtA
QVUfpgsmsAejjgXn+CaS/MKmhFJSZ0f9vegkMmLiQJp3u0+ggXI6fBOCK48n865D
XAsWw/TzhFCWRsmaywwkvxyymRj68pDyU75sjyaefQbbXfrLEzD2YaZVG8rHtO/o
wddPbtKZRMwD1C4nDptNfdMPmlAWk08L5eQQFYBn0EWbDfkyDTi5DYrfGTwRzuo4
HKorltiyP/LfgSL9a/nEh40tJg3Dw2E61RJtEyhA1hJHhM1Uk84Fncii9KHkJYPM
KwIDAQAB
-----END PUBLIC KEY-----

使用 openssl (openssl rsa -pubin -in temp/author.pkey) 测试这样的密钥会产生如下输出:

writing RSA key
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAkmaJzUb1E3fl2kPgi/Vi
8wZHKDuGw6RAzO8F9S86qwtwPKFzksKJ8O9AXrRsxe0YCrWX9SGNVSuP4XUoKQtA
QVUfpgsmsAejjgXn+CaS/MKmhFJSZ0f9vegkMmLiQJp3u0+ggXI6fBOCK48n865D
XAsWw/TzhFCWRsmaywwkvxyymRj68pDyU75sjyaefQbbXfrLEzD2YaZVG8rHtO/o
wddPbtKZRMwD1C4nDptNfdMPmlAWk08L5eQQFYBn0EWbDfkyDTi5DYrfGTwRzuo4
HKorltiyP/LfgSL9a/nEh40tJg3Dw2E61RJtEyhA1hJHhM1Uk84Fncii9KHkJYPM
KwIDAQAB
-----END PUBLIC KEY-----

iOS 密钥是使用 RSA/OAEP/SHA-256 方案创建的。 iOS 客户端创建的密钥可以被其他 iOS 客户端和服务器读取和加载,但不能被 Android 客户端读取和加载。 iOS 使用 Objective-C 和 Bouncy Castle 的组合导出和加载密钥。 (下面用 C# 编写)。

iOS 密钥导出

            var bytes = lookupKey()?.GetPublicKey().GetExternalRepresentation().ToArray();
            string pem = string.Empty;

            using (TextWriter writer = new StringWriter())
            {
                PemWriter pw = new PemWriter(writer);

                pw.WriteObject(new PemObject("PUBLIC KEY", bytes));
                pem = writer.ToString();

            }

            return pem;

iOS 密钥导入

                byte[] encoded = Convert.FromBase64String(pem);
                string cert = Encoding.UTF8.GetString(encoded);
                string[] parts = cert.Split('\n');
                string b64 = string.Join(string.Empty, parts.Skip(1).Take(parts.Length - 1));
                NSData data = new NSData(b64, NSDataBase64DecodingOptions.IgnoreUnknownCharacters);
            
                _pkey = SecKey.Create(data, SecKeyType.RSA, SecKeyClass.Public, 2048, null, out NSError err);

使用这些例程,iOS 生成一个 PEM 编码的公钥,类似于以下内容:

-----BEGIN PUBLIC KEY-----
MIIBCgKCAQEApg8H5D/HZE7RcvV1QkZ//HE+D9PNjxZw1Gur5S3l8QN731S28d3l
iMuVmf/4FCDvJ5GLoQKmbvslrL/s2Fc6WzS4cr84psg4dCxZVYrKY65vMMdTIoni
Z0jE6oFnl8+j3wuOA6bAid4wpSK6vIwo+u3N48csQfdj0wEG7QDsNhbj+btGVU8G
/pS3RRcSRlhxhpz+1LALjd0xZ6iXrAn85r39dsEZohIuOC4/+A5EIpUUw12k4+Dy
3qxI7nDiLpJySeE8NV4K9Fk0+4flmMDBNkLMJBT8Vt6DGHtc4ImBnhBLxFt/oSq9
8iW3TwxrRptBZUkdU2U+QXigLUYcj/T+MQIDAQAB
-----END PUBLIC KEY-----

使用 openssl (openssl rsa -pubin -in temp/author.pkey) 测试这样的密钥会产生如下输出:

unable to load Public Key
140230339794240:error:0D0680A8:asn1 encoding routines:asn1_check_tlen:wrong tag:../crypto/asn1/tasn_dec.c:1149:
140230339794240:error:0D07803A:asn1 encoding routines:asn1_item_embed_d2i:nested asn1 error:../crypto/asn1/tasn_dec.c:309:Type=X509_ALGOR
140230339794240:error:0D08303A:asn1 encoding routines:asn1_template_noexp_d2i:nested asn1 error:../crypto/asn1/tasn_dec.c:646:Field=algor, Type=X509_PUBKEY
140230339794240:error:0906700D:PEM routines:PEM_ASN1_read_bio:ASN1 lib:../crypto/pem/pem_oth.c:33:

我注意到从 iOS 导出的公钥比从 Android 导出的密钥少一行。我相信 iOS 使用与 X.509 证书略有不同的编码导出公钥。我从一个单独的 SO 线程中找到了有关 PKCS #1 规范的文档,但该线程没有提供如何处理所提供信息的方向。 https://www.rfc-editor.org/rfc/rfc3447#page-6

总的来说,我对密码学的经验很少,所以我可能错误地使用了一些术语,并且显然遗漏了一些对有更多经验的人来说可能很明显的东西。我不知道是否可以将 PKCS 编码的密钥转换为 PKIX 密钥(或者这是否是我的问题)。谁能提供关于我应该在这里尝试什么的任何线索?

【问题讨论】:

  • 这里有一些术语被轻微滥用,这增加了混乱。 PKIX 编码与 PKCS 编码 我认为您的意思是 SubjectPublicKeyInfo (SPKI) 公钥编码与 PKCS#1 RSA 公钥编码。 ...使用 RSA/OAEP/SHA-1 方案创建... OAEP/SHA-1 是对 RSA 加密期间使用的填充方案的引用。密钥创建/生成不涉及 OAEP 或 SHA-1。这只是 RSA。
  • ...iOS 生成一个 PEM 编码的公钥,类似于以下... 该密钥是 PKCS#1 RSA 公钥,它是 PEM 编码 不应该-----BEGIN PUBLIC KEY-----开头,它应该以-----BEGIN RSA PUBLIC KEY-----开头。是否无法修改 iOS 代码以生成 SPKI 公钥?因为那是你需要的。如果没有,则 Bouncycastle 可以在您提供正确的标头时解析密钥。
  • 啊,我不用再把它放进ASN.1解码器了。不过,对那个 API 感到羞耻,他们没有指定任何关于密钥编码的内容。那么它是微软,你能期待什么。他们不记录,期间。
  • 谢谢@PresidentJamesK.Polk。我认为很明显我的命名法可以做一些工作。我认为,关于填充方案,您是正确的。我尝试在输出文件中手动将-----BEGIN PUBLIC KEY----- 替换为-----BEGIN RSA PUBLIC KEY-----,并使用openssl 对其进行测试。我收到以下错误消息。 unable to load Public Key 140698173051520:error:0909006C:PEM routines:get_name:no start line:../crypto/pem/pem_lib.c:745:Expecting: PUBLIC KEY会不会还有什么我不明白的地方?
  • 对于该格式(PKCS#1 RSA 公钥),您需要 -RSAPublicKey_in 选项而不是 -pubin 选项。

标签: android ios openssl cryptography


【解决方案1】:

我相信有一个面向 iOS 的修复程序可以解决我正在尝试的问题。在挖掘有关 ASN.1 和各种公钥编码的资源材料时,我有点迷失了。 Swift API / SDK 文档中发布的关于公钥加密的方法并没有明确说明如何获取公钥的 N 或 E,尽管我确信如果我对这项技术有更好的了解,它们可以派生出来。

目前,我利用服务器进程对 iOS 客户端创建的密钥进行从 PKCS-1 到 SPKI 的单向转换。 GoLang 已经完全发布并有据可查的例程来处理这个问题,而且它们的实现非常简单。 (为简洁起见,以下已被简化)。

pBlock, _ := pem.Decode(pkcs1) // input ASN.1 PKCS-1 in PEM format
key, _ := x509.ParsePKCS1PublicKey(pBlock.Bytes) // get RSA public key

pkix, _ := x509.MarshalPKIXPublicKey(key) // encode the RSA key using SPKI

pkixPem := pem.EncodeToMemory(&pem.Block{  
    Type:  "PUBLIC KEY",
    Bytes: pkix,
}) // export ASN.1 PKIX in PEM format

涉及服务器并不理想,但它是一种单向转换,并生成一个从那时起客户端(Android 和 iOS)都可以使用的 PEM。我能够在 iOS 已经使用 RSA 公钥调用服务器时实现逻辑,因此不需要额外的往返。

总而言之,这对我的场景来说是一个可用的解决方案。很高兴知道可以在本机 Swift / Objective-C 中使用的例程,至少在学术上是这样。

感谢大家的意见。它帮助我达到了我的目标。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-11-27
    • 2012-10-07
    • 2015-10-19
    • 2011-03-10
    • 2015-04-02
    • 1970-01-01
    相关资源
    最近更新 更多