[IOS]IOS的key pair&sign与java互相转换
这个部分研究用了我很多时间,终于找到一个方法.
一.核心:
IOS的key pair和java的是不通的,没办法直接转换,原因在于他们的DER(Distinguished Encoding Rules)是不一样的.IOS接收的是ASN.1格式,而java接收的是X509格式.
解决思路就是把ASN.1转换成X509,反之亦然.
(同样道理,private key的格式也是不通的,iOS 的是PKCS1,而java的是PKCS8)
二.转换:
具体流程就如上所示.
这里面需要用到另外一个库
参考: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库.
补充:后来看到有同事使用cocopod来引用了openssl的库,应该是有生成EC曲线的API的,但还没尝试.
三.有可能有坑的地方:
首先,这样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进行编码来进行签名内容核对.