How can I generate an ed25519 key pair in OpenSSH format with swift? - swift

I tried to generate the keys with different approaches, get the raw data but I can't find any library that transform these bytes into OpenSSH compatible public and private keys.
Approach 1:
let privateKeyParams: [String: Any] = [
kSecAttrIsPermanent as String: false,
]
let parameters: [String: Any] = [
String(kSecClass): kSecClassKey,
String(kSecAttrKeyType): kSecAttrKeyTypeECSECPrimeRandom,
String(kSecAttrKeySizeInBits): 256,
kSecPrivateKeyAttrs as String: privateKeyParams
]
var error: Unmanaged<CFError>?
guard let privateKey = SecKeyCreateRandomKey(parameters as CFDictionary, &error) else {
throw error!.takeRetainedValue() as Error
}
guard let publicKey = SecKeyCopyPublicKey(privateKey) else {
throw KeyGeneratorError(description: "Failed to generate the public key")
}
guard let privateKeyData = SecKeyCopyExternalRepresentation(privateKey, nil) as? Data else {
throw KeyGeneratorError(description: "Failed to extract data from key")
}
guard let publicKeyData = SecKeyCopyExternalRepresentation(publicKey, nil) as? Data else {
throw KeyGeneratorError(description: "Failed to extract data from key")
}
Approach 2 (using CryptoKit):
let privateKey = Curve25519.KeyAgreement.PrivateKey()
let publicKey = privateKey.publicKey
let privateKeyData = privateKey.rawRepresentation
let publicKeyData = publicKey.rawRepresentation
Approach 3 (using SwCrypt library):
let (privateKey, publicKey) = try SwCrypt.CC.EC.generateKeyPair(256)
I always get an object of type "Data" but I cannot transform this data into an OpenSSH string of this type:
Private Key:
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABAZ9c1i+q
9gMY51eotxWjeRAAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIPAWeGw39CgOruIW
i0N/yji4+K/r/VPdwgg6BQD3e1Y5AAAAoJ9WV+HMYQx4bPyjTprzDQfLmcl3BiCC6hSe93
c29g2jd8zMSr4fVH0ECby6iaLxlwYbvRz+gdIqpI8MpxEJ+XBzWYNWM8r9tD7wS4rHICXg
rjXnXfq6OUfiq+RzYFrn6mLYE3PwA//IsNyrnk9tHk9y4E0dj1JlYHQ4Y5LBOg3DsosTNo
qTVa9+7XrCuSxDMaXqVMgSMbBo0H8bolX/4uM=
-----END OPENSSH PRIVATE KEY-----
Public Key:
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPAWeGw39CgOruIWi0N/yji4+K/r/VPdwgg6BQD3e1Y5

Related

How to create SecKey from pem file in Swift

I'm trying to create a SecKey using SecKeyCreateWithData. I'm loading the private key from a .pem file, and I create the key pair like this.
openssl genrsa -des3 -out WM_IO_my_rsa_key_pair 2048
Here is the code, but I get a SecKeyCreate init(RSAPublicKey) failed: -50 error.
import UIKit
import Security
func createKey(pem: String) -> SecKey? {
let attributes: [NSObject : NSObject] = [
kSecAttrKeyType: kSecAttrKeyTypeRSA,
kSecAttrKeyClass: kSecAttrKeyClassPrivate,
kSecAttrKeySizeInBits: NSNumber(value: 2048)
]
let privateKey = pem
.replacingOccurrences(of: "-----BEGIN PRIVATE KEY-----", with: "")
.replacingOccurrences(of: "-----END PRIVATE KEY-----", with: "")
.split(separator: "\n").joined()
guard let privateKeyData = Data(base64Encoded: privateKey, options: .ignoreUnknownCharacters) else {
return nil
}
var error: Unmanaged<CFError>?
return SecKeyCreateWithData(privateKeyData as CFData, attributes as CFDictionary, &error)
}
let privateKey = """
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCpySPOXAAsi1jN
vxDrNfinFOUZlLk38P8BF2ukJWU7OOAXol6TG8fFrn8O+UVW4jjPTZjLT9G6385N
iervmwopS5CtHS2p0ptLm1ExigZAuS/LTaIkw3yugr4b+5roEwL9nFW0Qfd4o/ds
3lKPtdwtO0rfqTfuZ6CqcQP0sTJcky7nk3mIyz6jZOZE2QLL/oF/oUKsi5Wt5uJp
uluZ+gt087Ftm1oGKR8lZt5bucbD7WZnmlckNyUh7O973mqqDZ6zTwrgNuxIBmtr
afNetsgZv6/We56IWenKayWW1xA9icPhmViJtzPU3Gu8HW/FMovyRZAMFD1Yzf+L
eiSGC5x9AgMBAAECggEAGOrN+HEEDYV9yOQrhXlsPokQfkqXTCBwLCbVw0dBrglQ
yecoXgqLrPVI7Fek13VnxPSsYdoa+4Pw8Ow2sGdefGT8nItVskCYTeZDajPJayJC
Y2HHVAHkgG/rmou4VirEG5gf885ilS/rFe0izLhx0amL+XsKHFDVAW6ImBaRP4iU
82fkZtLlh4DLi6w/oNu3ePZkMeTl4aAkJH8gRy2Mzsyl0DNi5+mH0du12BVSca58
iAXEhufIweTvDqb3lx0CR5+0+dbk/MeIvoklDCkPAlK9IOXr9m0UYjWXNYTADLi8
PmQf8k7pf74ma/2bFuT8LakIA6z3HEwLO7iKm4VVOQKBgQDYVG7AJWJL3DDMCpz3
csP+fKccr72Q3VKCZ5uq0jC/Wp4jfNip/b76Rt3xb8nqzgIDntu+B6Vzz6HPB5d4
j8510lNZrRzIQj0unxOx7pJHIGHhGv2dJ00IqVa5aNL8aCNhxXhgR3vIojyoo8R9
nc4WAQu4ID+xOCsqtvNBm6T1jwKBgQDI67Obqjqt1IDWV5/7dJundWEtfGBdHYBd
iuVxoyqOKAeasaWXuUxJgpzy/ukIAuI5WtzR8nP/US8MIUkRj7eXDi3E2tgpkUwZ
IT9StEdXM8UznpyXfQX9oMmlHC8MZRwLJkldcbGMjsfXsVcAMbe+HBmXs+izsxqe
icsEPPe/MwKBgQDU2O5HPAElJrcUa3TZux/AayF5hih8OmcOS6bMQhcYj658uD/t
se6QDd4dyaHf12X/7fPDW36dHjPUoWGVi9jV8GV2HG/vUc0k6/vS3CsstF3ZwFa8
o4iV9xePQeYl1sjJUCQKhwrx8z6/prKT7gpxeAHx3jkMw3klg/CAIwYBXQKBgFIc
OT+Rlv95S9nM352k7wPFrZwoKz2Ck/YmkFQbiYWlCE6I8RKLcIjOLxQDZvqWKxuj
bYEDY7Jg3ChJ5hGeOTorWjsL8LE0JvRSKQ3EjS8vAhVxaa9jMbKQJjgqx3N6Uraf
w/XDQ/scUsFsQRDcQKoZ07+yj5P4yxUlXOlzfRAPAoGBALri0o6PhjRRPwKzE3gO
ClUJ1KTEhA55GOPs5ZDPxDckYigSC+R8nLLxeueFiFkIDrar85tiO9J4P0j7tovB
Ivu6+QWQso3U3RC2Deb3uWagKHOt9m+DB8LdKsLpY/aPUXzPKKXJ4BWVIYExm418
FUzB7PCpc0Cdjg6A7Ca2cP0U
-----END PRIVATE KEY-----
"""
createKey(pem: privateKey)
The pem key is base64 encoded, so you need to decode it first:
guard let privateKeyData = Data(base64Encoded: privateKey, options: .ignoreUnknownCharacters) else {
...

How do I parse an x509 certificate and extract its key's signature in Swift?

I'm trying to generate an RSA pair key, then add an X.509 certificate to the header of Public Key. For this, I'm using library SwiftyRSA
The problem is that when I add the X.509 certificate to the header I cannot parse the Public Key. When I print the Public Key after that, it still shows the same as it was without the certificate.
But when I print bytes it shows different bytes, without the certificate it shows 270 bytes, with the certificate it shows 294bytes. This means it's adding the certificate but it's not parsing it with the certificate.
After reading deeper in SwiftyRSA library, it says:
Warning : Storing (with SwiftyRSA's methods) or creating a PublicKey
instance will automatically strip the header from the key. For more
info, see Under the hood above.
If worth mentioning even the case that is still Opened there.
I'm still looking for a solution but cannot find any. The code that I tried to implement:
if let password = passwordTextField.text {
//Generate RSA
guard let keyPair = try? SwiftyRSA.generateRSAKeyPair(sizeInBits: 2048),
let privateKeyPem = try? keyPair.privateKey.pemString(),
let publicKeyPem = try? keyPair.publicKey.pemString()
else {
return
}
/// Generate Certificate in format X.509 from Public Key
let publicKey = try! PublicKey(data: keyPair.publicKey.data())
let publicKeyData = try! keyPair.publicKey.data()
let publicKey_with_X509_header = try! SwiftyRSA.prependX509KeyHeader(keyData: publicKeyData)
let publicKey509 = try! PublicKey(data: publicKey_with_X509_header)
print(try! publicKey.pemString()) // Without Certificate
print(try! publicKey509.pemString()) // With Certificate
// These two print results are completely the same, but it should be different.
// Encrypt private key
let salt = String.random(length: 32)
let aesKey = Array(String((password + salt).prefix(32)).utf8)
let iv = [UInt8](String(salt.prefix(16)).utf8)
guard let aes = try? AES(key: aesKey, blockMode: CBC(iv: iv), padding: .pkcs7),
let inputData = privateKeyPem.data(using: .utf8),
let encryptedBytes = try? aes.encrypt(inputData.bytes)
else {
return
}
let encryptedData = NSData(bytes: encryptedBytes, length: encryptedBytes.count)
let base64String = encryptedData.base64EncodedString(options: NSData.Base64EncodingOptions(rawValue: 0))
// Print keys and salt
print(try! publicKey509.pemString())
print(base64String)
print(salt)
}
How can I parse the Public Key with an x509 certificate?
I would appreciate any contribution.
As mentioned above, you won't be able to create x509 certificate with SwiftyRSA. You can actually do this with Security framework.
First, you need to create an extension of Data, which will be used to add the certificate to the public key.
extension Data {
public func dataByPrependingX509Header() -> Data {
let result = NSMutableData()
let encodingLength: Int = (self.count + 1).encodedOctets().count
let OID: [CUnsignedChar] = [0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00]
var builder: [CUnsignedChar] = []
// ASN.1 SEQUENCE
builder.append(0x30)
// Overall size, made of OID + bitstring encoding + actual key
let size = OID.count + 2 + encodingLength + self.count
let encodedSize = size.encodedOctets()
builder.append(contentsOf: encodedSize)
result.append(builder, length: builder.count)
result.append(OID, length: OID.count)
builder.removeAll(keepingCapacity: false)
builder.append(0x03)
builder.append(contentsOf: (self.count + 1).encodedOctets())
builder.append(0x00)
result.append(builder, length: builder.count)
// Actual key bytes
result.append(self)
return result as Data
}
}
Then, make sure you import Security and, you change your code to this:
// Generate keys
if let password = passwordTextField.text {
//Generate RSA
let attributes: [String: Any] = [
String(kSecAttrKeyType): kSecAttrKeyTypeRSA,
String(kSecAttrKeySizeInBits): 4096, // you can change the size to 2048, but 4096 has higher security.
String(kSecPrivateKeyAttrs): [
String(kSecAttrIsPermanent): true,
String(kSecAttrApplicationTag): "com.example.keys.mykey"]
]
var error: Unmanaged<CFError>?
// Private Key
guard let privateKey = SecKeyCreateRandomKey(attributes as CFDictionary, &error) else {
fatalError("Error: \(error.debugDescription)")
}
let privateKeyData = SecKeyCopyExternalRepresentation(privateKey, &error)
let privData = privateKeyData as Data?
let privateKeyString = privData!.base64EncodedString()
print("Private Key")
print(privateKeyString)
// You'll probably need to structure Private and public key to go in new line every time after 76 character
let structuredPrivateKeyString = privateKeyString.inserting(separator: "\n", every: 76)
let privateKeyFinal = "-----BEGIN RSA PRIVATE KEY-----\n" + structuredPrivateKeyString + "\n\n-----END RSA PRIVATE KEY-----"
print(privateKeyFinal)
// Public Key
let publicKey = SecKeyCopyPublicKey(privateKey) // generate public key from private key
let publicKeyData = SecKeyCopyExternalRepresentation(publicKey!, &error)
let pubData = publicKeyData as Data? // We need to turn it into data so we can add the certificate or print it as string
print("Public Key")
print(pubData!.base64EncodedString())
// Add x509 certificate to public key
let publicKeyX509 = SecKeyCopyPublicKey(privateKey)
let publicKeyDataX509 = SecKeyCopyExternalRepresentation(publicKeyX509!, &error)
let pubData1 = publicKeyDataX509 as Data?
let x509Data = pubData1!.dataByPrependingX509Header() // Here we add the certificate (Function that we created previosly in Data extension).
var publicKeyX509String = x509Data.base64EncodedString()
print("Public Key x509")
print(publicKeyX509String)
let structuredPublicKeyX509String = publicKeyX509String.inserting(separator: "\n", every: 76)
let publicKeyFinal = "-----BEGIN RSA PUBLIC KEY-----\n\(structuredPublicKeyX509String)\n\n-----END RSA PUBLIC KEY-----"
print("Public Key for the API")
print(publicKeyFinal)
// Encrypt private key
let salt = String.random(length: 32)
let aesKey = Array(String((password + salt).prefix(32)).utf8)
let iv = [UInt8](String(salt.prefix(16)).utf8)
guard let aes = try? AES(key: aesKey, blockMode: CBC(iv: iv), padding: .pkcs7),
let inputData = privateKeyFinal.data(using: .utf8),
let encryptedBytes = try? aes.encrypt(inputData.bytes)
else {
setLoadingView(visible: false)
return
}
let encryptedData = NSData(bytes: encryptedBytes, length: encryptedBytes.count)
let base64String = encryptedData.base64EncodedString(options: NSData.Base64EncodingOptions(rawValue: 0))
// Print keys and salt
print(publicKeyFinal)
print(base64String)
print(salt)
}
Note: I added a comment on each line that I thought is important, so you will now what's happening.

Swift RSA Encryption Public Key to Java Server is failing

I am trying to create public base64 key from RSA Private key using Security framework. Here is snippet.
let tag = "com.example.keys.mykey"
public extension SecKey {
static func generateBase64Encoded2048BitRSAKey() throws -> (private: String, public: String) {
let type = kSecAttrKeyTypeRSA
let attributes: [String: Any] =
[kSecAttrKeyType as String: type,
kSecAttrKeySizeInBits as String: 2048
]
var error: Unmanaged<CFError>?
guard let key = SecKeyCreateRandomKey(attributes as CFDictionary, &error),
let data = SecKeyCopyExternalRepresentation(key, &error) as Data?,
let publicKey = SecKeyCopyPublicKey(key),
let publicKeyData = SecKeyCopyExternalRepresentation(publicKey, &error) as Data? else {
throw error!.takeRetainedValue() as Error
}
return (private: data.base64EncodedString(), public: publicKeyData.base64EncodedString())
}
}
do {
let (pvtKey, pubKey) = try SecKey.generateBase64Encoded2048BitRSAKey()
print(pubKey)
} catch let error {
print(error)
}
This is the output
MIIBCgKCAQEA1ZafTYboquQbCTZMEb1IqHKIr8wiDjdn6e0toRajZCQo9W5zuTlEuctrjJJQ08HcOuK3BPFRaFTUP1RBFvnba/T2S1Mc6WVX81b0DmKS8aPJ83TvvQlH3bZjVqFzndXJHJatcXRkZKlbidNQYxV9OYFCRLwgR5PBoJ1P5tp8f8735vIADOBL/93nFywODSjAWLXcyG5tUyRlRGX7eDodL7jqVOFxVMB7K9UOJehPuJQiheykyPSbBSLE6raZbpCHlranTLdihWYFs2tYbxzNrVbXzgKIxDDjrhDLVFvo3beudKQcLQkSO+m2LJIDT91zAnxVQ075AIn80ZHh5kdyQQIDAQAB
But this public key is not getting accepted by our Java server. It is throwing exception for the same.
Here is java snippet
public static void main(String[] args) {
String pubKey = "MIIBCgKCAQEA1ZafTYboquQbCTZMEb1IqHKIr8wiDjdn6e0toRajZCQo9W5zuTlEuctrjJJQ08HcOuK3BPFRaFTUP1RBFvnba/T2S1Mc6WVX81b0DmKS8aPJ83TvvQlH3bZjVqFzndXJHJatcXRkZKlbidNQYxV9OYFCRLwgR5PBoJ1P5tp8f8735vIADOBL/93nFywODSjAWLXcyG5tUyRlRGX7eDodL7jqVOFxVMB7K9UOJehPuJQiheykyPSbBSLE6raZbpCHlranTLdihWYFs2tYbxzNrVbXzgKIxDDjrhDLVFvo3beudKQcLQkSO+m2LJIDT91zAnxVQ075AIn80ZHh5kdyQQIDAQAB";
PublicKey key = getPublic(pubKey);
}
public static PublicKey getPublic(String key) {
PublicKey pbKey = null;
try {
byte[] keyBytes = Base64.getDecoder().decode(key);
System.out.println(keyBytes.length);
X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
KeyFactory factory = KeyFactory.getInstance("RSA");
pbKey = factory.generatePublic(spec);
} catch (Exception e) {
e.printStackTrace();
}
return pbKey;
}
Here is the exception
java.security.spec.InvalidKeySpecException: java.security.InvalidKeyException: IOException: algid parse error, not a sequence
at sun.security.rsa.RSAKeyFactory.engineGeneratePublic(RSAKeyFactory.java:205)
at java.security.KeyFactory.generatePublic(KeyFactory.java:334)
at Main.getPublic(Main.java:40)
at Main.main(Main.java:28)
But the online PEM parser website - https://8gwifi.org/PemParserFunctions.jsp is accepting this public key, which is using bouncycastle library in the background to validate this base64 encoded public key.
The exception is thrown because the ASN.1 DER encoding of an RSA public key generated on iOS is represented with the RSAPublicKey type as defined by PKCS#1, while Java (and many other languages and tools) expect the DER encoding to be represented with the SubjectPublicKeyInfo type as defined by X.509. There are of course two sides where this problem can be solved. And if you choose to convert the DER encoding of the RSA public key at the iOS side, you could use this project I recently published on GitHub. The structure you may be interested in is RSAPublicKeyExporter, which uses the SimpleASN1Writer for converting the DER encoding. The code snippet below shows how to use it:
import RSAPublicKeyExporter
let publicKeyData = ... // Get external representation of RSA public key some how
let x509EncodedKeyData = RSAPublicKeyExporter().toSubjectPublicKeyInfo(publicKeyData)
The answer I posted here contains some information that may be useful in case the exported key is fetched from the keychain.
Thanks guys. Due to some issues with bouncycastle library, we did not used it in backend service. So in iOS, we are including ASN1 header.
struct ASN1 {
let type: UInt8
let length: Int
let data: Data
init?(type: UInt8, arbitraryData data: Data) {
guard data.count > 4 else {
return nil
}
var result = data
let byteArray = [UInt8](result)
for (_, v) in byteArray.enumerated() {
if v == type { // ASN1 SEQUENCE Type
break
}
result = Data(result.dropFirst())
}
guard result.count > 4 else {
return nil
}
guard
let first = result.advanced(by: 0).first, // advanced start from 7.0
let second = result.advanced(by: 1).first,
let third = result.advanced(by: 2).first,
let fourth = result.advanced(by: 3).first
else {
return nil
}
var length = 0
switch second {
case 0x82:
length = ((Int(third) << 8) | Int(fourth)) + 4
break
case 0x81:
length = Int(third) + 3
break
default:
length = Int(second) + 2
break
}
guard result.startIndex + length <= result.endIndex else { // startIndex, endIndex start from 7.0
return nil
}
result = result[result.startIndex..<result.startIndex + length]
self.data = result
self.length = length
self.type = first
}
var last: ASN1? {
get {
var result: Data?
var dataToFetch = self.data
while let fetched = ASN1(type: self.type, arbitraryData: dataToFetch) {
if let range = data.range(of: fetched.data) {
if range.upperBound == data.count {
result = fetched.data
dataToFetch = Data(fetched.data.dropFirst())
} else {
dataToFetch = Data(data.dropFirst(range.upperBound))
}
} else {
break
}
}
return ASN1(type: type, arbitraryData: result!)
}
}
static func wrap(type: UInt8, followingData: Data) -> Data {
var adjustedFollowingData = followingData
if type == 0x03 {
adjustedFollowingData = Data([0]) + followingData // add prefix 0
}
let lengthOfAdjustedFollowingData = adjustedFollowingData.count
let first: UInt8 = type
var bytes = [UInt8]()
if lengthOfAdjustedFollowingData <= 0x80 {
let second: UInt8 = UInt8(lengthOfAdjustedFollowingData)
bytes = [first, second]
} else if lengthOfAdjustedFollowingData > 0x80 && lengthOfAdjustedFollowingData <= 0xFF {
let second: UInt8 = UInt8(0x81)
let third: UInt8 = UInt8(lengthOfAdjustedFollowingData)
bytes = [first, second, third]
} else {
let second: UInt8 = UInt8(0x82)
let third: UInt8 = UInt8(lengthOfAdjustedFollowingData >> 8)
let fourth: UInt8 = UInt8(lengthOfAdjustedFollowingData & 0xFF)
bytes = [first, second, third, fourth]
}
return Data(bytes) + adjustedFollowingData
}
static func rsaOID() -> Data {
var bytes = [UInt8]()
bytes = [0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01, 0x05, 0x00]
return Data(bytes)
}
}
Then called this during generating public key of RSA in swift.
class func RSAPublicKeyBitsFromKey(_ secKey:SecKey) -> Data? {
var queryPublicKey:[String:AnyObject] = [:]
queryPublicKey[kSecClass as String] = kSecClassKey as NSString
queryPublicKey[kSecAttrKeyType as String] = kSecAttrKeyTypeRSA as NSString
if let publicKeyData = SwiftCrypto.publicKeyInData(queryPublicKey, secKey: secKey) {
let bitstringSequence = ASN1.wrap(type: 0x03, followingData: publicKeyData)
let oidData = ASN1.rsaOID()
let oidSequence = ASN1.wrap(type: 0x30, followingData: oidData)
let X509Sequence = ASN1.wrap(type: 0x30, followingData: oidSequence + bitstringSequence)
return X509Sequence
}
return nil
}
So, in this way, I had fixed this issue.

SecKeyRawSign returning error -1, "generic error"?

I am working on an iOS application that needs to generate a key pair on the device, store the private key in the Secure Enclave, and then access it later to use for signing (does not need to be exported, ever). When I sign, I always hash the data using SHA256, following a couple of stack Overflow answers, and it seems to be working when I print the results. However, after obtaining a valid private key reference from the KeyChain, hashing the data to be signed, and specifying that it is a SHA256 hash, SecKeyRawSign still returns -1. This is just listed as a 'generic error', and my setup seems like it should be valid. Some insight on what's going wrong would be greatly appreciated. Here are my methods to generate and sign:
private func genKeyPair() -> (privateAlias: String, publicKey: NSData)? {
// Generate a keyhandle, which will be returned as an alias for the private key
let numBytes = Int(keyHandleLength)
var randomBytes = [UInt8](count: numBytes, repeatedValue: 0)
SecRandomCopyBytes(kSecRandomDefault, numBytes, &randomBytes)
let data = NSData(bytes: &randomBytes, length: numBytes)
let alias = data.base64EncodedStringWithOptions(NSDataBase64EncodingOptions(rawValue: 0))
let access = SecAccessControlCreateWithFlags(nil, kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly, .TouchIDCurrentSet, nil)!
// Key pair parameters
var keyParams: [String:AnyObject] = [
kSecAttrKeyType as String: kSecAttrKeyTypeEC,
kSecAttrKeySizeInBits as String: 256
]
// Private key parameters
keyParams[kSecPrivateKeyAttrs as String] = [
kSecAttrIsPermanent as String: true,
kSecAttrLabel as String: alias,
kSecAttrApplicationTag as String: applicationTag,
kSecAttrAccessControl as String: access
]
// Public key parameters
keyParams[kSecPublicKeyAttrs as String] = [
kSecAttrIsPermanent as String: true,
kSecAttrLabel as String: alias + "-pub",
kSecAttrApplicationTag as String: applicationTag
]
var pubKeyRef, privKeyRef: SecKey?
var err = SecKeyGeneratePair(keyParams, &pubKeyRef, &privKeyRef)
guard let _ = pubKeyRef where err == errSecSuccess else {
print("Error while generating key pair: \(err).")
return nil
}
// Export the public key for application use
let query = [
kSecClass as String: kSecClassKey,
kSecAttrLabel as String: alias + "-pub",
kSecAttrKeyType as String: kSecAttrKeyTypeEC,
kSecReturnData as String: true
]
var pubKeyOpt: AnyObject?
err = SecItemCopyMatching(query, &pubKeyOpt)
if let pubKey = pubKeyOpt as? NSData where err == errSecSuccess {
print("Successfully retrieved public key!")
return (alias, pubKey)
} else {
print("Error retrieving public key: \(err).")
return nil
}
}
private func sign(bytes data: NSData, usingKeyWithAlias alias: String) -> NSData? {
let query = [
kSecClass as String: kSecClassKey,
kSecAttrLabel as String: alias,
kSecAttrApplicationTag as String: applicationTag,
kSecAttrKeyType as String: kSecAttrKeyTypeEC,
kSecReturnRef as String: true
]
var privateKey: AnyObject?
var error = SecItemCopyMatching(query, &privateKey)
guard error == errSecSuccess else {
print("Could not obtain reference to private key with alias \"\(alias)\", error: \(error).")
return nil
}
print("\nData: \(data)")
print("Length: \(data.length)")
let hashedData = NSMutableData(length: Int(CC_SHA256_DIGEST_LENGTH))!
CC_SHA256(data.bytes, CC_LONG(data.length), UnsafeMutablePointer(hashedData.mutableBytes))
print("\nHashed data: \(hashedData)")
print("Length: \(hashedData.length)")
var signedHashLength = SecKeyGetBlockSize(privateKey as! SecKeyRef)
let signedHash = NSMutableData(length: signedHashLength)!
error = SecKeyRawSign(privateKey as! SecKeyRef, .PKCS1SHA256, UnsafePointer<UInt8>(hashedData.mutableBytes), hashedData.length, UnsafeMutablePointer<UInt8>(signedHash.mutableBytes), &signedHashLength)
print("\nSigned hash: \(signedHash)")
print("Length: \(signedHashLength)\n")
guard error == errSecSuccess else {
print("Failed to sign data, error: \(error).")
return nil
}
return signedHash
}

[Swift._NSContiguousString bytes]: unrecognized selector sent to instance

I'm working on Xcode version 7.1 (7B91b), I'm using Swift and building an OSX application (10.11.2). I'm trying to generate and store RSA key pairs to be used for data encryption / decryption. Here's the implementation that has been taken from: 'AsymmetricCrypto Github
// Constants
private let kAsymmetricCryptoManagerApplicationTag = "com.AsymmetricCrypto.keypair"
private let kAsymmetricCryptoManagerKeyType = kSecAttrKeyTypeRSA
private let kAsymmetricCryptoManagerKeySize = 2048
private let kAsymmetricCryptoManagerCypheredBufferSize = 1024
private let kAsymmetricCryptoManagerSecPadding: SecPadding = .PKCS1
func createSecureKeyPair(completion: ((success: Bool, error: AsymmetricCryptoException?) -> Void)? = nil) {
// private key parameters
let privateKeyParams: [String: AnyObject] = [
kSecAttrIsPermanent as String: false,
kSecAttrApplicationTag as String: kAsymmetricCryptoManagerApplicationTag
]
// private key parameters
let publicKeyParams: [String: AnyObject] = [
kSecAttrIsPermanent as String: false,
kSecAttrApplicationTag as String: kAsymmetricCryptoManagerApplicationTag
]
// global parameters for our key generation
let parameters: [String: AnyObject] = [
kSecAttrKeyType as String: kAsymmetricCryptoManagerKeyType,
kSecAttrKeySizeInBits as String: kAsymmetricCryptoManagerKeySize,
kSecPublicKeyAttrs as String: publicKeyParams,
kSecPrivateKeyAttrs as String: privateKeyParams,
]
// asynchronously generate the key pair and call the completion block
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) { () -> Void in
var pubKey, privKey: SecKeyRef?
let status = SecKeyGeneratePair(parameters, &pubKey, &privKey)
if status == errSecSuccess {
dispatch_async(dispatch_get_main_queue(), { completion?(success: true, error: nil) })
} else {
var error = AsymmetricCryptoException.UnknownError
switch (status) {
case errSecDuplicateItem: error = .DuplicateFoundWhileTryingToCreateKey
case errSecItemNotFound: error = .KeyNotFound
case errSecAuthFailed: error = .AuthFailed
default: break
}
dispatch_async(dispatch_get_main_queue(), { completion?(success: false, error: error) })
}
}
}
Upon calling the createSecureKeyPair(), as follows:
AsymmetricCryptoManager.sharedInstance.createSecureKeyPair({
(success, error) -> Void in
if (success) {
print("keys created")
} else {
print("error: \(error)")
}
})
I get this error:
2015-12-26 03:55:46.113 sectest[17165:20928431] -[Swift._NSContiguousString bytes]: unrecognized selector sent to instance 0x610000045880
and of course, the exception AsymmetricCryptoException.UnknownError is returned (meaning that the error is of an unknown nature), plus if it helps, SecKeyGeneratePair() returns the value -2070
The weird part is that the keys (public and private) are actually created in the Keychain as such:
What is this error and how can get the expected behavior out of the code above?
I got the answer to the problem from the Apple Developer Forums. The problem is with:
private let kAsymmetricCryptoManagerApplicationTag = "com.AsymmetricCrypto.keypair"
The values for key kSecAttrApplicationTag of params dictionaries need to be a CFDataRef, not String. The below line, resolves the problem.
private let kAsymmetricCryptoManagerApplicationTag = "com.sectest.keypair".dataUsingEncoding(NSUTF8StringEncoding)!