【发布时间】: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