OpenSSH秘钥指纹图像生成算法

OpenSSH秘钥指纹图像生成算法


使用 SSH 秘钥生成时产生疑惑,它的 randomart image 是如何生成的?下面进行了探索和研究

引入

生成位数为 4096 位的 rsa 公私钥

ssh-keygen -t rsa -b 4096
Generating public/private rsa key pair.
Enter file in which to save the key (/root/.ssh/id_rsa):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /root/.ssh/id_rsa
Your public key has been saved in /root/.ssh/id_rsa.pub
The key fingerprint is:
SHA256:zUAZt7Ydw2s0LQqupcMjvB1KtniGX344tnxl2CE3zUg root@ArchLinux
The key's randomart image is:
+---[RSA 4096]----+
|        oo.      |
|       ...Eo .   |
|        o.o+B .  |
|       ..*==o*   |
|        S==o+    |
|   . . +. +.     |
|   .= O. o       |
|  .+oX++o        |
|  .+=.==         |
+----[SHA256]-----+

这张图到底是如何生成的?我十分好奇

算法

OpenSSH-5.1(2008)引入了这种指纹可视化方案
醉酒的主教(drunken bishop)
根据秘钥的指纹转换为字节流,从场地中心开始根据字节流向四周游走,记录每个像素途径的次数,根据次数进行打印

图片名称

场地

定义场地宽为 17 个字符,高为 9 个字符,起点位于场地中心 (8, 4)

  01234567890123456
 +-----------------+ x
0|                 |
1|                 |
2|                 |
3|                 |
4|        S        |
5|                 |
6|                 |
7|                 |
8|                 |
 +-----------------+
 y

移动

以上面的秘钥指纹为例 SHA256:zUAZt7Ydw2s0LQqupcMjvB1KtniGX344tnxl2CE3zUg
首先通过 Base64 解码转换成 16进制 CD4019B7B61DC36B342D0AAEA5C323BC1D4AB678865F7E38B67C65D82137CD48
然后根据字节流进行拆分

Hex     |    C D    |    4 0    |    1 9    |    B 7    | ...
Bits    |11 00 11 01|01 00 00 00|00 01 10 01|10 11 01 11| ...
Steps   | 4  3  2  1| 8  7  6  5|12 11 10  9|16 15 14 13| ...

得到数组 ['01', '11', '00', '11', '00', '00', '00', '01', '01', '10', '01', '00', '11', '01', '11', '10', ...]

这个数组就是后续移动的步骤

定义移动方向 00 ↖ 01 ↗ 10 ↙ 11 ↘00 左上 01 右上 10 左下 11 右下

移动到边缘时:例如向左上,但是上方是边界,则向左走

图片名称

打印

根据移动步骤移动后,每个像素就会有相应的经过次数,根据次数打印

起点(start) : S
终点(end) : E

每个值对应的字符:

次数:  0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16
字符:     .  o  +  =  *  B  O  X  @  %  &  #  /  ^  S  E  
 .o+=*BOX@%&#/^SE

代码实现

#!/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)

使用

python drunken_bishop.py --mode sha256 zUAZt7Ydw2s0LQqupcMjvB1KtniGX344tnxl2CE3zUg
+---[   n/a  ]----+
|        oo.      |
|       ...Eo .   |
|        o.o+B .  |
|       ..*==o*   |
|        S==o+    |
|   . . +. +.     |
|   .= O. o       |
|  .+oX++o        |
|  .+=.==         |
+-----[SHA256]----+

引用

openssh.com/txt/release-5.1 OpenSSH 5.1 更新日志
Making art with SSH key randomart GIF图片引用,算法解析
The drunken bishop: An analysis of the OpenSSH fingerprint visualization algorithm 算法解析,马尔科夫分析
A command to display a a key's randomart image from a fingerprint? - Stack Overflow 代码引用

posted @   HUOE  阅读(130)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示