tmangin
tmangin

Reputation: 479

A command to display a a key's randomart image from a fingerprint?

I have a ssh-key fingerprint:

16:27:ac:a5:76:28:2d:36:63:1b:56:4d:eb:df:a6:48

I would like to see the randomart image of this fingerprint. Is there a command that take this fingerprint as input and that output the randomart image?

PS: I'm not asking for the -o VisualHostKey option coming with the SSH command.

Upvotes: 12

Views: 9330

Answers (1)

Parsa
Parsa

Reputation: 1065

No. SSH randomart displays the encryption algorithms and the hashing algorithm, and the visual art created from the fingerprint. What you have provided is only the fingerprint of the key, but not the encryption algorithm, nor the hashing algorithm of the fingerprint. As far as I can tell, OpenSSH does not come with a tool to generate the ASCII visual art from the fingerprint itself; however, that fingerprint is generated from a public key that you probably do have access to. If that is the case, you can put that public key in a file and run ssh-keygen -l on it.

For specific key(s):

ssh-keygen -lvf ~/.ssh/<id_whatever_name>

e.g., For all entries in known_hosts (Probably not practical but useful for demonstration)

ssh-keygen -lvf ~/.ssh/known_hosts

For the default key:

ssh-keygen -lv

Command Synopsis:

ssh-keygen -l [-v] [-E <fingerprint_hash>] [-f <input_keyfile>]
  • -l
    • Fingerprint of specified public key file
    • With -v prints both fingerprint and visual ASCII art of the key
  • -E <hash_algorithm>
    • Specify the hash algorithm used when displaying key fingerprints
    • Valid options:
      • sha256 (default)
      • md5 (Older systems only use md5)
  • -f <key file>
    • Specify the ssh key the fingerprint is going to be made from
      • Anything with a valid public key format
        • Includes authorized_keys, known_hosts
    • <key file> may contain a private or a public ssh key
      • Public key can be derived from the private key file by the user with the -y option
        • e.g., ssh-keygen -yf ~/.ssh/id_asghar
    • Man page: https://linux.die.net/man/1/ssh-keygen

Note:

You can obtain the ssh key of an active SSH server with ssh-keyscan <host>

Command Synopsis:

ssh-keyscan [-4|-6] [-f -|<file>] [-H] [-p <port>] [-T <timeout>] [-t <key type>] [-v]
  • -4
    • Only connect to IPv4 hosts
  • -6
    • Only connect to IPv6 hosts
  • -f
    • Read hostnames or <addrlist> <namelist> pair
    • -f -
      • Read from stdin
    • -f <file>
      • Read from file
    • Format
      • <host_address>[,<host_address>...] [<host_name>,[<host_name>...]]
      • One entry per line
      • e.g.,
        • 1.2.3.4,1.2.3.5 hostname.some.domain,some.fqdn,1.2.3.4,1.2.3.5 1.2.3.6 someother.fqdn,1.2.3.7,1.2.3.8
  • -H
    • Hash hostnames in the output
      • A security option
      • Hashes can be used by ssh and sshd
  • -p <port>
    • Port the ssh server is listening on
      • Default: 22
  • -T <timeout>
    • Wait for timeout seconds before giving up
    • Default: 5
  • -t
    • Type(s) of key to get from the ssh server.
      • Multiple types separated with comma
    • Default: display all available keys
    • Valid options:
      • rsa1 (version 1 only)
      • rsa
      • dsa
      • ecdsa
      • ed25519
  • Man page: https://linux.die.net/man/1/ssh-keyscan

e.g., Get the fingerprint and ASCII visual art for the RSA key of github.com

% ssh-keygen -lv -E md5 -f <(ssh-keyscan -t rsa github.com)
# github.com:22 SSH-2.0-babeld-7bdc42c4
2048 MD5:16:27:ac:a5:76:28:2d:36:63:1b:56:4d:eb:df:a6:48 github.com (RSA)
+---[RSA 2048]----+
|        .        |
|       + .       |
|      . B .      |
|     o * +       |
|    X * S        |
|   + O o . .     |
|    .   E . o    |
|       . . o     |
|        . .      |
+------[MD5]------+

However, if you insist on getting the randomart solely from the fingerprint, you probably have to generate it yourself. As I understand, OpenSSH generates the ASCII visual art from the fingerprint using the Drunken Bishop algorithm. Implementing this algorithm seems to be trivial, and in the case of your host with the MD5 fingerprint of 16:27:ac:a5:76:28:2d:36:63:1b:56:4d:eb:df:a6:48, the ASCII visual art, excluding the unprovided encryption algorithm, is:

+---[   n/a  ]----+
|        .        |
|       + .       |
|      . B .      |
|     o * +       |
|    X * S        |
|   + O o . .     |
|    .   E . o    |
|       . . o     |
|        . .      |
+------[MD5]------+

Here is the script:

#!/usr/bin/env python

# NOTE: Requires Python 3+
# usage: drunken_bishop.py [-h] [--mode {md5,sha256}] fingerprint
#
# Generate randomart from fingerprint
#
# positional arguments:
#   fingerprint
#
# optional arguments:
#   -h, --help            show this help message and exit
#   --mode {md5,sha256}, -m {md5,sha256}

import argparse
import base64


def simulate_bishop_stumbles(steps):
    field = [[0] * 17 for _ in range(9)]
    start_position = (4, 8)
    direction_map = {
        "00": (-1, -1),
        "01": (-1, 1),
        "10": (1, -1),
        "11": (1, 1),
    }

    def clip_at_walls(x, y):
        return min(max(x, 0), 8), min(max(y, 0), 16)

    pos = start_position
    for step in steps:
        x, y = pos
        field[x][y] += 1
        dx, dy = direction_map[step]
        pos = clip_at_walls(x + dx, y + dy)

    x, y = start_position
    field[x][y] = 15
    x, y = pos
    field[x][y] = 16

    return field


def get_steps(fingerprint_bytes):
    return [
        "{:02b}".format(b >> s & 3) for b in fingerprint_bytes for s in (0, 2, 4, 6)
    ]


def print_randomart(atrium, hash_mode):
    # Symbols for the number of times a position is visited by the bishop
    # White space means that the position was never visited
    # S and E are the start and end positions
    value_symbols = " .o+=*BOX@%&#/^SE"

    print("+---[   n/a  ]----+")
    for row in atrium:
        symbolic_row = [value_symbols[visits] for visits in row]
        print("|" + "".join(symbolic_row) + "|")
    print("+" + ("[" + hash_mode.upper() + "]").center(17, "-") + "+")


def get_bytes(fingerprint, hash_mode):
    if hash_mode == "md5":
        return [int(i, 16) for i in fingerprint.split(":")]
    elif hash_mode == "sha256":
        missing_padding = 4 - (len(fingerprint) % 4)
        fingerprint += "=" * missing_padding
        return base64.b64decode(fingerprint)
    raise RuntimeError("Unsupported hashing mode: {}".format(hash_mode))


def get_argparser():
    parser = argparse.ArgumentParser(description="Generate randomart from fingerprint")
    parser.add_argument("--mode", "-m", choices=["md5", "sha256"], default="sha256")
    parser.add_argument("fingerprint", type=str)
    return parser


def drunken_bishop(fingerprint, hash_mode):
    fingerprint_bytes = get_bytes(fingerprint, hash_mode)
    steps = get_steps(fingerprint_bytes)
    atrium_state = simulate_bishop_stumbles(steps)
    print_randomart(atrium_state, hash_mode)


if __name__ == "__main__":
    parser = get_argparser()
    args = parser.parse_args()
    drunken_bishop(args.fingerprint, args.mode)

Upvotes: 19

Related Questions