Get Even More Visitors To Your Blog, Upgrade To A Business Listing >>

How to Migrate RSA SSH Keys

The OpenSSH team recently announced the removal of support for ssh-rsa keys in OpenSSH. This announcement was met with a modest and well-deserved fanfare from cryptographers, because RSA sucks.

In the release notes, the OpenSSH team recommends a few migration options. The best of which is the newer ed25519 key type.

However, not all devices support Ed25519 (i.e. OpenWRT). So the OpenSSH team also suggested moving to the rsa-sha2-256 or rsa-sha2-512 key types, but it doesn’t really explain how to do that. Using RSA keys with a SHA-2 hash function is left as an exercise to the reader.

The OpenSSH release notes also states:

The RFC8332 RSA SHA-2 signature algorithms rsa-sha2-256/512. These algorithms have the advantage of using the same key type as “ssh-rsa” but use the safe SHA-2 hash algorithms. These have been supported since OpenSSH 7.2 and are already used by default if the client and server support them.

OpenSSH Release Notes for 8.4 – From the list of alternatives to ssh-rsa

Except, on some Linux distributions, it still defaults to ssh-rsa keys in ssh-keygen, and doesn’t silently upgrade keys to use a SHA-2 family hash function.

Confound you, Fedora! (Art by Khia.)

So here’s a quick guide to migrating existing RSA keys.

Migrating SSH RSA keys to use SHA-2 instead of SHA-1

First, you don’t need to regenerate your SSH keypairs. However, you will need to tweak your public key in two places:

  1. Your client-side public key file (e.g. ~/.ssh/id_rsa.pub)
  2. Your server-side authorized keys file (e.g. ~/.ssh/authorized_keys)

I’ve included a script below that will change either file:

const fs = require('fs').promises;
const args = process.argv.slice(2);
if (args.length === 0) {
    console.error("You must pass a file path");
    process.exit(1);
}

/* length + "rsa-sha2-256" */
const sha256 = Buffer.from([
    0,       0,    0,   12, 0x72, 0x73, 0x61, 0x2d,
    0x73, 0x68, 0x61, 0x32, 0x2d, 0x32, 0x35, 0x36
]);

/**
 * Replace SHA1 with SHA256
 *
 * @param {string} line
 * @returns {Promise}
 */
async function removeSha1(line) {
    const pieces = line.split(' ');
    const decoded = (Buffer.from(pieces[1], 'base64'));
    const spliced = Buffer.concat([
        sha256,
        decoded.slice(11)
    ]);
    return (
        'rsa-sha2-256 ' +
        spliced.toString('base64') +
        ' ' + pieces.slice(2).join(' ')
    );
}

/**
 * Source: https://soatok.blog/2020/11/18/how-to-migrate-rsa-ssh-keys/
 *
 * @param {string} file  Name of the file to read
 * @returns {Promise}
 */
async function exec(file) {
    const lines = (await fs.readFile(file)).toString().split("\n");
    let output = [];
    for (let line of lines) {
        if (line.match(/^ssh-rsa/)) {
            output.push(await removeSha1(line));
        } else {
            output.push(line);
        }
    }
    await fs.writeFile(file, output.join("\n"));
}

exec(args[0]).then(() => {
    console.log('Done');
}).catch((e) => {
    console.error(e.message);
    process.exit(1);
});

Save it as fix-rsa-keys.js then run it like so:

node fix-rsa-keys.js /path/to/file/here

If you’d like to test this script, here is a known answer test (example key before and after conversion).

Before:

ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCzXQxp8GcyMW8r1xfmDiEYNeux6SK7Q4AZXUKMoCZhzKNAo8I/O0olbLZsphjWG2n7CfP9dZTgdDmP2B04zUY+OFNoaDByf+ygXMRbme1muE9xbfWjD4Bb0WP43z4jsLpObXWNM2vkkxyoqppf5mJI3uY6+2ohwGGQnGBzR/K31Z6QOMl1MnFOyjiLNXb2qiUSY0XRpIW3P98HkPao9GXTPJ3b6Ez/qsD8zJ2+0cRHm4Nqu1vq6Up1/2kndkFuTmgQnBgCZdwA05jMu4DtNJTQLBRW/+B/5dPKf/z/BnGcoHGlszyVtE2kFdBOTwJC/ztrs3D+jEjMaezmD2FOwTsn soatok

After:

rsa-sha2-256 AAAADHJzYS1zaGEyLTI1NgAAAAMBAAEAAAEBALNdDGnwZzIxbyvXF+YOIRg167HpIrtDgBldQoygJmHMo0Cjwj87SiVstmymGNYbafsJ8/11lOB0OY/YHTjNRj44U2hoMHJ/7KBcxFuZ7Wa4T3Ft9aMPgFvRY/jfPiOwuk5tdY0za+STHKiqml/mYkje5jr7aiHAYZCcYHNH8rfVnpA4yXUycU7KOIs1dvaqJRJjRdGkhbc/3weQ9qj0ZdM8ndvoTP+qwPzMnb7RxEebg2q7W+rpSnX/aSd2QW5OaBCcGAJl3ADTmMy7gO00lNAsFFb/4H/l08p//P8GcZygcaWzPJW0TaQV0E5PAkL/O2uzcP6MSMxp7OYPYU7BOyc= soatok

And that’s all there is to it.

What Does This Script Do?

First, you’ll need to know the structure of an SSH public key to understand the above JavaScript code.

Every public key used by SSH is encoded in the following format:

type Base64EncodedBlobOfKeyMaterial optionalComment

The structure of the base64-encoded blob is as follows (for RSA keys; all numbers encoded in Big Endian):

  1. 4 bytes: Length of type identifier -> X
  2. X bytes (determined by step 1): The type identifier
  3. 4 bytes: Length of public exponent (e) -> Y
  4. Y bytes: Public exponent (e)
  5. 4 bytes: Length of modulus (n) -> Z
  6. A single NUL byte
  7. Z bytes: Modulus (n)

Since we’re parsing ssh-rsa keys, we can replace the first 11 bytes (4 bytes length, 7 bytes for type) with 16 bytes (4 bytes length, 12 bytes for new type) and the rest of the decoded bytes can be left alone.

This means that the base64-encoded portion of the key will be configured to have rsa-sha2-256 as its type.

Next, all we need to do is replace the prefix (ssh-rsa -> rsa-sha2-256) before the base64-encoded blob, and carry over the comment.

Is that really all there is to it?

Yes!

The hash algorithm used for SSH authentication isn’t a part of your RSA public key. The only parts that matter are e and n; every thing else is runtime configuration (which is what we just changed).

Can I use this script?

I hereby release it under the WTFPL.



This post first appeared on Dhole Moments - Software, Security, Cryptography, And The Furry Fandom, please read the originial post: here

Share the post

How to Migrate RSA SSH Keys

×

Subscribe to Dhole Moments - Software, Security, Cryptography, And The Furry Fandom

Get updates delivered right to your inbox!

Thank you for your subscription

×