Creating OpenSSH compatible key pairs

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"

I’m trying that with this simplified piece of code:


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

Answered by DTS Engineer in 414158022

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"
Accepted Answer

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"

I have to comment that I'm not surprised at all that it's you, who provided answers and help :-) Yeah, I've noticed the keys are different, but I didn't know anything about the legacy format "1.1" (I mentioned I'm not very experienced with cryptography).


In any case, thanks for clarification.

Hey @milke I've come across the same problem while working on this Swift Package, did you ever solve it?

The SPM I'm working on https://github.com/App-Maker-Software/GitProviders

@joehinkle11, I did solve it, but not using system APIs. I use: https://www.openssh.com/portable.html

I actually created my own small library, aimed only to create those keys and stripped away all other unnecessary code, trying to keep its footprint as small as possible.

Creating OpenSSH compatible key pairs
 
 
Q