Swift 和 C# 之间的 RSA 加密

RSA encryption between Swift and C#

我需要将 Apple 客户端构建到用 .Net/C# 编写的现有服务器。客户端应在 OS X 和 iOS 上 运行。部分通信是使用加密数据完成的,C# 源类似于

    string privateKey = "xyz…=" // Base64 encoded
    string publicKey  = "abc…=" // Base64 encoded

    byte[] decrypt(byte[] encryptedBytes)
    {
        CspParameters cspParams = new CspParameters { ProviderType = 1 /* PROV_RSA_FULL */ };
        RSACryptoServiceProvider rsaProvider = new RSACryptoServiceProvider(cspParams);
        rsaProvider.ImportCspBlob(Convert.FromBase64String(privateKey));

        return rsaProvider.Decrypt(encryptedBytes, false);
    }

这看起来很简单,但我找不到在 Swift 中实现 encrypt 对应的方法。

publicKey 数据仅由模数和指数组成。我可以使用 rsaProvider.ExportParameters(false).Modulus 从 public 键中提取模数,指数像往常一样是 [1, 0, 1]

如何在 OS X / iOS 客户端的 Swift 应用程序中使用模数(或 publicKey 字符串常量本身),以及我如何加密可以在 C# 服务器端解密的纯文本?

我知道 Apple 不太喜欢以这种低级方式引入加密密钥,但似乎有可能将这样的密钥导入钥匙串,然后使用它进行加密。

我有积木(SecItem…()SecKeyEncrypt() 等),但我无法搭建起来 运行宁。

像这样的东西应该适用于 iOS。

对于 OS X,您需要一种稍微不同的方法(SecItemImport 而不是 SecItemAdd)。

import UIKit
import Security

let publicKey = "MIIBCgKCAQEAxWp6GqUOG3xuMhaE0Eeb0eOqbPHE2lRQ53qg2A1rInWdBTVtQaU82Yurv6rFoz++jswiHf3VBy3plhalF+1CTruuzSqVUjpeWTGFppoIym8andVtGLP5mN56Ks7z8VxwQ4MvmM5lGqw3YX6NWVNirWTGdJsqiplmhkAZXFAY43ivwTFSbQ4Uhx7SA0PK537V6je5MJ9edaWpKc1HoGH/bZG9/qrunv2Wam0w9qb8/TOsNvxdgBFs9WZaU0amkNb4h94y9ZrJKYsRGTngDAZ/uA+WK5ZM+Dz3GelsDUErvlUlswLyhQKYPPGn+QlVbMF4drUZ6piZWPmvpY2a/iyRcwIDAQAB"

let keyData = NSData(base64EncodedString: publicKey, options: NSDataBase64DecodingOptions.IgnoreUnknownCharacters)

var dictionary: [NSString: AnyObject] = [
    kSecClass: kSecClassKey,
    kSecAttrKeyType: kSecAttrKeyTypeRSA,
    kSecAttrKeyClass: kSecAttrKeyClassPublic,
    kSecAttrApplicationTag: "mypubkeyappspecifictag",
    kSecValueData: keyData!,
    kSecReturnRef: true
];

var err = SecItemAdd(dictionary, nil);

if ((err != noErr) && (err != errSecDuplicateItem)) {
    print("error loading public key");
}

var keyRef: AnyObject?;
err = SecItemCopyMatching(dictionary, &keyRef);
if (err == noErr) {
    if let keyRef = keyRef as! SecKeyRef? {
        let plaintext = "12345";

        let plaintextLen = plaintext.lengthOfBytesUsingEncoding(NSUTF8StringEncoding);
        let plaintextBytes = [UInt8](plaintext.utf8);

        var encryptedLen: Int = SecKeyGetBlockSize(keyRef);
        var encryptedBytes = [UInt8](count: encryptedLen, repeatedValue: 0);

        err = SecKeyEncrypt(keyRef, SecPadding.PKCS1, plaintextBytes, plaintextLen, &encryptedBytes, &encryptedLen);
        if (err != noErr) {
            print(encryptedBytes);
        }
    }
}

SecItemDelete(dictionary);

请注意,iOS 的 public 密钥应从 ASN1 前导码中删除,如 here 所指定。

终于,我找到了正确的组合。 Alex Skalozub 的回答对这个过程帮助很大。

这是我的一步一步的过程:

它以服务器端提供的 public 密钥字符串开头。这是以前通过这种方式生成的:(C# .Net)

CspParameters cspParams = new CspParameters { ProviderType = 1 /* PROV_RSA_FULL */ };

// generate key pair with given size
RSACryptoServiceProvider rsaProvider = new RSACryptoServiceProvider(4096, cspParams);

string publicKey = Convert.ToBase64String(rsaProvider.ExportCspBlob(false));
string privateKey = Convert.ToBase64String(rsaProvider.ExportCspBlob(true));

这些键在内部存储为字符串常量,然后在从其 base64 表示中提取它们之后,在服务器初始化时导入:

CspParameters cspParams = new CspParameters { ProviderType = 1 /* PROV_RSA_FULL */ };
// no key generation this time:
RSACryptoServiceProvider rsaProvider = new RSACryptoServiceProvider(cspParams);
rsaProvider.ImportCspBlob(Convert.FromBase64String(publicKey));

由于这些 "CSP Blobs" 是专有的 Microsoft 加密 API 结构,我不得不使用它们并提取低级 RSA 数字(ne 用于 public 密钥;d 用于私钥在这里并不是真正需要的):(C# .Net)

RSAParameters pub = rsaProvider.ExportParameters(false);
string n = Convert.ToBase64String(pub.Modulus);
string e = Convert.ToBase64String(pub.Exponent);

RSAParameters priv = rsaProvider.ExportParameters(true);
string d = Convert.ToBase64String(priv.D);

下一步是根据数字构建与 Apple 兼容(即不符合标准)的密钥字符串。我将 Python 与 PyCrypto 包一起使用:

import base64
from Crypto.Util import asn1

def b64ToNum(b64str):
  byteStr = base64.b64decode(b64str)
  num = 0L
  for digit in byteStr:
    num = num * 256 + ord(digit)
  return num

n_b64 = "..."   # copied from C# output
e_b64 = "..."   # copied from C# output

n = b64ToNum(n_b64)
e = b64ToNum(e_b64)

seq = asn1.DerSequence()
seq[:] = [ n, e ]  ## Standard would be [ 0, n, e ] !!!

print s.encode("base64").replace("\n", "")

这会生成一个 base64 编码的 public 密钥字符串,可用于插入 iOS 钥匙链,如 Alex 的代码所示(SecItemAddSecItemCopyMatching).

我还验证了 C# 代码可以成功解密我在 iOS 端加密的数据。