Re: Factor

Factor: the language, the theory, and the practice.

Drunken Bishop

Sunday, August 27, 2023

#math

The OpenSSH project is a widely available tool for working with the SSH protocol in a variety of ways on a variety of operating systems. Their project description states:

OpenSSH is the premier connectivity tool for remote login with the SSH protocol. It encrypts all traffic to eliminate eavesdropping, connection hijacking, and other attacks. In addition, OpenSSH provides a large suite of secure tunneling capabilities, several authentication methods, and sophisticated configuration options.

One of the interesting features that it contains is a method of visualizing public key fingerprints to allow a user to more easily see that a key has changed by examining a visual output that looks something like this:

+----[RSA 2048]---+
|        . o.+o  .|
|     . + *  +o...|
|      + * .. ... |
|       o + .   . |
|        S o   .  |
|         o     . |
|          .     o|
|               .o|
|               Eo|
+------[MD5]------+

This is the Drunken Bishop algorithm, a variant of a technique called random art that was originally described in the paper Hash Visualization: a New Technique to improve Real-World Security. You can see more information about it in The drunken bishop: An analysis of the OpenSSH fingerprint visualization algorithm.

This OpenSSH feature is controlled by the VisualHostKey flag:

VisualHostKey

If this flag is set to yes, an ASCII art representation of the remote host key fingerprint is printed in addition to the fingerprint string at login and for unknown host keys. If this flag is set to no (the default), no fingerprint strings are printed at login and only the fingerprint string will be printed for unknown host keys.

This can be enabled by adding to your ~/.ssh/config:

VisualHostKey yes

Or by adding this option in your ssh command:

$ ssh -o VisualHostKey=yes your.host.name

Implementation

We are going to be implementing this in the Factor programming language.

The algorithm begins by defining a visual board – by default 9 rows by 17 columns – and a starting position in the middle of the board. Each 8-bit byte of input is split into 2-bit groups which indicate where the bishop moves:

Value Direction
00
01
10
11

As the bishop moves around the board diagonally, it increments a counter in each cell. However, the bishop cannot move through the walls on the edge of the board, remaining stuck in whichever direction is blocked.

CONSTANT: WIDTH 17
CONSTANT: HEIGHT 9

:: drunken-bishop ( bytes -- board )
    HEIGHT [ WIDTH 0 <array> ] replicate :> board
    HEIGHT 2/ :> y!
    WIDTH 2/ :> x!

    15 x y board nth set-nth ! starting position

    bytes [
        { 0 -2 -4 -6 } [
            shift 2 bits {
                { 0b00 [ -1 -1 ] }
                { 0b01 [ -1  1 ] }
                { 0b10 [  1 -1 ] }
                { 0b11 [  1  1 ] }
            } case :> ( dy dx )
            dy y + 0 HEIGHT 1 - clamp y!
            dx x + 0 WIDTH 1 - clamp x!
            x y board nth [ dup 14 < [ 1 + ] when ] change-nth
        ] with each
    ] each

    16 x y board nth set-nth ! ending position

    board ;

The output is rendered using an .o+=*BOX@%&#/^ alphabet with S for the starting position and E for the ending position specially rendered:

CONSTANT: SYMBOLS " .o+=*BOX@%&#/^SE"

: drunken-bishop. ( bytes -- )
    drunken-bishop [ SYMBOLS nths print ] each ;

We can try this out and see that it works:

IN: scratchpad "fc94b0c1e5b0987c5843997697ee9fb7"
               hex-string>bytes drunken-bishop.
       .=o.  .   
     . *+*. o    
      =.*..o     
       o + ..    
        S o.     
         o  .    
          .  . . 
              o .
               E.

This is available in the drunken-bishop vocabulary in a recent development version.