到目前为止,我已经花了两天时间研究这个问题,并梳理了我可以使用的所有资源,所以这是最后的手段。
我有一个 X509 证书,其公钥已存储在 iPhone 的钥匙串中(此时仅限模拟器)。在 ASP.NET 方面,我已在证书存储区中使用私钥获取了证书。当我在 iPhone 上加密一个字符串并在服务器上解密它时,我得到一个CryptographicException
“糟糕的数据。”我尝试过Array.Reverse
建议在RSACryptoServiceProvider http://msdn.microsoft.com/en-us/library/system.security.cryptography.rsacryptoserviceprovider.aspx页面上的长镜头,但它没有帮助。
我比较了两边的 base-64 字符串,它们是相等的。我比较了解码后的原始字节数组,它们也相等。如果我使用公钥在服务器上加密,则字节数组与 iPhone 的版本不同,并且很容易使用私钥解密。原始明文字符串有 115 个字符,因此它在我的 2048 位密钥的 256 字节限制之内。
这是 iPhone 的加密方法(几乎是逐字逐句地来自CryptoExercise 示例应用程序 http://developer.apple.com/iphone/library/samplecode/CryptoExercise/listing15.html's 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
只有SecPadding
的定义PKCS1
, PKCS1MD2
, PKCS1MD5
, and PKCS1SHA1
。也许 Microsoft 的 PKCS#1 1.5 != Apple 的 PKCS1,因此填充会影响加密的二进制输出。我尝试使用kSecPaddingPKCS1
与fOAEP
set to false
但它仍然不起作用。 显然,kSecPaddingPKCS1
is 相等的 http://lists.apple.com/archives/Apple-cdsa/2009/Jul/msg00027.html到 PKCS#1 1.5。回到理论的绘图板......
其他新尝试的理论:
- iPhone 上的证书(.cer 文件)与服务器上的 PKCS#12 捆绑包(.pfx 文件)不完全相同,因此它永远无法工作。在不同的证书存储中安装了 .cer 文件,并且服务器加密的字符串往返就好了;
- 转换为 base-64 以及 POST 到服务器的行为会导致同一类往返中不存在的奇怪情况,因此我首先尝试了一些 URLEncoding/Decoding,然后从 iPhone 发布原始二进制文件,验证它是否相等,并得到相同的错误数据;
- 我的原始字符串是 125 字节,所以我认为它可能会在 UTF-8 中被截断(长镜头),所以我将其裁剪为 44 字节字符串,但没有结果;
- 回顾 System.Cryptography 库,以确保我使用了合适的类,并发现了“RSAPKCS1KeyExchangeDeformatter”,对新的前景感到高兴,但当它的行为完全相同时感到沮丧。
Success!
事实证明,我的 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