在 C# 中解密使用 RSA 在 iPhone 上加密的内容时遇到问题
到目前为止,我已经花了两天时间研究这个问题,并梳理了我可以使用的所有资源,所以这是最后的手段。
我有一个 X509 证书,其公钥已存储在 iPhone 的钥匙串中(此时仅限模拟器)。 在 ASP.NET 方面,我已在证书存储区中使用私钥获取了证书。 当我在 iPhone 上加密字符串并在服务器上解密时,我收到 CryptographicException
“错误数据”。 我尝试了 RSACryptoServiceProvider
页面上的一个长镜头,但它没有帮助。
我比较了两边的 base-64 字符串,它们是相等的。 我比较了解码后的原始字节数组,它们也相等。 如果我使用公钥在服务器上加密,则字节数组与 iPhone 的版本不同,并且很容易使用私钥解密。 原始明文字符串有 115 个字符,因此它在我的 2048 位密钥的 256 字节限制之内。
这是 iPhone 加密方法(几乎逐字来自 CryptoExercise 示例应用 的 wrapSymmetricKey
方法):
+ (NSData *)encrypt:(NSString *)plainText usingKey:(SecKeyRef)key error:(NSError **)err
{
size_t cipherBufferSize = SecKeyGetBlockSize(key);
uint8_t *cipherBuffer = NULL;
cipherBuffer = malloc(cipherBufferSize * sizeof(uint8_t));
memset((void *)cipherBuffer, 0x0, cipherBufferSize);
NSData *plainTextBytes = [plainText dataUsingEncoding:NSUTF8StringEncoding];
OSStatus status = SecKeyEncrypt(key, kSecPaddingNone,
(const uint8_t *)[plainTextBytes bytes],
[plainTextBytes length], cipherBuffer,
&cipherBufferSize);
if (status == noErr)
{
NSData *encryptedBytes = [[[NSData alloc]
initWithBytes:(const void *)cipherBuffer
length:cipherBufferSize] autorelease];
if (cipherBuffer)
{
free(cipherBuffer);
}
NSLog(@"Encrypted text (%d bytes): %@",
[encryptedBytes length], [encryptedBytes description]);
return encryptedBytes;
}
else
{
*err = [NSError errorWithDomain:@"errorDomain" code:status userInfo:nil];
NSLog(@"encrypt:usingKey: Error: %d", status);
return nil;
}
}
这是服务器端 C# 解密方法:
private string Decrypt(string cipherText)
{
if (clientCert == null)
{
// Get certificate
var store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
foreach (var certificate in store.Certificates)
{
if (certificate.GetNameInfo(X509NameType.SimpleName, false) == CERT)
{
clientCert = certificate;
break;
}
}
}
using (var rsa = (RSACryptoServiceProvider)clientCert.PrivateKey)
{
try
{
var encryptedBytes = Convert.FromBase64String(cipherText);
var decryptedBytes = rsa.Decrypt(encryptedBytes, false);
var plaintext = Encoding.UTF8.GetString(decryptedBytes);
return plaintext;
}
catch (CryptographicException e)
{
throw(new ApplicationException("Unable to decrypt payload.", e));
}
}
}
我怀疑平台之间存在一些编码问题。 我知道一个是大尾数,另一个是小尾数,但我不太了解哪个是哪个,或者如何克服差异。 Mac OS X、Windows 和 iPhone都是小端字节序,所以这不是问题。
新理论:如果将 OAEP 填充布尔值设置为 false,则默认为 PKCS#1 1.5 填充。 显然,SecKey
仅具有 PKCS1
、PKCS1MD2
、PKCS1MD5
和 的
。 也许 Microsoft 的 PKCS#1 1.5 != Apple 的 PKCS1,因此填充会影响加密的二进制输出。 我尝试使用 SecPadding
定义>PKCS1SHA1kSecPaddingPKCS1
并将 fOAEP
设置为 false
但它仍然不起作用。kSecPaddingPKCS1< /code> 相当于 PKCS#1 1.5 。 回到理论的绘图板...
其他新尝试的理论:
- iPhone 上的证书(.cer 文件)与服务器上的 PKCS#12 捆绑包(.pfx 文件)并不完全相同,因此它永远无法工作。 在不同的证书存储中安装了 .cer 文件,并且服务器加密的字符串往返就好了;
- 转换为 base-64 以及 POST 到服务器的行为会导致同一类往返中不存在的奇怪情况,因此我首先尝试了一些 URLEncoding/Decoding,然后从 iPhone 发布原始二进制文件,验证它是否相等,并得到相同的错误数据;
- 我的原始字符串是 125 字节,所以我认为它可能会在 UTF-8 中被截断(长镜头),所以我将其裁剪为 44 字节字符串,但没有结果;
- 回顾 System.Cryptography 库,以确保我使用了合适的类,并发现了“RSAPKCS1KeyExchangeDeformatter”,对新的前景感到高兴,但当它的行为完全相同时感到沮丧。
成功!
事实证明,我在 iPhone 模拟器上的钥匙串中存在一些问题,可以这么说,这些问题使水变得浑浊。 我删除了 ~/Library/Application Support/iPhone Simulator/User/Library/Keychains/keychain-2-debug.db
处的钥匙串数据库,以使其重新创建并且工作正常。 感谢您的所有帮助。 我认为这可能是一些简单但不明显的事情。 (我学到了两件事:1)从模拟器中卸载应用程序不会清除其钥匙串条目,2)定期重新启动。)
注意:钥匙串文件的通用路径取决于 iOS 版本: 〜/库/应用程序支持/iPhone模拟器/[版本]/Library/Keychains/keychain-2-debug.db 例如, 〜/库/应用程序支持/iPhone模拟器/4.3/Library/Keychains/keychain-2-debug.db
I've spent two days on this so far and combed through every source at my disposal, so this is the last resort.
I have an X509 certificate whose public key I have stored in the iPhone's keychain (simulator only at this point). On the ASP.NET side, I've got the certificate in the cert store with a private key. When I encrypt a string on the iPhone and decrypt it on the server, I get a CryptographicException
"Bad data." I tried the Array.Reverse
suggested in the RSACryptoServiceProvider
page on a longshot, but it did not help.
I have compared the base-64 strings on both sides and they're equal. I've compared the raw byte arrays after decoding and they too are equal. If I encrypt on the server using the public key, the byte array is different from the iPhone's version and readily decrypts using the private key. The raw plaintext string is 115 characters so it's within the 256-byte limitation of my 2048-bit key.
Here's the iPhone encryption method (pretty much verbatim from the CryptoExercise sample app's wrapSymmetricKey
method):
+ (NSData *)encrypt:(NSString *)plainText usingKey:(SecKeyRef)key error:(NSError **)err
{
size_t cipherBufferSize = SecKeyGetBlockSize(key);
uint8_t *cipherBuffer = NULL;
cipherBuffer = malloc(cipherBufferSize * sizeof(uint8_t));
memset((void *)cipherBuffer, 0x0, cipherBufferSize);
NSData *plainTextBytes = [plainText dataUsingEncoding:NSUTF8StringEncoding];
OSStatus status = SecKeyEncrypt(key, kSecPaddingNone,
(const uint8_t *)[plainTextBytes bytes],
[plainTextBytes length], cipherBuffer,
&cipherBufferSize);
if (status == noErr)
{
NSData *encryptedBytes = [[[NSData alloc]
initWithBytes:(const void *)cipherBuffer
length:cipherBufferSize] autorelease];
if (cipherBuffer)
{
free(cipherBuffer);
}
NSLog(@"Encrypted text (%d bytes): %@",
[encryptedBytes length], [encryptedBytes description]);
return encryptedBytes;
}
else
{
*err = [NSError errorWithDomain:@"errorDomain" code:status userInfo:nil];
NSLog(@"encrypt:usingKey: Error: %d", status);
return nil;
}
}
And here's the server-side C# decryption method:
private string Decrypt(string cipherText)
{
if (clientCert == null)
{
// Get certificate
var store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
foreach (var certificate in store.Certificates)
{
if (certificate.GetNameInfo(X509NameType.SimpleName, false) == CERT)
{
clientCert = certificate;
break;
}
}
}
using (var rsa = (RSACryptoServiceProvider)clientCert.PrivateKey)
{
try
{
var encryptedBytes = Convert.FromBase64String(cipherText);
var decryptedBytes = rsa.Decrypt(encryptedBytes, false);
var plaintext = Encoding.UTF8.GetString(decryptedBytes);
return plaintext;
}
catch (CryptographicException e)
{
throw(new ApplicationException("Unable to decrypt payload.", e));
}
}
}
My suspicion was that there was some encoding problems between the platforms. I know that one is big-endian and the other is little-endian but I don't know enough to say which is which or how to overcome the difference. Mac OS X, Windows, and the iPhone are all little-endian so that's not the problem.
New theory: if you set the OAEP padding Boolean to false, it defaults to PKCS#1 1.5 padding. Apparently, SecKey
only has SecPadding
definitions of PKCS1
, PKCS1MD2
, PKCS1MD5
, and PKCS1SHA1
. Perhaps Microsoft's PKCS#1 1.5 != Apple's PKCS1 and so the padding is affecting the binary output of the encryption. I tried using kSecPaddingPKCS1
with the fOAEP
set to false
and it still didn't work.kSecPaddingPKCS1
is equivalent to PKCS#1 1.5. Back to the drawing board on theories…
Other newly-tried theories:
- Certificate on iPhone (.cer file) is not exactly the same as the PKCS#12 bundle on the server (.pfx file) and so it could never work. Installed .cer file in different cert store and server-encrypted string roundtripped just fine;
- Conversion to base-64 and act of POSTing to server resulted in oddness that wasn't present in same class roundtrip so I first tried some URLEncoding/Decoding and then posted raw binary from iPhone, verified that it was equal, and got same bad data;
- My original string was 125 bytes so I thought it might be truncating in UTF-8 (long shot) so I cropped it down to a 44-byte string with no result;
- Looked back over the System.Cryptography library to make sure I was using an appropriate class and discovered `RSAPKCS1KeyExchangeDeformatter`, became elated at new prospects, and dejected when it behaved exactly the same.
Success!
It turned out that I had some cruft in my Keychain on the iPhone Simulator that was muddying the waters, so to speak. I deleted the Keychain DB at ~/Library/Application Support/iPhone Simulator/User/Library/Keychains/keychain-2-debug.db
to cause it to be re-created and it worked fine. Thank you for all of your help. Figures it would have been something simple but non-obvious. (Two things I learned: 1) uninstalling the app from the simulator does not clear its Keychain entries and 2) start absolutely fresh periodically.)
NOTE: The generic path for the keychain file is dependent on the iOS version:
~/Library/Application Support/iPhone Simulator/[version]/Library/Keychains/keychain-2-debug.db
e.g.,
~/Library/Application Support/iPhone Simulator/4.3/Library/Keychains/keychain-2-debug.db
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
嗯...第一步(正如您所说的那样)是使用 iPhone 和 C# 实现使用相同的初始化向量来加密相同的消息。 您应该得到相同的输出。 你说你没有,所以有问题。
这意味着:
我建议前两种可能性不大,但可能性很小。
您声明:“在不同的证书存储中安装了 .cer 文件,并且服务器加密的字符串往返得很好”...这并不能证明任何事情:所有这些证明的是,给定一组特定的随机数字,您可以成功加密/解密一个平台。 您不能保证两个平台都看到相同的随机数集。
所以我建议你把它降到尽可能低的水平。 检查两个平台上加密的直接(字节数组)输入和输出。 如果使用完全相同的(二进制)输入,您没有得到相同的输出,那么您就会遇到平台问题。 我认为这不太可能,所以我猜你会发现 IV 的解释不同。
Well... the first step (as you say you have done) is to encrypt the same messages with the same initialization vectors using both the iPhone and the C# implementation. You should get the same output. You said you didn't, so there is a problem.
This means either:
I would suggest the first two are unlikely, however they are remotely possible.
You state: "Installed .cer file in different cert store and server-encrypted string roundtripped just fine"... this doesn't prove anything: all this proves is that given a particular random set of numbers you can encrypt/decrypt successfully on one platform. You are not guaranteeing that both platforms are seeing the same set of random numbers.
So I suggest you take it down to the lowest level possible here. Inspect the direct (byte array) inputs and outputs of the encryption on both platforms. If with the exact same (binary) inputs you don't get the same output, then you have a platform problem. I think this is unlikely, so I'm guessing you will find that the IVs are being interpreted differently.
这是我在stackoverflow上的第一个回答,如果我做错了,请原谅我!
我无法给你一个完整的答案,但是当我尝试与 PHP 集成时,我遇到了非常相似的问题 - 似乎 Apple 的证书文件的格式与其他软件期望的格式(包括 openssl)有点不同。
以下是我在 PHP 中解密加密签名的方法 - 我实际上从传输的公钥中手动提取模数和 PK 并将其用于 RSA 内容,而不是尝试导入密钥:
生成此公钥的 Objective-c 是:
使用 Apple 示例项目 CryptoExercise 中的 SecKeyWrapper(您可以在此处查看该文件: https ://developer.apple.com/iphone/library/samplecode/CryptoExercise/listing15.html)
我希望这有帮助吗?
this is my first answer on stackoverflow, so please forgive me if I do it wrong!
I can't give you a complete answer, however I had very similar issues when I tried to integrate with PHP - it seems that the format of Apple's certificate files is a little different from that which other software expects (including openssl).
Here's how I decrypt an encrypted signature in PHP - I actually extract the modulus and PK from the transmitted public key manually and use that for the RSA stuff, rather than trying to import the key:
The objective-c that generates this public key is:
Using SecKeyWrapper from Apple's example project CryptoExercise (you can view the file here: https://developer.apple.com/iphone/library/samplecode/CryptoExercise/listing15.html)
I hope this helps?
这对你有帮助吗?
使用 .NET 和 .NET 的非对称密钥加密 C#
Will this help you ?
Asymmetric Key Encryption w/ .NET & C#
我相信你自己已经回答了这个问题。 问题肯定出在字节顺序上。
这是编写双向转换方法的一种可能方法:
这对您来说可能是一个很好的资源(维基百科除外):http://betterexplained.com/articles/understanding-big-and-little-endian-byte-order/
I believe you've answered the question yourself. The problem most certainly lies within the endianness.
This is a possible way of writing two-way conversion methods:
This might be a good resource for you (other than wikipedia): http://betterexplained.com/articles/understanding-big-and-little-endian-byte-order/