Posts

Post not yet marked as solved
4 Replies
Right, so I did some more experimenting with both of the keys. Transforming the SecureEnclave key into a SecKey will always generate a new private key in the Secure Enclave, causing the initial Secure Enclave key and the SecKey to be non identical: let privateKey = try SecureEnclave.P256.Signing.PrivateKey() let attributes: [CFString: Any] = [ kSecClass: kSecClassKey, kSecAttrKeyType: kSecAttrKeyTypeECSECPrimeRandom, kSecAttrKeyClass: kSecAttrKeyClassPrivate, kSecAttrTokenID: kSecAttrTokenIDSecureEnclave ] let secKey = SecKeyCreateWithData(privateKey.dataRepresentation as CFData, attributes as CFDictionary, nil) // let secKey = SecKeyCreateWithData(Data() as CFData, attributes as CFDictionary, nil) Interestingly enough, it doesn't really matter what Data you provide to the SecKeyCreateWithData function when you set the kSecAttrTokenID field, odd... We do however get a valid SecKey reference which can be used, but as mentioned, it's a different one from the privateKey we created before. I checked this by comparing the public keys of both of these private keys, which do not match. Now, normally speaking, the SecKeyCopyExternalRepresentation function will result in an error for Secure Enclave SecKey objects, which makes sense, it's a non extractable key, but the SecureEnclave enum exposes the Data through the dataRepresentation. This appears to simply be an ASN.1 octet stream containing the X9.62 export of the actual Secure Enclave private key. So I decided to do some more digging and found out that we can get the same data through from the SecKey, only at key generation time. let attributes: [CFString: Any] = [ kSecClass: kSecClassKey, kSecAttrKeyType: kSecAttrKeyTypeECSECPrimeRandom, kSecAttrKeyClass: kSecAttrKeyClassPrivate, kSecAttrTokenID: kSecAttrTokenIDSecureEnclave ] let secKey = SecKeyCreateRandomKey(attributes as CFDictionary, nil) let secAttributes = (SecKeyCopyAttributes(secKey!) as! [CFString: Any]) let secBytes = secAttributes["toid" as CFString] as! Data let privateKey = try SecureEnclave.P256.Signing.PrivateKey(dataRepresentation: secBytes) The toid field is available right after we create the key. It exposes the same data as the SecureEnclave enum (dataRepresentation) and we can actually create the SecureEnclave` key if we use this data. Now I end up with a couple more questions: Is there no way to convert a SecureEnclave key into a SecKey somehow? Is the dataRepresentation of the SecureEnclave insecure after all, as one can easily extract X9.62 data from it? (And therefor also the toid field in the attributes)? Is the toid field something I can use to continue (also in a production app)?
Post marked as solved
3 Replies
Are you aware of any improvements to the SecKey interfacing that will allow to store keys properly? A generic password is very different from a SecKey
Post marked as solved
6 Replies
I have tried recreating the key from your data in iOS using Swift and in Python using the cryptography library (which I can't link because of the new editor here, sigh...). Anyways, my iOS code looks like this import CryptoKit import Foundation let x963 = Data(base64Encoded: "BBFANnBUfWaxiaM25MGHsga26xt6eAoRwULv7FnptrIDn6WQz7uMkIALgLM5xriANs5VXKy/2Q71jetoI6Pzojk=")! print(x963, x963 as NSData) let x = x963.prefix(33).suffix(32) print(x, x as NSData) let y = x963.suffix(32) print(y, y as NSData) print(x.base64EncodedString()) print(y.base64EncodedString()) let pub = try! P256.Signing.PublicKey(x963Representation: x963) /* Outputs the following 65 bytes {length = 65, bytes = 0x04114036 70547d66 b189a336 e4c187b2 ... 8deb6823 a3f3a239 } 32 bytes {length = 32, bytes = 0x11403670 547d66b1 89a336e4 c187b206 ... 42efec59 e9b6b203 } 32 bytes {length = 32, bytes = 0x9fa590cf bb8c9080 0b80b339 c6b88036 ... 8deb6823 a3f3a239 } EUA2cFR9ZrGJozbkwYeyBrbrG3p4ChHBQu/sWem2sgM= n6WQz7uMkIALgLM5xriANs5VXKy/2Q71jetoI6Pzojk=/ Which successfully creates the P256 key, meaning, the data provided is correct. Do note that I do not use the URL encoding as JWT suggests, but that shouldn't matter. from cryptography.hazmat.primitives.asymmetric import ec from cryptography.hazmat.primitives import serialization from cryptography.hazmat.backends import default_backend import base64 a = bytes.fromhex('04\  11403670547D66B189A336E4C187B206\ B6EB1B7A780A11C142EFEC59E9B6B203\ 9FA590CFBB8C90800B80B339C6B88036\ CE555CACBFD90EF58DEB6823A3F3A239') ec.EllipticCurvePublicKey.from_encoded_point(ec.SECP256R1(), a) x = base64.b64decode('EUA2cFR9ZrGJozbkwYeyBrbrG3p4ChHBQu/sWem2sgM=') y = base64.b64decode('n6WQz7uMkIALgLM5xriANs5VXKy/2Q71jetoI6Pzojk=') x963 = bytes.fromhex('04') + x + y ec.EllipticCurvePublicKey.from_encoded_point(ec.SECP256R1(), x963) And this too works perfectly fine. So I guess it's a server error. May I ask what platform you're using on your server to reconstruct the keys? Note that some cryptography libraries are a bit finicky when serialising elliptic curve keys. Make sure your library supports: Reconstruction of the public key from given public key format (uncompressed point). Which is different from, for instance, subject public key info (used in certificates) and compressed point. Reconstruction of the public key from X9.62/X9.63 encoded data is supported. Which is different from DER or PEM. One last thing to check, make sure your base64 url encoded data is decoded correctly. As seen in the example code eskimo provided, you can see the padding is removed and the + and / are replaced. Make sure this is of course reversed on your server (if that is one of the requirements. base64 URL encoded is not the same as base64 encoded). Hope this can help you some!
Post not yet marked as solved
20 Replies
To answer your question shortly, SecKeyCreateWithData expects DER formatted data, not PEM (DER is pretty much the PEM without the headers). Also note that at least kSecAttrKeyType, kSecAttrKeyClass and kSecAttrKeySizeInBits must be defined in the attributes. You are correct that the size attribute doesn’t matter (for now) as I have experienced that too, but it’s still required in the recreation attributes. Hope this helps! Craz1k0ek
Post marked as solved
3 Replies
Solved by using a python library.import plistlib import hashlib def calc_hash(file_name): try: f = open(file_name, 'r') f_data = f.read() f.close() file = plistlib.loads(f_data.encode()) except (ValueError, Exception) as e: print("Failed to load file {}".format(file_name)) return dump = plistlib.dumps(file, fmt=plistlib.FMT_BINARY) hasher = hashlib.sha256() hasher.update(dump) digest = hasher.digest() print("{} {}".format(file_name, digest.hex())) file_name = str(input('Please enter the relative path to the plist (i.e. path/to/plist.plist):\n')) calc_hash(file_name)
Post marked as solved
3 Replies
Is there a way to prevent that from happening? In direct reply to your answer, I did convert it to binary myself:plutil -convert binary1 file.plist