码屋 2018-12-13
这个部分研究用了我很多时间,终于找到一个方法.
一.核心:
IOS的key pair和java的是不通的,没办法直接转换,原因在于他们的DER(Distinguished Encoding Rules)是不一样的.IOS接收的是ASN.1格式,而java接收的是X509格式.
解决思路就是把ASN.1转换成X509,反之亦然.
(同样道理,private key的格式也是不通的,iOS 的是PKCS1,而java的是PKCS8)
二.转换:
![[IOS]IOS的key pair&sign与java互相转换 [IOS]IOS的key pair&sign与java互相转换](https://cdn.ancii.com/article/image/v1/CJ/su/DY/YDsJuCxciS9WuzwhFy-bH1XTkGooxXX_mbkq3YDfHkstVRn1dSS4fCkLE_bNs80-zmSIBqaFYNCwA-9IvkEm8HD4IwgvVk93aQqcv6Bwmhxo3hwuUta9EGbCzGT4mcBRCIS_piJhGEiRg-VaG_qtN_WAZJ1Z7KsFQvMD9TwrvTEnOyMm5LwZg44alyZ7VEvbyIDiY0KiwumLWB5_QH4u5I9sp4el8R1AVMi1lZaPpghKWlEQbc2IMJuhHZ7hSG9E.png)
具体流程就如上所示.
这里面需要用到另外一个库
参考:https://digitalleaves.com/blog/2015/10/sharing-public-keys-between-ios-and-the-rest-of-the-world/
"Exporting iOS-generated public keys to the outside world."这部分
这是GitHub:https://github.com/DigitalLeaves/CryptoExportImportManager
分析:
这个是export的总流程:
@IBAction func generateAndExportPublicKey(_ sender: AnyObject) {
self.view.isUserInteractionEnabled = false
self.textView.text = "Trying to get public key data from Keychain first..."
let keyType = getKeyTypeFromSegmentedControl()
if let publicKeyData = getPublicKeyData(kExportKeyTag + keyType) {
self.textView.text = self.textView.text + "Success!\nPublic key raw bytes: \(publicKeyData.hexDescription)\n\n"
self.exportKeyFromRawBytesAndShowInTextView(publicKeyData)
self.view.isUserInteractionEnabled = true
} else {
self.textView.text = self.textView.text + "Failed! Will try to generate keypair...\n"
createSecureKeyPair(kExportKeyTag + keyType) { (success, pubKeyData) -> Void in
if success && pubKeyData != nil {
self.textView.text = self.textView.text + "Success!\nPublic key raw bytes:\(pubKeyData!)\n"
self.exportKeyFromRawBytesAndShowInTextView(pubKeyData!)
} else {
self.textView.text = self.textView.text + "Oups! I was unable to generate the keypair to test the export functionality."
}
self.view.isUserInteractionEnabled = true
}
}
}这里生成key pair:
func createSecureKeyPair(_ keyTag: String, completion: ((_ success: Bool, _ pubKeyData: Data?) -> Void)? = nil) {
// private key parameters
let privateKeyParams: [String: AnyObject] = [
kSecAttrIsPermanent as String: true as AnyObject,
kSecAttrApplicationTag as String: keyTag as AnyObject,
]
// private key parameters
let publicKeyParams: [String: AnyObject] = [
kSecAttrApplicationTag as String: keyTag as AnyObject,
kSecAttrIsPermanent as String: true as AnyObject
]
// global parameters for our key generation
let parameters: [String: AnyObject] = [
kSecAttrKeyType as String: getKeyTypeFromSegmentedControl() as AnyObject,
kSecAttrKeySizeInBits as String: getKeyLengthFromSegmentedControl() as AnyObject,
kSecPublicKeyAttrs as String: publicKeyParams as AnyObject,
kSecPrivateKeyAttrs as String: privateKeyParams as AnyObject,
]
// asynchronously generate the key pair and call the completion block
DispatchQueue.global(qos: DispatchQoS.QoSClass.default).async { () -> Void in
var pubKey, privKey: SecKey?
let status = SecKeyGeneratePair(parameters as CFDictionary, &pubKey, &privKey)
if status == errSecSuccess {
DispatchQueue.main.async(execute: {
print("Successfully generated keypair!\nPrivate key: \(privKey)\nPublic key: \(pubKey)")
let publicKeyData = self.getPublicKeyData(kExportKeyTag + self.getKeyTypeFromSegmentedControl())
completion?(true, publicKeyData)
})
} else {
DispatchQueue.main.async(execute: {
print("Error generating keypair: \(status)")
completion?(false, nil)
})
}
}
}用的也是ios的api,可以参考Apple的文档:
https://developer.apple.com/documentation/security/certificate_key_and_trust_services/keys
/generating_new_cryptographic_keys
这样重新获取public key:(根据generate时的key tag)
func getPublicKeyData(_ keyTag: String) -> Data? {
let parameters = [
kSecClass as String: kSecClassKey,
kSecAttrKeyType as String: getKeyTypeFromSegmentedControl(),
kSecAttrKeyClass as String: kSecAttrKeyClassPublic,
kSecAttrApplicationTag as String: keyTag,
kSecReturnData as String: true
] as [String : Any]
var data: AnyObject?
let status = SecItemCopyMatching(parameters as CFDictionary, &data)
if status == errSecSuccess {
return data as? Data
} else { print("Error getting public key data: \(status)"); return nil }
}这里就是转换成PEM(X509的文件格式):
func exportKeyFromRawBytesAndShowInTextView(_ rawBytes: Data) {
let keyType = getKeyTypeFromSegmentedControl()
let keySize = getKeyLengthFromSegmentedControl()
let exportImportManager = CryptoExportImportManager()
if let exportableDERKey = exportImportManager.exportPublicKeyToDER(rawBytes, keyType: keyType, keySize: keySize) {
self.textView.text = self.textView.text + "Exportable key in DER format:\n\(exportableDERKey.hexDescription)\n\n"
print("Exportable key in DER format:\n\(exportableDERKey.hexDescription)\n")
let exportablePEMKey = exportImportManager.PEMKeyFromDERKey(exportableDERKey)
self.textView.text = self.textView.text + "Exportable key in PEM format:\n\(exportablePEMKey)\n\n"
print("Exportable key in PEM format:\n\(exportablePEMKey)\n")
} else {
self.textView.text = self.textView.text + "Unable to generate DER key from raw bytes."
}
}经过上面的步骤,就获得一串经过base64编码的string,如下:
Exportable key in PEM format: -----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE0CglywDQCmCdLNT8tiCJm75poZQo f2w+zdKCtyphK5LEgbWC8RUYojgFU0AEDdBVT5U4fBZwjBkjOnzCGCQEAQ== -----END PUBLIC KEY-----
切去"-----BEGIN PUBLIC KEY-----""-----END PUBLIC KEY-----"然后传给java,java用base64 decode,获得byte[],就可以顺利generate出public key.
**注意:这个库也有个缺憾,就是没有192的曲线头
private let kCryptoExportImportManagerSecp256r1CurveLen = 256 private let kCryptoExportImportManagerSecp256r1header: [UInt8] = [0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01, 0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07, 0x03, 0x42, 0x00] private let kCryptoExportImportManagerSecp256r1headerLen = 26
所以暂时转换不了192的曲线.
附加:使用openssl命令生成的PEM,取出string也可以转给java,不过ios的openssl库没有EC加密,只有RSA.所以这里不用openssl库.
三.有可能有坑的地方:
首先,这样ios生成出来的PEM可以转换成java,但是打印发现java的key pair长度是91,但是ios的是123.后来发现第三方库生成的public key raw byte长度也是91,但是原封代码嵌入到我的项目中就不知道为什么出现123长度.而且这个长度base64 decode后也是可以转成java的public key的,十分神奇.
四.签名:
1.首先一下这篇文章介绍了如何进行非对称加密和签名和认证:
https://digitalleaves.com/blog/2015/10/asymmetric-cryptography-in-swift/
GitHub:https://github.com/DigitalLeaves/AsymmetricCrypto
我使用的也是上面这个库的签名方法.
这是Apple的签名guide:https://developer.apple.com/documentation/security/certificate_key_and_trust_services/keys/signing_and_verifying?language=objc
以下是代码:
func signWithPrivate(_ text: String) -> String?{
let privateKey: SecKey = self.getPrivateKeyReference("your generate tag")!
if #available(iOS 10.0, *) {
let algorithm: SecKeyAlgorithm = .ecdsaSignatureMessageX962SHA1
guard SecKeyIsAlgorithmSupported(privateKey, .sign, algorithm) else {
print("Can't not sign")
return nil
}
var error: Unmanaged<CFError>?
let data = text.data(using: String.Encoding.utf8)
guard let signature = SecKeyCreateSignature(privateKey,
algorithm,
data! as CFData,
&error) as Data? else {
print("Sign fail")
return nil
}
// let sign = String(data: signature.base64EncodedData() as Data, encoding: String.Encoding.utf8)
return signature.base64EncodedString()
} else {
let signature = self.signWithPrivateKeyUnderIOS10("Test123", privateKey)
return signature
}
}
private func signWithPrivateKeyUnderIOS10(_ text: String, _ key: SecKey) -> String? {
var digest = Data(count: Int(CC_SHA1_DIGEST_LENGTH))
let data = text.data(using: .utf8)!
let _ = digest.withUnsafeMutableBytes { digestBytes in
data.withUnsafeBytes { dataBytes in
CC_SHA1(dataBytes, CC_LONG(data.count), digestBytes)
}
}
var signature = Data(count: SecKeyGetBlockSize(key) * 4)
var signatureLength = signature.count
let result = signature.withUnsafeMutableBytes { signatureBytes in
digest.withUnsafeBytes { digestBytes in
SecKeyRawSign(key,
SecPadding.PKCS1SHA1,
digestBytes,
digest.count,
signatureBytes,
&signatureLength)
}
}
let count = signature.count - signatureLength
signature.removeLast(count)
guard result == noErr else {
return nil;
}
// let str123 = String(data: signature.base64EncodedData() as Data, encoding: String.Encoding.utf8)
// return str123
return signature.base64EncodedString()
}上面的方法需要在ios10以上使用,下面的是适配ios10以下.签名后是base64编码了的,java那边验证的话也是需要先对这个签名进行解码.
另外需要重点说明一下,如果需要加密的内容含有类等元素,直接转成string来签名,签名后是验证不上的.因为转string过程中失真了.这个时候就需要编码一下,把要签名的内容先编码,然后再签名,这样最稳妥.那么对于java那边,同样需要对content进行编码来进行签名内容核对.