Clefs SSH: génération, usage, programmation
par Benoit, 2013-04-22

SSH est la manière moderne d’exécuter une session de shell sur un hôte distant, et son implantation libre OpenSSH est l’un de mes outils préférés. Dans ce texte, je présente comment on peut générer une paire de clefs publique et privée de manière à s’authentifier à l’hôte sans transfert d’information privée. En boni, je décris aussi comment une telle paire de clef peut être utilisée pour mettre en oeuvre la cryptographie RSA en Python.

SSH en quelques phrases

SSH est un protocole de communication permettant des échanges fortement encryptés entre deux hôtes. Son usage principal est l’exécution de programmes sur un hôte distant, particulièrement un shell, via la commande ssh. Cet outil remplace avantageusement rsh et telnet, les protocoles non encryptés utilisés en ce sens auparavant. Il existe plusieurs implantations de SSH pour Unix et Windows. L’une des plus répandues demeure OpenSSH, distribuée sous la licence libre BSD. OpenSSH est distribué sur la plupart des systèmes Unix et des distributions GNU/Linux, ainsi que sur Mac OS X.

Authentification par clefs asymétriques

Lorsqu’un client se connecte par SSH à un hôte, ce dernier authentifie le premier contre un des comptes qu’il comporte. Bien que cette authentification peut être réalisée par l’échange conventionnel d’un nom d’usager et d’un mot de passe, il existe une approche arguablement plus intéressante, basée sur la cryptographie asymétrique.

Les techniques de cryptographie asymétrique, comme l’algorithme RSA, est ainsi nommé par le fait qu’un message n’est pas encrypté et décrypté par la même clef. Qui désire qu’on communique avec lui par cryptographie RSA génère deux clefs: une clef privée, qu’il garde pour lui, et une clef publique, qu’il distribue à ses amis. Ces derniers encryptent le message à l’aide de cette clef publique, qui ne peut être décrypté qu’à l’aide de la clef privée (le problème de décryption sans la clef privée est NP-complet).

Un tel système cryptographique peut aisément servir pour la vérification d’un client. Considérons que hamelin dispose d’un compte sur l’hôte lointain.com. hamelin a déjà pris la peine d’inscrire sa clef publique dans un fichier spécial sur son compte. L’algorithme de vérification peut donc procéder de la manière suivante:

  1. hamelin contacte lointain.com.
  2. lointain.com encrypte une chaîne d’octets aléatoire avec la clef publique de hamelin (qu’il connaît déjà) et transmet cette chaîne encryptée, avec sa propre clef publique.
  3. hamelin décrypte la chaîne à l’aide de sa clef privée et la ré-encrypte avec la clef publique de lointain.com. Il renvoit le tout.
  4. lointain.com décrypte la chaîne qu’il reçoit de hamelin et la compare avec celle générée à l’étape 2. Si les chaînes correspondent, la vérification est complète.

L’avantage de ce système est que seules des informations publiques ou encryptées sont échangées sur le réseau pour procéder à la vérification. À l’opposé, la vérification par nom d’usager et mot de passe exige qu’une signature cryptographique unique du mot de passe soit envoyée du client au serveur (après la saisie du mot de passe), laquelle signature peut être capturée en cours d’échange par une tierce partie, qui peut ensuite de tenter de deviner le mot de passe.

La sécurité de l’authentification par clefs asymétriques repose donc sur celle de la clef privée des pairs, dont ils sont entièrement responsables. Bien qu’il soit possible de conserver cette clef dans un fichier non encrypté, le risque de vol de la clef est alors excessif. Par conséquent, la clef privée est typiquement encryptée à l’aide d’un algorithme d’encryption symétrique (comme DES ou AES), dont la clef est un mot de passe (passphrase) solide.

Génération des clefs pour OpenSSH

OpenSSH supporte évidemment l’authentification par clefs asymétriques. Un usager peut générer sa paire de clefs à l’aide de la commande ssh-keygen. Bien qu’il soit possible de générer des clefs pour l’algorithme DSA, il est recommandé d’utiliser l’algorithme RSA, choisi par défaut. Il importe aussi de spécifier la taille de la clef, en bits. Plus longue est la clef, plus difficile il est de la deviner par force brute, et plus long est le message que la clef peut encoder. Par contre, le temps des calculs nécessaires à l’encryption et à la décryption est croît avec la taille de la clef. En ligne, on se fait généralement indiquer qu’une clef de 1024 bits est suffisamment forte. Cependant, en 2013, ayant égard à la puissance de calcul de nos ordinateurs, la taille minimale qui devrait être envisagée est selon moi 2048 bits; j’utilise pour ma part des clefs de 4096 bits, sans pénalité de performance significative. On génère la paire de clefs par la commande:

ssh-keygen -b <taille>

<taille> est bien sûr remplacé par la taille de la clef désirée (en bits). La clef est générée (le calcul est long dans le cas d’une clef de 4096 bits ou plus), puis le mot de passe pour l’encryption de la clef privée est demandé. Il est possible de n’entrer aucun mot de passe: la clef privée n’est alors pas encryptée. J’insiste sur le fait qu’une clef privée destinée à l’authentification doit être encryptée. Autrement, la sécurité du compte sur les systèmes distants configurés pour la vérification par clefs asymétriques est compromise.

ssh-keygen génère la clef privée dans le fichier $HOME/.ssh/id_rsa, et la clef publique dans $HOME/.ssh/id_rsa.pub. Ces fichiers sont utilisés par défaut par la commande ssh lorsque la vérification par clefs asymétrique est exigée. On peut néanmoins créer la clef dans des fichiers distincts avec l’option -f de ssh-keygen, et utiliser l’option -i de ssh pour spécifier la clef privée à employer pour l’authentification.

Les clefs sont des chaînes binaires conservées dans des fichiers texte, sous une représentation Base64. En outre de la clef, le fichier de clef publique indique l’algorithme auquel la clef est destinée, ainsi qu’un commentaire qui permet l’identification informelle de la clef par un humain. Par défaut, ce commentaire prend la forme <usager>@<hotelocal>; on peut le changer grâce à l’option -C de ssh-keygen, ce qui permet de gérer facilement une collection de multiples clefs.

Configuration de l’hôte distant

Une fois la paire de clefs générée, il faut configurer le compte sur l’hôte distant afin qu’il choisisse le protocole de vérification par clefs asymétriques lorsqu’il est contacté, et qu’il connaisse en ce sens la clef publique du client. Pour cela, il suffit d’ajouter la clef publique tout juste générée au fichier $HOME/.ssh/authorized_keys sur l’hôte distant. Depuis l’hôte local où la clef a été générée, on fait alors simplement:

scp $HOME/.ssh/id_rsa.pub usager@lointain.com:id_rsa.pub
ssh usager@lointain.com 'cat id_rsa.pub >> $HOME/.ssh/authorized_keys'

On peut ensuite supprimer le fichier id_rsa.pub téléchargé sur l’hôte distant. Évidemment, les deux commandes ci-haut seront nécessiteront l’authentification par usager et mot de passe. Pour éviter de devoir taper le mot de passe à deux reprises, on peut simplement utiliser l’unique

cat $HOME/.ssh/id_rsa.pub | ssh usager@lointain.com 'cat >> $HOME/.ssh/authorized_keys'

À la prochaine connexion à l’hôte distant via ssh, au lieu d’exiger le mot de passe du compte distant, ssh exigera le mot de passe de la clef privée. Les distributions GNU/Linux modernes et Mac OS X comportent par ailleurs un agent de cache des clefs cryptographiques utilisées. Lorsque ssh désire décrypter une clef, la commande contacte cet agent. La première fois, l’agent demande le mot de passe, mais stocke ensuite en mémoire la clef décryptée. Ainsi, lorsque ssh exige à nouveau cette clef, l’agent la lui transmet sans entrée du mot de passe. C’est un peu risqué, mais tellement confortable. D’ailleurs, pour les fans des bonnets en papier d’Al, les agents peuvent typiquement être configurés pour oublier la clef décryptée au bout d’un moment.

Usage des clefs OpenSSH pour applications cryptographiques en Python

La cryptographie RSA gagne à être mise en oeuvre pour protéger les données devant être transmises entre pairs d’une application, ou stockées pour usage privilégié. Le nombre de bits pouvant être encryptés par l’algorithme RSA correspond à la longueur de la clef, moins 1. Les stratégies cryptographiques typiques utilisent donc un algorithme symétrique comme DES, AES ou ARC4 pour protéger un contenu de taille arbitraire et encryptent la clef de cet algorithme symétrique à l’aide de RSA.

Bien que les algorithmes d’encryption et décryption RSA soient relativement simples à mettre en oeuvre, la gestion correcte et sûre de la clef privée est un peu plus compliquée. Il est donc intéressant d’utiliser des librairies toutes faites pour gérer les conversions entre texte clair et texte cryptique. En langage Python, on peut utiliser pour cela la librairie PyCrypto, qui offre d’ailleurs toute une panoplie d’outils cryptographiques. Sur Debian/Ubuntu, on installe simplement le paquet python-crypto. Si on a setuptools (e.g. Mac OS X), on peut alternativement exécuter

sudo easy_install PyCrypto

Il est possible d’utiliser les outils de PyCrypto pour générer des paires de clefs. Cependant, si cela est intéressant pour l’application, il est aussi possible d’utiliser les clefs RSA générées via ssh-keygen. L’exemple suivant encrypte la chaîne text avec la clef publique:

import os.path
from Crypto.PublicKey import RSA
from getpass import getpass

text = "Texte clair!"
with open(os.path.expanduser("~/.ssh/id_rsa.pub")) as key_file:
  pub_key = RSA.importKey(key_file.read())
crypt = pub_key.encrypt(text)

La suite vérifie la décryption à l’aide de la clef privée:

with open(os.path.expanduser("~/.ssh/id_rsa")) as key_file:
  priv_key = RSA.importKey(
      key_file.read(),
      getpass("Mot de passe:")
      )
dec = priv_key.decrypt(text)

Cet exemple de décryption ne fonctionnera pas sur certains systèmes d’exploitation, dont Mac OS X. Sur ces systèmes, ssh-keygen est configuré pour encrypter la clef privée à l’aide de l’algorithme AES-128, alors que PyCrypto ne comprend les clefs SSH que si elles sont encryptées par DES ou 3DES. Pour convertir l’encryption de la clef privée, on doit faire appel à openssl, un couteau suisse cryptographique disponible tant sur Mac OS X que sur GNU/Linux. La solution:

openssl rsa -in $HOME/.ssh/id_rsa -out $HOME/.ssh/id_rsa_des -des3

On remplace alors "~/.ssh/id_rsa" dans l’exemple ci-haut par "~/.ssh/id_rsa_des".

Commentaires