【问题标题】:Having trouble decrypting in C# something encrypted on iPhone using RSA在 C# 中解密使用 RSA 在 iPhone 上加密的内容时遇到问题
【发布时间】:2010-11-11 03:33:20
【问题描述】:

到目前为止,我已经为此花费了两天时间,并梳理了我可以使用的所有来源,所以这是最后的手段。

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

我比较了两边的 base-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));
        }
    }
}

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

新理论:如果将 OAEP 填充布尔值设置为 false,则默认为 PKCS#1 1.5 填充。 SecKey 只有SecPadding 定义PKCS1PKCS1MD2PKCS1MD5PKCS1SHA1。也许 Microsoft 的 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`,对新的前景感到高兴,当它的行为完全相同时感到沮丧。

成功了!

事实证明,我在 iPhone 模拟器上的钥匙串中有一些东西弄脏了水,可以这么说。我在~/Library/Application Support/iPhone Simulator/User/Library/Keychains/keychain-2-debug.db 删除了钥匙串数据库,以使其重新创建并且工作正常。感谢您的所有帮助。数字这将是一些简单但不明显的事情。 (我学到了两件事:1)从模拟器中卸载应用程序并不会清除其钥匙串条目;2)定期重新启动。)

注意:钥匙串文件的通用路径取决于 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

【问题讨论】:

    标签: c# iphone rsa encryption


    【解决方案1】:

    我相信您自己已经回答了这个问题。问题肯定出在字节顺序上。

    这是写双向转换方法的一种可能方式:

    short convert_short(short in)
    {
     short out;
     char *p_in = (char *) ∈
     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 *) ∈
     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/

    【讨论】:

    • 很高兴听到并感谢您的回复!请原谅我的无知,但我将如何使用这些转换方法呢?我假设它们适用于 iPhone,那么我应该在哪里将其插入我的方法中?
    • 原来平台之间的字节顺序是 PowerPC 时代的遗物。基于 x86 架构的 Mac OS X 是 little-endian——与 Windows 相同。 ARM 架构上的 iPhone 也是 little-endian,我可以从搜索中确定。
    • 无论如何,字节序在这里都不是问题。您的所有操作都涉及原始字节,而不是多字节数字。
    【解决方案2】:

    这对你有帮助吗?

    Asymmetric Key Encryption w/ .NET & C#

    • 很抱歉这篇短文、时间限制等等。无论如何,看到你的 Twitter 请求帮助.. 这显示了我是如何使用 PHP 做到这一点并在 .NET 上解密的,类似。我注意到您的解密类与我的略有不同,因此本文可能会有所帮助。

    【讨论】:

    • 遗憾的是,OpenSSL 在 iPhone 上不可用,所以它对我没有帮助。但是感谢您的尝试!
    • 我的目的是尝试解密例程以匹配我的。我发现 509Certificate2 更好。
    • 那是我的错。在上面的代码示例中,有一个名为“clientCert”的私有实例变量,其类型为 X509Certificate2。我未能将其包含在列表中。抱歉……
    【解决方案3】:

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

    这意味着:

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

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

    您说:“在不同的证书存储中安装 .cer 文件,并且服务器加密的字符串往返就好了”...这并不能证明任何事情:这一切都证明了给定一组特定的随机数字,您可以加密/在一个平台上成功解密。您不能保证两个平台看到的是同一组随机数。

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

    【讨论】:

    • 感谢您的回复!我在 .NET 或 Cocoa 库中找不到可以为 RSA 加密或解密设置 IV 的地方。我认为 IV 仅适用于对称算法。我也不会认为 1 或 2 都是可能的,但我也无法理解 X509 证书的公钥在两个平台上的解释会不同。但是我从手机中取出证书并将其放入服务器并在那里成功,所以我不知道该怎么想。我将查看每个平台中的公钥详细信息,以确保它们是相同的,
    • 但我很确定,相同的公钥可能会在不同的过程中为相同的输入生成不同的输出,这并不奇怪。这就是 PKCS#1 填充的目的,Apple 工程师证实了这一点:lists.apple.com/archives/apple-cdsa/2009/Jul/msg00032.html
    • 抱歉 - 当我说 IV 时,我可能误用了术语 - 这里已经很晚了 :) 我的意思基本上是加密密钥:您从 .NET 中的 ExportParameters 方法获得的数据或您的内容iPhone 上的“key”参数。我认为确保它们相同(当然不包括私人部分)是值得的。
    • 我给你答案是因为你敦促一个新的开始。
    • 我的两分钱在这里:IV 用于分组密码,不一定是对称算法。
    【解决方案4】:

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

    我不能给你一个完整的答案,但是当我尝试与 PHP 集成时,我遇到了非常相似的问题 - 苹果的证书文件的格式似乎与其他软件所期望的有点不同(包括 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

    我希望这会有所帮助?

    【讨论】:

    • 正如 Jon Grant 建议的那样,我将自己检查公钥位,但事实是 iPhone 正在读取与应用程序捆绑在一起的 X509 证书(不是在手机本身上生成的),我觉得这令人难以置信手机会以不同的方式读取公钥。我会通过检查字节来解决这个问题,但如果是这样的话,我会感到非常惊讶。
    猜你喜欢
    • 2016-12-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-29
    相关资源
    最近更新 更多