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

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.

No. What you have provided is only the MD5 fingerprint of the key. SSH randomart displays the encryption algorithms and the hashing algorithm, and the visual art created from the fingerprint. OpenSSH does not seem to come with a tool to generate the ASCII visual art from the fingerprint itself, but 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.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
-v
Verbose output
Can be repeated several times to increase verbosity level as desired
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 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
try:
import numpy as np
except ImportError as ex:
import sys
print(
str(ex) + '. Make sure that numpy is installed and available.\n',
file=sys.stderr)
sys.exit(1)
def get_steps(bits):
bits_grouped = np.array(bits, dtype=np.int8).reshape((-1, 4, 2))
bits_grouped_reordered = np.flip(bits_grouped, axis=1)
return bits_grouped_reordered.reshape((-1, 2))
def drunken_bishop(steps):
field = np.zeros((9, 17), dtype=np.int8)
start_position = (4, 8)
def walk(start_position):
def get_step_direction(b):
direction_map = {
(0, 0): (-1, -1),
(0, 1): (-1, 1),
(1, 0): (1, -1),
(1, 1): (1, 1)
}
return direction_map[tuple(b)]
def adjust_for_walls(pos, field_dims):
return np.maximum(np.minimum(pos, field_dims), (0, 0))
pos = np.array(start_position)
for step in steps:
field[tuple(pos)] += 1
pos += get_step_direction(step)
pos = adjust_for_walls(pos, field.shape)
return tuple(pos)
end_position = walk(start_position)
field[start_position] = 15
field[end_position] = 16
return field
def print_randomart(atrium, hash_mode):
# Symbols for the number of times a position is visited by the bishop
values = {
0: ' ', # Initial value
1: '.', 2: 'o', 3: '+', 4: '=', 5: '*', 6: 'B', 7: 'O', 8: 'X',
9: '#', 10: '%', 11: '&', 12: '#', 13: '/', 14: '^',
15: 'S', # Start position
16: 'E' # End position
}
print('+---[ n/a ]----+')
for row in atrium:
symbolic_row = [values[visit_count] for visit_count in row]
print('|{}|'.format(''.join(symbolic_row)))
if hash_mode == 'md5':
print('+------[MD5]------+')
elif hash_mode == 'sha256':
print('+----[SHA256]-----+')
else:
raise RuntimeError('Unsupported hashing mode: {}'.format(hash_mode))
def get_bits(fingerprint, hash_mode):
def get_md5_bits(fingerprint):
return np.array([list('{:08b}'.format(int(i, 16)))
for i in fingerprint.split(':')])
def get_sha256_bits(fingerprint):
missing_padding = 4 - (len(fingerprint) % 4)
fingerprint += '=' * missing_padding
return np.array([list('{:08b}'.format(i))
for i in base64.b64decode(fingerprint)])
if hash_mode == 'md5':
return get_md5_bits(fingerprint)
elif hash_mode == 'sha256':
return get_sha256_bits(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 main():
parser = get_argparser()
args = parser.parse_args()
bits = get_bits(args.fingerprint, args.mode)
steps = get_steps(bits)
atrium = drunken_bishop(steps)
print_randomart(atrium, args.mode)
if __name__ == '__main__':
main()

Related

How to merge multiple markdown files with pandoc while retaining cross document links?

I am trying to merge multiple markdown documents in a single folder together into a PDF with pandoc.
The documents may contain links to each other which should be browseable in the markdown format, e.g. through IntelliJ or within GitLab.
Simple example documents:
0001-file-a.md
---
id: 0001
---
# File a
This is a simple file without an [external link](www.stackoverflow.com).
0002-file-b.md
---
id: 0002
---
# File b
This file links to [another file](0001-file-a.md).
By default pandoc does not handle this case out of the box, e.g. when running the following command:
pandoc -s -f markdown -t pdf *.md -V linkcolor=blue -o test.pdf
It merges the files, creates a PDF and highlights the links correctly, but when clicking the second link it wants to open the file instead of jumping to the right location in the document.
This problem has been experienced by many before me but none of the solutions I found so far have solved it. The closest I came was with the help of this answer: https://stackoverflow.com/a/61908457/6628753
It defines a filter that is first applied to each file and then the resulting JSON files are merged.
I modified this filter to fit my needs:
Add the number of the file to the label of the top-level header
Prepend the top-level header to all other header labels
Remove .md from internal links
Here is the filter:
#!/usr/bin/env python3
from pandocfilters import toJSONFilter, Header, Link
import re
import sys
"""
Pandoc filter to convert internal links for multifile documents
"""
headerL1 = []
def fix_links(key, value, format, meta):
global headerL1
# Store level 1 headers
if key == "Header":
[level, [label, t1, t2], header] = value
if level == 1:
id = meta.get("id")
newlabel = f"{id['c'][0]['c']}-{label}"
headerL1 = [newlabel]
sys.stderr.write(f"\nGlobal header: {headerL1}\n")
return Header(level, [newlabel, t1, t2], header)
# Prepend level 1 header label to all other header labels
if level > 1:
prefix = headerL1[0]
newlabel = prefix + "-" + label
sys.stderr.write(f"Header label: {label} -> {newlabel}\n")
return Header(level, [newlabel, t1, t2], header)
if key == "Link":
[t1, linktext, [linkref, t4]] = value
if ".md" in linkref:
newlinkref = re.sub(r'.md', r'', linkref)
sys.stderr.write(f'Link: {linkref} -> {newlinkref}\n')
return Link(t1, linktext, [newlinkref, t4])
else:
sys.stderr.write(f'External link: {linkref}\n')
if __name__ == "__main__":
toJSONFilter(fix_links)
And here is a script that executes the whole thing:
#!/bin/bash
MD_INPUT=$(find . -type f | grep md | sort)
# Pass the markdown through the gitlab filters into Pandoc JSON files
echo "Filtering Gitlab markdown"
for file in $MD_INPUT
do
echo "Filtering $file"
pandoc \
--filter fix-links.py \
"$file" \
-t json \
-o "${file%.md}.json"
done
JSON_INPUT=$(find . -type f | grep json | sort)
echo "Generating LaTeX"
pandoc -s -f json -t latex $JSON_INPUT -V linkcolor=blue -o test.tex
echo "Generating PDF"
pandoc -s -f json -t pdf $JSON_INPUT -V linkcolor=blue -o test.pdf
Applying this script generates a PDF where the second link does not work at all.
Looking at the LaTeX code the problem can be solved by replacing the generated \href directive with \hyperlink.
Once this is done the linking works as expected.
The problem now is that this isn't done automatically by pandoc, which almost seems like a bug.
Is there a way to tell pandoc a link is internal from within the filter?
After running the filter it is non-trivial to fix the issue since there is no good way to differentiate internal and external links.

AES encryption in Perl 6?

I'm trying to convert a module writing in Python to Perl 6, I find there is no AES method in Perl 6:
from Cryptodome.Cipher import AES
import base64
def aes(text, key):
pad = 16 - len(text) % 16
text = text + bytearray([pad] * pad)
encryptor = AES.new(key, 2, b"0102030405060708")
ciphertext = encryptor.encrypt(text)
return base64.b64encode(ciphertext)
Is there any module writing in Perl 6 that implemented AES method?
It seems that the OpenSSL module provides access to various AES ciphers. It depends on the openssl library being available (although on Windows I believe it downloads the DLL as part of the module installation process).
With that installed (zef install OpenSSL), one can:
use OpenSSL::CryptTools;
And then use encrypt/decrypt:
# Fake IV and key
my $iv = ('0' x 16).encode;
my $key = ('xy' x 16).encode;
# Encrypt.
my $ciphertext = encrypt("asdf".encode, :aes256, :$iv, :$key);
say $ciphertext;
# Decrypt.
say decrypt($ciphertext, :aes256, :$iv, :$key).decode;
See these tests for more examples.

SHA-256 Hash and base64 encoding

With the string "123456Adrian Wapcaplet" I need to
generate the SHA-256 hash
take the least significant 128 bits of the hash
represent these bits in base64 encoding
I need help with #1 and #3. Below is how I generate this in Linux
$ echo -n "123456Adrian Wapcaplet" | shasum -a 256 | cut -c33-64 | xxd -r -p | base64
jNWe9TyaqmgxG3N2fgl15w==
Install the pgcrypto extension:
create extension pgcrypto;
Then you can use the digest function to do sha256. The rest of the functions you'll need are built in:
select encode(substring(digest('123456Adrian Wapcaplet', 'sha256') from 17), 'base64');
Note that there's an implicit cast from text to bytea performed here when calling digest. It will use the default encoding for the database. To avoid problems due to environmental differences you can specify the encoding for the conversion:
digest(convert_to('123456Adrian Wapcaplet', 'utf8'), 'sha256')

Code Sample for Reading Values from Hyper-V KVP component on Linux (aka KVP Data Exchange)

Hyper-V includes a KVP component that transmits key / value pairs between the host and a guest VM.
Code samples for sending and receiving values are available for Windows Guests in PowerShell in WMI.
However, my guest is using a Linux version of this service.
Where can I find a sample Linux script that queries this service for key / value pairs?
Key details from a blog entry I wrote covering the problem. (I could not find the answer elsewhere):
First, make sure you have the KVP service installed.
KVP data is transferred to the Linux file system through the collaboration of a kernel driver and a user mode daemon.
The [KVP driver code], hv_kvp.c, is compiled into the hv_util kernel module Source). Since the driver is part of the Linux kernel code, it is provided by default with recent versions of common Linux distributions. E.g.
[root#centos6-4-hv ~]# cat /etc/*-release
CentOS release 6.4 (Final)
CentOS release 6.4 (Final)
CentOS release 6.4 (Final)
[root#centos6-4-hv ~]# modinfo -F filename hv_utils
/lib/modules/2.6.32-358.el6.i686/kernel/drivers/hv/hv_utils.ko
However, it is the usermode daemon, hv_kvp_daemon, that copies KVP data to the system. On startup, hv_kvp_daemon creates files to store kvp data under
/var/lib/hyperv (source). Each file is known as a 'pool', and there is a file for each data pool. E.g.
[root#centos6-4-hv hyperv]# ls -al /var/lib/hyperv/
total 36
drwxr-xr-x. 2 root root 4096 Sep 11 21:33 .
drwxr-xr-x. 16 root root 4096 Sep 10 13:59 ..
-rw-r--r--. 1 root root 2560 Sep 10 17:05 .kvp_pool_0
-rw-r--r--. 1 root root 0 Sep 10 14:02 .kvp_pool_1
-rw-r--r--. 1 root root 0 Sep 10 14:02 .kvp_pool_2
-rw-r--r--. 1 root root 28160 Sep 10 14:02 .kvp_pool_3
-rw-r--r--. 1 root root 0 Sep 10 14:02 .kvp_pool_4
The prefix of each file is the pool number. This corresponds to the KVP source. E.g. remember that source '0' is used for transmitting data from host to guest? That means our KVP data is in /var/lib/hyperv/.kvp_pool_0. E.g.
[root#centos6-4-hv hyperv]# cat /var/lib/hyperv/.kvp_pool_0
cloudstack-vm-userdatausername=root;password=1pass#word1[root#centos6-4-hv hyperv]#
These KVP data files contain an array of key / value pairs. Each is a byte array of a fixed size. (source)
/*
* Maximum key size - the registry limit for the length of an entry name
* is 256 characters, including the null terminator
*/
#define HV_KVP_EXCHANGE_MAX_KEY_SIZE (512)
/*
* bytes, including any null terminators
*/
#define HV_KVP_EXCHANGE_MAX_VALUE_SIZE (2048)
The byte array contains a UTF-8 encoded string, which is padded out to the max size with null characters. However, null string termination is not guaranteed (see kvp_send_key).
Provided there is only one key and the key name known, the easiest way to parse the file is to use sed. To remove null characters and the key name used in our example, you would use the following:
[root#centos6-4-hv hyperv]# cat /var/lib/hyperv/.kvp_pool_0 | sed 's/\x0//g' | sed 's/cloudstack-vm-userdata//g' > userdata
[root#centos6-4-hv hyperv]# more userdata
username=root;password=1pass#word1
Here is a bash script to read the key-value pairs for a given file:
#!/bin/bash
fname=$1
echo "Reading $fname"
nb=$(wc -c < $1)
nkv=$(( nb / (512+2048) ))
for n in $(seq 0 $(( $nkv - 1 )) ); do
offset=$(( $n * (512 + 2048) ))
k=$(dd if=$fname count=512 bs=1 skip=$offset status=none | sed 's/\x0.*//g')
v=$(dd if=$fname count=2048 bs=1 skip=$(( $offset + 512 )) status=none | sed 's/\x0.*//g')
echo "$k = $v"
done
Same functionality in java:
String guest_param_file="/var/lib/hyperv/.kvp_pool_3";
if (new File(guest_param_file).exists())
{
try
{
BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(guest_param_file),"UTF-8"));
char [] ckey=new char[512] ;
char [] cvalue=new char[2048] ;
while (true)
{
int charcount=br.read(ckey);
if (charcount==-1)
{
break;
}
br.read(cvalue);
String key=new String(ckey).trim();
String value=new String(cvalue).trim();
System.out.println( key+" = "+value);
}
br.close();
}
catch (UnsupportedEncodingException ex)
{
}
catch (FileNotFoundException ex)
{
}
catch (IOException ex)
{
}
}

Finding non-expiring keys in Redis

In my setup, the info command shows me the following:
[keys] => 1128
[expires] => 1125
I'd like to find those 3 keys without an expiration date. I've already checked the docs to no avail. Any ideas?
Modified from a site that I can't find now.
redis-cli keys "*" | while read LINE ; do TTL=`redis-cli ttl "$LINE"`; if [ $TTL -eq -1 ]; then echo "$LINE"; fi; done;
edit: Note, this is a blocking call.
#Waynn Lue's answer runs but uses the Redis KEYS command which Redis warns about:
Warning: consider KEYS as a command that should only be used in production environments with extreme care. It may ruin performance when it is executed against large databases.
Redis documentation recommends using SCAN.
redis-cli --scan | while read LINE ; do TTL=`redis-cli ttl "$LINE"`; if [ $TTL -eq -1 ]; then echo "$LINE"; fi; done;
If you want to scan for a specific key pattern, use:
redis-cli --scan --pattern "something*"
In case somebody is getting bad arguments or wrong number of arguments error,
put double quotes around $LINE.
So,it would be
redis-cli keys "*" | while read LINE ; do TTL=`redis-cli ttl "$LINE"`; if [ $TTL -eq -1 ]; then echo "$LINE"; fi; done;
This happens when there are spaces in the key.
To me the accepted answer appears unusable for a medium-sized dataset, as it will run a redis-cli command for each and every key.
Instead I used this lua script to filter the keys inside the redis server:
local show_persistent = ARGV[1] ~= "expiring"
local keys = {}
for i, name in ipairs(redis.call("keys", "*")) do
local persistent = redis.call("pttl", name) < 0
if persistent == show_persistent then
table.insert(keys, name)
end
end
return keys
This can be called as
$ redis-cli --eval show-persistent-keys.lua
to get all keys without an expiration time. It also can be called as
$ redis-cli --eval show-persistent-keys.lua , expiring
to find the opposite key set of all keys with an expiration time set.
On the downside this may block for too long (appears fine for 1 M keys). I'd use scan instead but I happen to have to run this against a legacy Redis at version 2.6, which does not have scan available.
I needed to extract non-expiring keys from bigger (40GB) dataset, so using keys command was not suitable for me. So in case someone is looking for offline/non-blocking solution, you can use https://github.com/sripathikrishnan/redis-rdb-tools for extraction of non-expiring keys from redis rdb dump.
You can just install libraries via pip:
pip install rdbtools python-lzf
Then create simple parser which extracts keys and values which has expiration set to None:
from rdbtools import RdbParser, RdbCallback
from rdbtools.encodehelpers import bytes_to_unicode
class ParserCallback(RdbCallback):
def __init__(self):
super(ParserCallback, self).__init__(string_escape=None)
def encode_key(self, key):
return bytes_to_unicode(key, self._escape, skip_printable=True)
def encode_value(self, val):
return bytes_to_unicode(val, self._escape)
def set(self, key, value, expiry, info):
if expiry is None:
print('%s = %s' % (self.encode_key(key), self.encode_value(value)))
callback = ParserCallback()
parser = RdbParser(callback)
parser.parse('/path/to/dump.rdb')
#!/usr/bin/env python
import argparse
import redis
p = argparse.ArgumentParser()
p.add_argument("-i", '--host', type=str, default="127.0.0.1", help="redis host", required=False)
p.add_argument("-p", '--port', type=int, default=6379, help="redis port", required=False)
p.add_argument("-n", '--db', type=int, default=0, help="redis database", required=False)
args = p.parse_args()
r = redis.Redis(host=args.host, port=args.port, db=args.db)
try:
keys = r.keys()
for key in keys:
if r.ttl(key) < 0:
print(key)
except KeyboardInterrupt:
pass