在使用RSA在iPhone上加密的C#中解密时遇到问题

时间:2009-07-15 20:16:13

标签: c# iphone rsa encryption

到目前为止,我花了两天的时间来处理这个问题,并且根据我所掌握的每一个来源进行梳理,所以这是最后的选择。

我有一个X509证书,其公钥存储在iPhone的钥匙串中(此时仅为模拟器)。在ASP.NET方面,我在证书库中获得了带有私钥的证书。当我在iPhone上加密字符串并在服务器上解密时,我收到CryptographicException“错误数据”。我尝试了RSACryptoServiceProvider页面上建议的Array.Reverse,但没有帮助。

我比较了两侧的64字符串,它们是相同的。我在解码后比较了原始字节数组,它们也相等。如果我使用公钥在服务器上加密,则字节数组与iPhone的版本不同,并且可以使用私钥轻松解密。原始明文字符串是115个字符,因此它在我的2048位密钥的256字节限制范围内。

这是iPhone加密方法(几乎逐字逐句CryptoExercise sample appwrapSymmetricKey方法):

+ (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));
        }
    }
}

我怀疑平台之间存在一些编码问题。 我知道一个是big-endian而另一个是little-endian但是我不知道哪个是哪个或如何克服差异。 Mac OS X,Windows和iPhone都是小端的,所以这不是问题。

新理论:如果将OAEP padding布尔值设置为false,则默认为PKCS#1 1.5填充。 SecKey仅对SecPaddingPKCS1PKCS1MD2PKCS1MD5PKCS1SHA1个定义。也许微软的PKCS#1 1.5!= Apple的PKCS1,因此填充正在影响加密的二进制输出。我尝试使用kSecPaddingPKCS1fOAEP设置为false,但仍然无法正常工作。显然,kSecPaddingPKCS1equivalent到PKCS# 1 1.5。回到理论上的绘图板......

其他新尝试的理论:

  1. iPhone上的证书(.cer文件)与服务器上的PKCS#12捆绑包(.pfx文件)不完全相同,因此它永远不会起作用。已安装的.cer文件在不同的证书存储区和服务器加密的字符串中进行了很好的处理;
  2. 转换为base-64并且POST到服务器的行为导致了同一类往返中不存在的奇怪现象,所以我首先尝试了一些URLEncoding / Decoding,然后从iPhone发布了原始二进制文件,验证它是相同的,并得到了相同的不良数据;
  3. 我的原始字符串是125个字节,所以我认为它可能在UTF-8(远射)中截断,所以我将其裁剪为44字节的字符串,没有结果;
  4. 回顾一下System.Cryptography库以确保我使用了一个合适的类并发现了`RSAPKCS1KeyExchangeDeformatter`,在新的潜在客户中变得兴高采烈,并且当它表现完全相同时会感到沮丧。
  5. 成功!

    事实证明,我在iPhone模拟器上的钥匙扣上有一些混乱,可以说是混淆了水域。我删除了~/Library/Application Support/iPhone Simulator/User/Library/Keychains/keychain-2-debug.db处的钥匙串数据库,以便重新创建它并且工作正常。谢谢你的帮助。数字本来是简单但不明显的。 (我学到的两件事:1)从模拟器卸载应用程序不会清除其钥匙串条目,2)定期开始绝对新鲜。)

    注意:keychain文件的通用路径取决于iOS版本: 〜/ Library / Application Support / iPhone Simulator / [version] /Library/Keychains/keychain-2-debug.db 例如。, 〜/ Library / Application Support / iPhone Simulator / 4.3 / Library / Keychains / keychain-2-debug.db

5 个答案:

答案 0 :(得分:3)

嗯......第一步(正如你所说的那样)是使用iPhone和C#实现使用相同的初始化向量加密相同的消息。你应该得到相同的输出。你说你没有,所以有一个问题。

这意味着:

  • RSA的iPhone实现不正确。
  • RSA的.NET实现不正确。
  • 密钥文件不同(或解释不同)。

我建议前两个不太可能,但它们是远程可能的。

您声明:“已安装的.cer文件在不同的证书存储区和服务器加密的字符串中进行了详细检查”......这并不能证明任何事情:所有这一切都证明,给定一组特定的随机数字可以加密/在一个平台上成功解密。您无法保证两个平台都能看到相同的随机数。

所以我建议你把它降到最低水平。在两个平台上检查加密的直接(字节数组)输入和输出。如果使用完全相同的(二进制)输入,则无法获得相同的输出,那么您就遇到了平台问题。我认为这不太可能,所以我猜你会发现IV的解释方式不同。

答案 1 :(得分:1)

这是我在stackoverflow上的第一个答案,所以如果我做错了,请原谅我!

我无法给你一个完整的答案,但是当我尝试与PHP集成时,我遇到了类似的问题 - 似乎Apple的证书文件格式与其他软件预期的格式略有不同(包括openssl)

以下是我在PHP中解密加密签名的方法 - 实际上我手动从传输的公钥中提取模数和PK,并将其用于RSA内容,而不是尝试导入密钥:

// Public key format in hex (2 hex chars = 1 byte):
//30480241009b63495644db055437602b983f9a9e63d9af2540653ee91828483c7e302348760994e88097d223b048e42f561046c602405683524f00b4cd3eec7e67259c47e90203010001
//<IGNORE><--------------------------------------------- MODULUS --------------------------------------------------------------------------><??>< PK > 
// We're interested in the modulus and the public key.
// PK = Public key, probably 65537

// First, generate the sha1 of the hash string:
$sha1 = sha1($hashString,true);

// Unencode the user's public Key:
$pkstr = base64_decode($publicKey);
// Skip the <IGNORE> section:
$a = 4;
// Find the very last occurrence of \x02\x03 which seperates the modulus from the PK:
$d = strrpos($pkstr,"\x02\x03");
// If something went wrong, give up:
if ($a == false || $d == false) return false;
// Extract the modulus and public key:
$modulus = substr($pkstr,$a,($d-$a));
$pk = substr($pkstr,$d+2);

// 1) Take the $signature from the user
// 2) Decode it from base64 to binary
// 3) Convert the binary $pk and $modulus into (very large!) integers (stored in strings in PHP)
// 4) Run rsa_verify, from http://www.edsko.net/misc/rsa.php
$unencoded_signature = rsa_verify(base64_decode($signature), binary_to_number($pk), binary_to_number($modulus), "512");

//Finally, does the $sha1 we calculated match the $unencoded_signature (less any padding bytes on the end)?
return ($sha1 == substr($unencoded_signature,-20)); // SHA1 is only 20 bytes, whilst signature is longer than this.  

生成此公钥的objective-c是:

NSData * data = [[SecKeyWrapper sharedWrapper] getPublicKeyBits];
[req addValue:[data base64Encoding] forHTTPHeaderField: @"X-Public-Key"];
data = [[SecKeyWrapper sharedWrapper] getSignatureBytes:[signatureData dataUsingEncoding:NSUTF8StringEncoding]];
[req addValue:[data base64Encoding] forHTTPHeaderField: @"X-Signature"];

使用Apple的示例项目CryptoExercise中的SecKeyWrapper(您可以在此处查看文件:https://developer.apple.com/iphone/library/samplecode/CryptoExercise/listing15.html

我希望这有帮助吗?

答案 2 :(得分:0)

这会对你有帮助吗?

Asymmetric Key Encryption w/ .NET & C#

  • 对不起,短信,时间紧迫等等。无论如何,看到你的Twitter请求帮助..这显示我如何使用PHP并在.NET上解密,simliar。我注意到你的解密类比我的稍微不同,所以这篇文章可能有所帮助。

答案 3 :(得分:-2)

我相信你自己已经回答了这个问题。问题当然在于字节序。

这是编写双向转换方法的可能方法:

short convert_short(short in)
{
 short out;
 char *p_in = (char *) &in;
 char *p_out = (char *) &out;
 p_out[0] = p_in[1];
 p_out[1] = p_in[0];  
 return out;
}

long convert_long(long in)
{
 long out;
 char *p_in = (char *) &in;
 char *p_out = (char *) &out;
 p_out[0] = p_in[3];
 p_out[1] = p_in[2];
 p_out[2] = p_in[1];
 p_out[3] = p_in[0];  
 return out;
} 

对于您(维基百科除外),这可能是一个很好的资源:http://betterexplained.com/articles/understanding-big-and-little-endian-byte-order/

答案 4 :(得分:-3)

因为你控制双方,我的建议(如果你不能让图书加密算法在两个平台上一起工作)就是使用相同的算法在双方自己编写加密。

这样你就拥有了控制权,并且能够调试加密内部,看看出了什么问题。

这是最后的手段(当然),但可能花费的时间少于你已经花费的三天,并且很有可能获得成功

HTH