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 数字(n 和e 用于 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 的代码所示(SecItemAdd
,SecItemCopyMatching
).
我还验证了 C# 代码可以成功解密我在 iOS 端加密的数据。
我需要将 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 数字(n 和e 用于 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 的代码所示(SecItemAdd
,SecItemCopyMatching
).
我还验证了 C# 代码可以成功解密我在 iOS 端加密的数据。