I am using ssh-keygen -t ecdsa -b 256 which generates this for the public part:

ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBNaHgNjXShzF/hmZOuhTCCsokgpSCyFohETHT4+OQMxW5g+d9nZCYxpDhwivuWbsoXTYpQWlLATXxjbQr2Y3KRY= t0132456@L541918

I need to do the same in Java. I am using BouncyCastle:

ECParameterSpec ecSpec = ECNamedCurveTable.getParameterSpec("secp256r1");
KeyPairGenerator g = KeyPairGenerator.getInstance("ECDSA", "BC");
g.initialize(ecSpec, new SecureRandom());
KeyPair pair = g.generateKeyPair();

ECPublicKey publicKey = (ECPublicKey)pair.getPublic();
System.out.println("kpub=" + Base64.toBase64String(publicKey.getEncoded()));

but the public key is smaller and does not start with same header:


How to be generate the same way ssh-genkey does but in Java?


Sizes are different because the formats are different. The strings you show are both binary values encoded in Base64. In all this message I talk about the binary values, i.e. I suppose that you first decoded the Base64 strings into binary.

Mathematically, an ECDSA public key is a point on an elliptic curve. A point has two coordinates, called X and Y, which are bound to each other through the curve equation (in that case, Y2 = X3 + aX + b for two constants a and b that define the specific curve). Here, you use the NIST P-256 curve, which has been specified to work in a 256-bit field, i.e. X and Y are two 256-bit integers. The ANSI X9.62 standard defines a 65-byte format for representing such X and Y; basically, a first byte of value 0x04, followed by X over exactly 32 bytes (in big-endian notation), then Y over another 32 bytes.

So far so good. Then both Java and SSH wrap that 65-byte value into a structure that also identifies explicitly the curve itself through some symbolic identifiers. They do so differently.

Java follows the ASN.1-based encoding also specified in ANSI X9.62. There are several options, but, in your case, you got a SEQUENCE whose contents are a nested sub-SEQUENCE, then a BIT STRING. The nested sub-SEQUENCE contains two OBJECT IDENTIFIER values, the first one being 1.2.840.10045.2.1 (which means "this is an elliptic curve public key"), and the second is 1.2.840.10045.3.1.7, which designates the NIST P-256 curve. The BIT STRING contents are, exactly, the 65-byte encoding of the curve point. To make the story short: the Java value, when Base64-decoded, yields a value of length 91 bytes, the last 65 bytes of which being the encoding of X and Y.

OpenSSH, on the other hand, is well-known for a chronic allergy to standards, and of course they invented their own format. It has been then documented in RFC 5656, section 3.1. Basically, the encoded key consists in three "strings", one after the other. Each string consists in a 32-bit header (4 bytes, big-endian notation) followed by the string value; the header contains the value length, in bytes. The first string is the ASCII encoding of "ecdsa-sha2-nistp256" (this is identifies the signature algorithm). The second string is the ASCII encoding of "nistp256" (this identifies the curve, redundantly with the first string). The third string has a 65-byte value, and, you guessed it, that's the 65-byte encoding of X and Y. The total length is 104 bytes, and the X and Y encoding uses the last 65 of these 104 bytes.

So basically, in both formats, the interesting part is exactly the last 65 bytes; the rest is just an identifier for the involved curve, in two distinct dialects. If you generate many keys with ssh-keygen, you will notice that they will differ only in their last 65 bytes (there again, after Base64 decoding). Similarly with producing many keys in Java.

Therefore, converting from a Java-produced public key to a SSH-compatible public key is as simply as taking the last 65 bytes of that key (after Base64 decoding, where applicable), and concatenating these after the 39-byte header of a SSH key.

| improve this answer | |
  • 1
    I wish all answers were this complete. Outstanding – HashHazard Jul 13 '16 at 2:56
  • Thanks for this detailled and very well explained answer. – akira2x3x Jul 13 '16 at 13:34
  • The link to the SEC1 paper in the RFC doesn't work anymore. Here's one that works: secg.org/SEC1-Ver-1.0.pdf (and no, the site sadly doesn't have HTTPS) – robinst Sep 2 '16 at 4:20

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.