Question

I'm looking to be able to generate the same kind of public-key string that you'd get from "ssh-keygen -t ecdsa". I have an EC_KEY.

I tried using:

PEM_write_bio_EC_PUBKEY(bio_out, ecdsa);

...But I get a string that's entirely too large.

I tried this:

ec_group = EC_KEY_get0_group(pubkey->ecdsa);
ec_point = EC_KEY_get0_public_key(pubkey->ecdsa);

encoded = EC_POINT_point2hex(
            ec_group,
            ec_point,
            POINT_CONVERSION_UNCOMPRESSED,
            NULL);

...But obviously I want something that's base64-encoded.

Can anyone give me direction, here?

Was it helpful?

Solution 2

It turns out that the public-key string is PKCS8. At the command-line, you can convert from OpenSSL ECDSA to OpenSSH with OpenSSL:

$ openssl ecparam -genkey -name prime256v1 -noout -out prime256v1.key.pem
$ openssl ec -in prime256v1.key.pem -pubout | ssh-keygen -f /dev/stdin -i -m PKCS8
read EC key
writing EC key
ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBC3FrhznL2pQ/titzgnWrbznR3ve2eNEgevog/aS7SszS9Vkq0uFefavBF4M2Txc34sIQ5TPiZxYdm9uO1siXSw=

(I wrote about it here: https://the.randomengineer.com/2014/06/28/creating-those-neat-openssh-public-keys-and-dsa-and-ecdsa-keys-with-openssl-in-general/)

Just find a PKCS8 library for whatever language you're using. For Python, it looks like both PyCrypto and Paramiko support it, based on this Gist: https://gist.github.com/jtriley/7270594

First, this:

sha1digest = hashlib.sha1(k.exportKey('DER', pkcs=8)).hexdigest()

Where exportKey contains this:

if use_pycrypto:
    key = RSA.importKey(key_fobj, passphrase=passphrase)
else:
    key = paramiko.RSAKey.from_private_key(key_fobj,
                                           password=passphrase)

OTHER TIPS

@noloader reminded me that I could look in ssh-keygen.c .

This is what I found.

This is called for all key types (rsa, dsa, ecdsa):

key_to_blob(key, &blob, &len);
uu = xmalloc(2*len);
n = uuencode(blob, len, uu, 2*len);
if (n > 0) {
        fprintf(f, "%s %s", key_ssh_name(key), uu);
        success = 1;
}

Where key_to_blob() eventually leads to to_blob():

to_blob(const Key *key, u_char **blobp, u_int *lenp, int force_plain)
{
        Buffer b;
        int len, type;

        if (blobp != NULL)
                *blobp = NULL;
        if (lenp != NULL)
                *lenp = 0;
        if (key == NULL) {
                error("key_to_blob: key == NULL");
                return 0;
        }
        buffer_init(&b);
        type = force_plain ? key_type_plain(key->type) : key->type;
        switch (type) {
        case KEY_DSA_CERT_V00:
        case KEY_RSA_CERT_V00:
        case KEY_DSA_CERT:
        case KEY_ECDSA_CERT:
        case KEY_RSA_CERT:
        case KEY_ED25519_CERT:
                /* Use the existing blob */
                buffer_append(&b, buffer_ptr(&key->cert->certblob),
                    buffer_len(&key->cert->certblob));

Which eventually leads to this:

        buffer_clear(&k->cert->certblob);
        buffer_put_cstring(&k->cert->certblob, key_ssh_name(k));

        /* -v01 certs put nonce first */
        arc4random_buf(&nonce, sizeof(nonce));
        if (!key_cert_is_legacy(k))
                buffer_put_string(&k->cert->certblob, nonce, sizeof(nonce));

        /* XXX this substantially duplicates to_blob(); refactor */
        switch (k->type) {
        case KEY_DSA_CERT_V00:
        case KEY_DSA_CERT:
                buffer_put_bignum2(&k->cert->certblob, k->dsa->p);
                buffer_put_bignum2(&k->cert->certblob, k->dsa->q);
                buffer_put_bignum2(&k->cert->certblob, k->dsa->g);
                buffer_put_bignum2(&k->cert->certblob, k->dsa->pub_key);
                break;
#ifdef OPENSSL_HAS_ECC
        case KEY_ECDSA_CERT:
                buffer_put_cstring(&k->cert->certblob,
                    key_curve_nid_to_name(k->ecdsa_nid));
                buffer_put_ecpoint(&k->cert->certblob,
                    EC_KEY_get0_group(k->ecdsa),
                    EC_KEY_get0_public_key(k->ecdsa));
                break;
#endif

Which eventually leads to this (2):

int
buffer_put_ecpoint_ret(Buffer *buffer, const EC_GROUP *curve,
    const EC_POINT *point)
{
        u_char *buf = NULL;
        size_t len;
        BN_CTX *bnctx;
        int ret = -1;

        /* Determine length */
        if ((bnctx = BN_CTX_new()) == NULL)
                fatal("%s: BN_CTX_new failed", __func__);
        len = EC_POINT_point2oct(curve, point, POINT_CONVERSION_UNCOMPRESSED,
            NULL, 0, bnctx);
        if (len > BUFFER_MAX_ECPOINT_LEN) {
                error("%s: giant EC point: len = %lu (max %u)",
                    __func__, (u_long)len, BUFFER_MAX_ECPOINT_LEN);
                goto out;
        }
        /* Convert */
        buf = xmalloc(len);
        if (EC_POINT_point2oct(curve, point, POINT_CONVERSION_UNCOMPRESSED,
            buf, len, bnctx) != len) {
                error("%s: EC_POINT_point2oct length mismatch", __func__);
                goto out;
        }
        /* Append */
        buffer_put_string(buffer, buf, len);
        ret = 0;
 out:
        if (buf != NULL) {
                bzero(buf, len);
                free(buf);
        }
        BN_CTX_free(bnctx);
        return ret;
}

Where the actual string is built here:

buf = xmalloc(len);
if (EC_POINT_point2oct(curve, point, POINT_CONVERSION_UNCOMPRESSED,
    buf, len, bnctx) != len) {
        error("%s: EC_POINT_point2oct length mismatch", __func__);
        goto out;
}
/* Append */
buffer_put_string(buffer, buf, len);

Where the following is used to render an "octet string" (bnctx should be allowed to be NULL):

EC_POINT_point2oct(curve, point, POINT_CONVERSION_UNCOMPRESSED, buf, len, bnctx);

I'm looking to be able to generate the same kind of public-key string that you'd get from "ssh-keygen -t ecdsa".

You can write the private key (id_ecdsa) with PEM_write_ECPrivateKey. A quick search of the sources:

$ grep -R PrivateKey *
...
ssh-keygen.c:           ok = PEM_write_DSAPrivateKey(stdout, k->dsa, NULL,
ssh-keygen.c:           ok = PEM_write_ECPrivateKey(stdout, k->ecdsa, NULL,
ssh-keygen.c:           ok = PEM_write_RSAPrivateKey(stdout, k->rsa, NULL,

However, I don't believe OpenSSL will write the "public-key string" (id_ecdsa.pub) in the format string you expect:

$ cat id_ecdsa.pub 
ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlz
dHAyNTYAAABBBEs/aVnJ16NcSOTGVNbk8ifPvPbZ0Edxd7uclo/5chC81MK7
iFb/++6parCUv0FBh47MBxV+k4rxGJ1OESe4Vxs= jwalton@debian-q500

That's because OpenSSL lacks the function that applies the SSH formatting. OpenSSL only deals with DER and PEM encodings, and not SSH encodings.

For completeness, there is a call to PEM_write_EC_PUBKEY, but PEM formatting does not include prologue (like ecdsa-sha2-nistp256) or epilogue (like email address).

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top