Relentless Coding

A Developer’s Blog

Cisco IOS SSH

While connecting to a Cisco device out-of-band (i.e. not using IP) using a serial connection is the most secure way to configure the device, we also need to be able to connect over the network. Telnet works, but is not secure. So we need SSH. This posts looks at how to set up SSH with username and password on a Cisco device, how to use public-key certificates as a best practice to authenticate and how to disable SSH altogether if it is not needed.

Cisco, of course, has pages upon pages of discussion on the most common operations with SSH with examples. They are the authoritative source, so consult their documentation when necessary.

Set Up SSH with Username & Password

Before you can generate an RSA server host key, the hostname and the domain name of the device need to be set. Together, those make a Fully-Qualified Domain Name (FQDN). That is how Cisco IOS identifies the key. Although it’s not entirely clear to me why it needs this FQDN before generating an RSA key pair.

Router# conf t
Router(config)# hostname ISR4300
ISR4321(config)# ip domain-name example.invalid

Now, we can generate the host key:

ISR4321(config)# crypto key generate rsa
The name for the keys will be: ISR4300.example.invalid
Choose the size of the key modulus in the range of 360 to 4096 for your
General Purpose Keys. Choosing a key modulus greater than 512 may take
a few minutes.

How many bits in the modulus [512]: 3072
% Generating 3072 bit RSA keys, keys will be non-exportable...[OK]

Note: according to NIST, [3072 bit RSA keys are recommended and considered secure through 2030 and beyond][NIST]. The newest versions of IOS should also support Ed25519.

SSH only works with usernames in addition to passwords:

ISR4321(config)# username stefan secret stefan

Enable only SSH authentication (not telnet or anything else):

ISR4321# conf t
ISR4321(config)# line vty 0 15
ISR4321(config-line)# transport input ssh
ISR4321(config-line)# login local
ISR4321(config-line)# no password
ISR4321(config-line)# end

If you set login with a password, 1 of 2 things might happen:

  1. Configured users can use their username but share the same password
  2. Nobody can log in, neither with their own password, nor with the shared password.

So setting login local gets rid of this confusing mess: it will enable everybody to use their own credentials.

For good measure, enable only SSH 2, and harden the configuration a bit:

ISR4321(config)# ip ssh version 2
ISR4321(config)# ip ssh authentication-retries 2
ISR4321(config)# ip ssh time-out 60

ip ssh authentication-retries 2 shuts down the connection after 2 failed authentication attempts.

ip ssh time-out 60 shuts down the connection when not receiving authentication from the client after a minute. (In my experimentation, it looks like IOS might not respect a timeout of less than 30 seconds, though.)

Show information about the configured SSH server:

ISR4321# sh ip ssh
SSH Enabled - version 2.0
Authentication timeout: 60 secs; Authentication retries: 2

Show information about connected SSH clients:

SW01#sh ssh
Connection  Version  Mode  Encryption  Hmac       State             Username  
0           1.99     IN    aes128-cbc  hmac-sha1  Session Started   stefan
0           1.99     OUT   aes128-cbc  hmac-sha1  Session Started   stefan

Change how long an SSH or Telnet session lingers with exec-timeout <min> <sec>. By default, the SSH server disconnects after 5 minutes. Change it to 30 minutes and 50 seconds:

ISR4321(config)# line vty 0 15
ISR4321(config-line)# exec-timeout 30 50

Use Public Keys Instead of Passwords

To only accept public-key authentication, disabling password and keyboard-interactive authentication:

ISR4321(config)# ip ssh server algorithm authentication publickey

Use an existing RSA key or generate a new one on your machine:

linux$ C="$(hostname)-$USER-remotehost-remoteuser-$(date -I)"
linux$ ssh-keygen -t rsa -f ~/.ssh/"$C" -C "$C"

Now, import user’s public key into IOS. There is one thing to know, you cannot just copy paste the entire RSA public key. IOS does not allow that amount of input. So you need to cut the public-key string up into bits of say 100 characters each (apparently, up to 254 characters). Yeah, not very handy:

ISR4321(config)# ip ssh pubkey-chain
ISR4321(conf-ssh-pubkey)# username stefan
ISR4321(conf-ssh-pubkey-user)# key-string
ISR4321(conf-ssh-pubkey-data)# AAAAB3NzaC1yc2EAAAADAQABAAABgQCq8kjk8UYdDYco+U9glX74dm0x1AzBJO0Gnj9tfjcR29HJC1TxauyDuni0QIfkoqr8AMU
ISR4321(conf-ssh-pubkey-data)# ua8zl5h8iTbQe1ErBPBLyevTEfCgA+JOMx9RaBytSg8S5JCR+ur65zuBLU3AhrogtadJiprr6SBcgfIh5Ryrj/oqRzBD+WTskQTdGNeBBn5
ISR4321(conf-ssh-pubkey-data)# ... etc ...
ISR4321(conf-ssh-pubkey-data)# exit

You could also specify the hash of your public key instead:

ISR4321(config)# ip ssh pubkey-chain
ISR4321(conf-ssh-pubkey)# username stefan
ISR4321(conf-ssh-pubkey-user)# key-hash ssh-rsa 7f7f72bfa276b87f44aaca363042c344 test

On my (old) version of IOS (16.06.04), the key-hash command takes a key-type (which can only be ssh-rsa) and the hex-encoded MD5 digest of your public key. Lastly, you can enter an optional comment (test in my case).

To calculate the hex-encoded MD5 digest of public key with openssl, first cut the base64-encoded public key from the public-key file (2nd field):

$ cut -d ' ' -f2 < pub.key | \
    openssl base64 -d | \
    openssl dgst -md5
MD5(stdin)= 7f7f72bfa276b87f44aaca363042c344

After pasting the base64-encoded public key or entering the MD5 hash, you can now authenticate with your public key:

$ ssh -l stefan -i ~/.ssh/yourkey 192.168.88.254

Disabling SSH

I am a big fan of disabling services I do not use. That decreases attack surface and is good security hygiene. I cannot find a way to disable the listening socket on port 22, but I can disable any kind of meaningful interaction on the port:

ISR4321# conf t
ISR4321(config)# line vty 0 15
ISR4321(config-line)# no transport input

IOS will now send a TCP FIN immediately when my SSH client starts to talk:

$ sudo tcpdump -nti eth1 port ssh
IP 192.168.88.252.38808 > 192.168.88.254.22: Flags [S], seq 1614430111, win 64240, options [mss 1460,sackOK,TS val 72081744 ecr 0,nop,wscale 7], length 0
IP 192.168.88.254.22 > 192.168.88.252.38808: Flags [S.], seq 1208382352, ack 1614430112, win 4128, options [mss 1460], length 0
IP 192.168.88.252.38808 > 192.168.88.254.22: Flags [.], ack 1, win 64240, length 0
IP 192.168.88.252.38808 > 192.168.88.254.22: Flags [P.], seq 1:22, ack 1, win 64240, length 21: SSH: SSH-2.0-OpenSSH_9.9
IP 192.168.88.254.22 > 192.168.88.252.38808: Flags [.], ack 22, win 4107, length 0
IP 192.168.88.254.22 > 192.168.88.252.38808: Flags [FP.], seq 1, ack 22, win 4107, length 0
IP 192.168.88.252.38808 > 192.168.88.254.22: Flags [F.], seq 22, ack 2, win 64239, length 0
IP 192.168.88.254.22 > 192.168.88.252.38808: Flags [.], ack 23, win 4107, length 0

no transport input does disable telnet on port 23 entirely, though:

$ sudo tcpdump -nti eth1 port telnet
IP 192.168.88.252.59455 > 192.168.88.254.23: Flags [S], seq 3152383467, win 1024, options [mss 1460], length 0
IP 192.168.88.254.23 > 192.168.88.252.59455: Flags [R.], seq 0, ack 3152383468, win 0, length 0

Obtain Server’s Public Key

On the client side, we need to know the server’s public key, so we can be sure nobody is playing miscreant in the middle:

ISR4321#sh ip ssh
SSH Enabled - version 1.99
Authentication methods:publickey,keyboard-interactive,password
Authentication Publickey Algorithms:x509v3-ssh-rsa,ssh-rsa
Hostkey Algorithms:x509v3-ssh-rsa,ssh-rsa
Encryption Algorithms:aes128-ctr,aes192-ctr,aes256-ctr
MAC Algorithms:hmac-sha2-256,hmac-sha2-512,hmac-sha1,hmac-sha1-96
KEX Algorithms:diffie-hellman-group-exchange-sha1,diffie-hellman-group14-sha1
Authentication timeout: 120 secs; Authentication retries: 3
Minimum expected Diffie Hellman key size : 2048 bits
IOS Keys in SECSH format(ssh-rsa, base64 encoded): TP-self-signed-3738675831
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCn62IP88wHq9NilUvTuQ2Min+1CZQ+N8NtFyamsNaP
gPibo71xq9joiCUQQcdi/tZYqsUfsGQQJ05QXlDj81z2EEnqZ+a3GK7XjulbStWdY2innc+UA//gYrhj
21lar4PSsuFjz2VoBYxEoloh/Mk4o068IjeMNWrV3zADgAyHwigfYJyNdpoDkIM/PMb0J1XKR65/cCJz
710olSBTorYdrmLEVp+mGnnGUfVQr4xovVeU/Sze1foutzVEkds6E9Olxn6bCqHjbiIetLtwiCYDEyLy
tjPA2SIYtmltCi/TByLl36cR6tNj6PiFMgvb1UkjwiF1Cse6WvKLzxS9k0M3

The line starting with “IOS Keys” is the server’s public key. If you copy it to a Unix box, you can calculate the SHA256 fingerprint as follows:

$ ssh-keygen -l -f isr4300.pub

Or using openssl:

$ openssl base64 -d < isr4300.pub | \
    openssl dgst -sha256 -binary | \
    openssl base64
CRqX+T3a/48l6GT00Tgz3SmnkcNwlgt0B+vFI8VuzxI=

Either paste this hash when ssh(1) prompts to verify the remote host’s fingerprint (SHA256:CRqX+T3a/48l6GT00Tgz3SmnkcNwlgt0B+vFI8VuzxI=) or store the public key in ~/.ssh/known_hosts:

192.168.88.254 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCn62IP88wHq9NilUvTuQ2Min+1CZQ+N8NtFyamsNaPgPibo71xq9joiCUQQcdi/tZYqsUfsGQQJ05QXlDj81z2EEnqZ+a3GK7XjulbStWdY2innc+UA//gYrhj21lar4PSsuFjz2VoBYxEoloh/Mk4o068IjeMNWrV3zADgAyHwigfYJyNdpoDkIM/PMb0J1XKR65/cCJz710olSBTorYdrmLEVp+mGnnGUfVQr4xovVeU/Sze1foutzVEkds6E9Olxn6bCqHjbiIetLtwiCYDEyLytjPA2SIYtmltCi/TByLl36cR6tNj6PiFMgvb1UkjwiF1Cse6WvKLzxS9k0M3

Now we can connect to our Cisco device with the peace of mind that we are only talking to the device, and nobody else.