Curve25519 String representation

Hi.

I've been trying to make an ed25519 key pair from already existing keys, which has been giving me significant issues.

I made an ed25519 key pair through OpenSSH (the keygen command), and attempted to input the private and public keys into the Curve25519 object. They keep giving me back CryptoKit Error 1, which has no further description.

So I also tried to generate keys with Curve25519, but I cannot make a string representation of them. I always get 'nil' back trying to utf8 encode them into Strings from Data, but I can base64 encode them successfully. I need the original String representation.

So how do I add already existing ed25519 keys, and how do I get String representations of ones I generate with Curve25519?

Replies

I need the original String representation.

Can you post an example of what you mean by “original String representation”?

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"
So for example:

Code Block
let privateKey = Curve25519.Signing.PrivateKey()
let publicKey = privateKey.publicKey


What I want is the string representation of the private key and public key shown. I can get a base64 String:

privateKey.rawRepresentation.base64EncodedString()

But when I try to get the String of the private key with utf8 encoding, I get nil:

String(data: privateKey.rawRepresentation, encoding: .utf8) // this gives back nil


Code Block
String(data: privateKey.rawRepresentation, encoding: .utf8)


Why would expect that to work? The rawRepresentation of a key is a bunch of (effectively) random bytes. You can’t parse that as a UTF-8 string.

Can you post an example of the sort of string you’re looking for?

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"
It's very common for signing keys of any type to have a String representation, so I would expect there is one for any ed25519 key pair. There are a number of examples online, and you can see a String representation through generating them in OpenSSH

ssh-keygen -t 25519
I'm guessing you'd get more useful replies if you actually posted an example of what you wanted your key to look like. Posting a command that I cannot run without installing software is not particularly helpful. I also didn't find any particularly consistent examples with some cursory google searching.

As noted above, a 25519 key is a series of random bytes. There are numerous standard ways to represent bytes in a string form. Base64 is one of those standard ways and is, for example, how 25519 keys are displayed in a WireGuard config file. Aside from base64 (or one of its variants), there's also hexadecimal strings (in which each byte is represented by two characters in the range of 0 - 9, a - f). If you post an example of what you're looking for, someone may be able to identify for you what the format is and tell you how to reproduce it.

UTF8 is used to represent strings in byte form and cannot be used to represent arbitrary bytes as a string.
hyllus wrote:

I'm guessing you'd get more useful replies if you actually posted an
example of what you wanted your key to look like.

Quite.



It's very common for signing keys of any type to have a String
representation

Right, but there are a wide variety of potential string representations for any given key type, which is why I asked for a specific example.

you can see a String representation through generating them in OpenSSH

I tried the command you suggested on my Mac (running 11.2.1), and it failed:

Code Block
% ssh-keygen -t 25519
unknown key type 25519


I also tried it in an Ubuntu 18.04 VM (the newest I have lying around) and it failed there as well.

Ah, I think you meant ssh-keygen -t ed25519. So, for example:

Code Block
% ssh-keygen -t ed25519
Generating public/private ed25519 key pair.
Enter file in which to save the key (/Users/quinn/.ssh/id_ed25519):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
% cat .ssh/id_ed25519
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACAoSm6+8gO6/Tej0dPOstvZUg8nJfIz291HEN8BpBylcAAAAJidoAf+naAH
/gAAAAtzc2gtZWQyNTUxOQAAACAoSm6+8gO6/Tej0dPOstvZUg8nJfIz291HEN8BpBylcA
AAAEDHZrwc6IPx2KQNQZodJLwuQ511eG6BkNEFg9flDpOKjyhKbr7yA7r9N6PR086y29lS
Dycl8jPb3UcQ3wGkHKVwAAAAE3F1aW5uQHNsaW1leS5sb2NhbC4BAg==
-----END OPENSSH PRIVATE KEY-----


Does that match your expectations?

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Posting a command that I cannot run without installing software is not particularly helpful.

I stated in the opening, and that post, that I used OpenSSH which is what the command is from (and I stated the command as well). And I've never had to install it so I assumed it comes pre-loaded in a macbook.


Ah, I think you meant ssh-keygen -t ed25519. So, for example:

Yes, I forgot the "ed" in that command. That's my mistake. And yes, that representation is what I'm having trouble with adding to Curve25519.


My main objective of adding external keys has been completed. While I couldn't do it with the OpenSSH generated keys, I had to decode my acquired keys from a different encoding and made those decoded keys into byte arrays to insert.


And yes, that representation is what I'm having trouble with adding to
Curve25519.

OK. The first thing to do here is undo the Base64 encoding:

Code Block
% base64 -D > id_ed25519.bin
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACAoSm6+8gO6/Tej0dPOstvZUg8nJfIz291HEN8BpBylcAAAAJidoAf+naAH
/gAAAAtzc2gtZWQyNTUxOQAAACAoSm6+8gO6/Tej0dPOstvZUg8nJfIz291HEN8BpBylcA
AAAEDHZrwc6IPx2KQNQZodJLwuQ511eG6BkNEFg9flDpOKjyhKbr7yA7r9N6PR086y29lS
Dycl8jPb3UcQ3wGkHKVwAAAAE3F1aW5uQHNsaW1leS5sb2NhbC4BAg==
^D
% xxd id_ed25519.bin
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 0000 3300 0000 0b73 ..........3....s
00000030: 7368 2d65 6432 3535 3139 0000 0020 284a sh-ed25519... (J
00000040: 6ebe f203 bafd 37a3 d1d3 ceb2 dbd9 520f n.....7.......R.
00000050: 2725 f233 dbdd 4710 df01 a41c a570 0000 '%.3..G......p..
00000060: 0098 9da0 07fe 9da0 07fe 0000 000b 7373 ..............ss
00000070: 682d 6564 3235 3531 3900 0000 2028 4a6e h-ed25519... (Jn
00000080: bef2 03ba fd37 a3d1 d3ce b2db d952 0f27 .....7.......R.'
00000090: 25f2 33db dd47 10df 01a4 1ca5 7000 0000 %.3..G......p...
000000a0: 40c7 66bc 1ce8 83f1 d8a4 0d41 9a1d 24bc @.f........A..$.
000000b0: 2e43 9d75 786e 8190 d105 83d7 e50e 938a .C.uxn..........
000000c0: 8f28 4a6e bef2 03ba fd37 a3d1 d3ce b2db .(Jn.....7......
000000d0: d952 0f27 25f2 33db dd47 10df 01a4 1ca5 .R.'%.3..G......
000000e0: 7000 0000 1371 7569 6e6e 4073 6c69 6d65 p....quinn@slime
000000f0: 792e 6c6f 6361 6c2e 0102 y.local...


The openssh-key-v1 header confirms that this is OpenSSH’s ‘new’ key format. AFAICT this isn’t formally documented, although there’s tonnes of info about it lurking on the ’net. Based on some digging (primarily this) I believe that the Curve25519 public key appears 3 times in that data:

Code Block
00000030: 284a
00000040: 6ebe f203 bafd 37a3 d1d3 ceb2 dbd9 520f
00000050: 2725 f233 dbdd 4710 df01 a41c a570
00000070: 28 4a6e
00000080: bef2 03ba fd37 a3d1 d3ce b2db d952 0f27
00000090: 25f2 33db dd47 10df 01a4 1ca5 70
000000c0: 28 4a6e bef2 03ba fd37 a3d1 d3ce b2db
000000d0: d952 0f27 25f2 33db dd47 10df 01a4 1ca5
000000e0: 70


And the private key just once:

Code Block
000000a0: c7 66bc 1ce8 83f1 d8a4 0d41 9a1d 24bc
000000b0: 2e43 9d75 786e 8190 d105 83d7 e50e 938a
000000c0: 8f


I plugged these into CryptoKit and they seem to work:

Code Block
let privateKeyBytes: [UInt8] = [
0xC7, 0x66, 0xBC, 0x1C, 0xE8, 0x83, 0xF1, 0xD8,
0xA4, 0x0D, 0x41, 0x9A, 0x1D, 0x24, 0xBC, 0x2E,
0x43, 0x9D, 0x75, 0x78, 0x6E, 0x81, 0x90, 0xD1,
0x05, 0x83, 0xD7, 0xE5, 0x0E, 0x93, 0x8A, 0x8F,
]
let publicKeyBytes: [UInt8] = [
0x28, 0x4A, 0x6E, 0xBE, 0xF2, 0x03, 0xBA, 0xFD,
0x37, 0xA3, 0xD1, 0xD3, 0xCE, 0xB2, 0xDB, 0xD9,
0x52, 0x0F, 0x27, 0x25, 0xF2, 0x33, 0xDB, 0xDD,
0x47, 0x10, 0xDF, 0x01, 0xA4, 0x1C, 0xA5, 0x70,
]
let privateKey = try! Curve25519.Signing.PrivateKey(rawRepresentation: privateKeyBytes)
let publicKey = try! Curve25519.Signing.PublicKey(rawRepresentation: publicKeyBytes)
let signature = try! privateKey.signature(for: Data("Hello Cruel World!".utf8))
let isValid = publicKey.isValidSignature(signature, for: Data("Hello Cruel World!".utf8))
print(isValid) // -> true


So, to summarise:
  • You are working with a Curve25519 key in OpenSSH’s ‘new’ key format.

  • CryptoKit has no facilities for importing or exporting this format.

  • If you want to do that then you’ll need write (or find) code that extracts the Curve25519 from this format (or the reverse).

As always, if you’d like to see better support for this sort of thing, you should file an enhancement request describing your requirements. If you do, please post your bug number, just for the record.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"