Hello,
I’m not very experienced in cryptography and related topics, so it may be possible I’m overlooking something quite obvious. The straightforward question is: is it possible to create OpenSSH compatible key pairs, like ssh-keyget utility does, using Security framework? Let’s say, I want to do the equivalent of the following terminal command:
ssh-keygen -t rsa -b 4096 -C “me@mail.com"
CFMutableDictionaryRef privAttrs = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); CFDictionarySetValue(privAttrs, kSecAttrIsPermanent, kCFBooleanFalse); CFDictionarySetValue(privAttrs, kSecAttrLabel, CFSTR("me@mail.com")); CFMutableDictionaryRef attrs = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); CFDictionarySetValue(attrs, kSecAttrKeyType, kSecAttrKeyTypeRSA); CFDictionarySetValue(attrs, kSecAttrKeySizeInBits, CFSTR("4096")); CFDictionarySetValue(attrs, kSecPrivateKeyAttrs, privAttrs); CFErrorRef error = NULL; SecKeyRef privateKey = SecKeyCreateRandomKey(attrs, &error); if (privateKey) { CFDataRef data = NULL; OSStatus status = SecItemExport(privateKey, kSecFormatSSH, kSecItemPemArmour, NULL, &data); if (status == errSecSuccess) { // ... save private key data to a file ... } SecKeyRef publicKey = SecKeyCopyPublicKey(privateKey); if (publicKey) { status = SecItemExport(publicKey, kSecFormatSSH, kSecItemPemArmour, NULL, &data); if (status == errSecSuccess) { // ... save public key data to a file ... } CFRelease(publicKey); } CFRelease(privateKey); } CFRelease(privAttrs); CFRelease(attrs);
This seems to create valid RSA key pair, which I can add to the Keychain on creation if I choose to (setting permanent attribute to true), but the exporting operation doesn’t end up with files I expect. The PEM armour seems not to be correct, the text representation of the key begins and ends with -----BEGIN/END RSA PRIVATE KEY----- as opposed to -----BEGIN/END OPENSSH PRIVATE KEY----- and the key structure looks different in general. If I try to show key fingerprint using
ssh-keygen -l -f <keyfile>
I get response that “<keyfile> is not a key file. If I, for example, try to upload the public key to GitHub to use for SSH connection, it’s rejected, because “key is invalid, you must supply a key in OpenSSH public key format."
I’d appreciate if anyone points out what I’m doing wrong and whether this is achievable with Security framework at all.
Thanks,
-- Dragan
If you export an RSA key using
.formatSSH
, without the PEM wrapper, you’ll get data that looks like this:
% xxd formatSSH.dat | head -n 4
00000000: 5353 4820 5052 4956 4154 4520 4b45 5920 SSH PRIVATE KEY
00000010: 4649 4c45 2046 4f52 4d41 5420 312e 310a FILE FORMAT 1.1.
00000020: 0000 0000 0000 0000 0800 0800 a26b c310 .............k..
00000030: 4135 a3a7 209d 63d3 8952 7f3e 1821 e815 A5.. .c..R.>.!..
In contrast, if you use
ssh-keygen
to generate a key (without a passphrase) and then decode the Base64, you’ll get data like this:
% xxd keygen.dat | head -n 4
00000000: 6f70 656e 7373 682d 6b65 792d 7631 0000 openssh-key-v1..
00000010: 0000 046e 6f6e 6500 0000 046e 6f6e 6500 ...none....none.
00000020: 0000 0000 0000 0100 0002 1700 0000 0773 ...............s
00000030: 7368 2d72 7361 0000 0003 0100 0100 0002 sh-rsa..........
These are clearly different. I’m not an OpenSSH expert but my understanding is that the
SSH PRIVATE KEY FILE FORMAT 1.1
is a legacy format and
openssh-key-v1
is the more modern one. AFAIK macOS has no API to generate the latter. You may be able to convince OpenSSH to accept the legacy format, or use it to convert the legacy format to the modern format. If not, you’ll have to write your own code to do this conversion.
Share and Enjoy
—
Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware
let myEmail = "eskimo" + "1" + "@apple.com"