un OTP avec python

temps de lecture ~17 min
  1. 1. La théorie
  2. 2. Synchronisation
  3. 3. La pratique
    1. 3.1. Voir le code associé à un token
    2. 3.2. Vérifier le code associé à un token
    3. 3.3. Enrollement avec QR code
  4. 4. Conclusion
  5. 5. Bibliographie
  6. 6. à voir aussi

La sécurité d’accès aux sites web est souvent basée sur un couple identifiant et mot de passe qui peut parfois s’avérer trop léger face aux données à protéger sur le compte. Sachant que la majorité des utilisateurs n’utilise pas de gestionnaire de mot de passe[1] permettant de conserver des versions complexes, la tendance est donc à l’utilisation systématique d’un mot de passe simple voir trivial (ie What is your password sur youtube). Proposer un mot de passe unique en complément peut alors s’avérer une bonne solution.

L’objet de ce billet est de présenter l’OTP et une implémentation simple avec quelques lignes de python.

En ajoutant un troisième facteur à usage unique à une authentification on complexifie d’autant l’accès par une personne étrangère et les attaques par force brute, souvent la solidité des mots de passe peut laisser à désirer et laisser ouverte la voie de l’usurpation par déduction.

Parmi les moyens de demander à l’utilisateur ce troisième facteur à usage unique on pense au SMS qui est souvent utilisé. Ce qui m’intéresse ici ce sont les mécanismes à base de jetons (token), qu’ils soient physiques ou embarqué dans un logiciel comme Google Authenticator par exemple.

La théorie

Deux moyens principaux sont à notre disposition dans le monde des token :

  • hotp : le code change à chaque sollicitation (hmac based)
  • totp : le code change à intervalle régulier (time based)

La première catégorie (hotp) était très utilisée avec les jetons physiques, on disposait alors d’une carte ou d’un petit composant qui fournissait à la demande le code nécessaire à la connexion ou l’authentification. Ce système était principalement utilisé pour les connexions à des VPN d’entreprise.

La seconde catégorie (totp) est beaucoup plus fréquente de nos jours, notamment avec les applications que peuvent être embarquées sur nos smartphones préférés. Le code délivré dans ce cas est limité dans le temps, il change généralement toutes les 30 ou 60 secondes. On trouve désormais ce type de système sur quelques solutions de carte à puce en remplacement du code statique au dos de la carte [1], [2], [3].

Que le token soit basé sur l’une ou l’autre des solutions, le fondement est le même : un système HMAC combinant une clé privée, un message à authentifier et un algorithme de hachage. On note généralement HMAC(K,m) cette fonction.

  • La clé privée utilisé est propre à chaque token. Si celui-ci est physique elle devra être fournie par le constructeur, sinon, on la définie à la construction du token logique. La clé est privée et doit par conséquent être conservée dans un espace protégé sur le système d’authentification et sur le token. On évitera donc de stocker dans la même base de données l’identifiant, son mot de passe haché et la clé privée de son token pour éviter les impacts si la base venait à être utilisée à des fins détournées.

  • Le message qui sera authentifié est composé dans le cas du hotp par un numéro d’ordre qui augmente à chaque pression sur le bouton du token et dans le cas du totp d’un numéro d’ordre dépendant de l’heure actuelle et de la période de validité du code.

  • L’algorithme de hachage est standard, on préférera aujourd’hui des choses comme SHA-256 ou SHA-512, mais on trouve encore des implémentations avec MD5 ou SHA1. L’algorithme est passé deux fois sur les données dans le cadre de HMAC, la formule simplifiée étant : f(k, m) = H(k + H(k) + m)

avec H=fonction de hachage, k=clé privée et m=message, l’addition représente une concaténation de chaîne de caractères.

En conservant la clé sur le serveur d’authentification, on recalcule le code attendu en provenance de l’utilisateur qui souhaite s’authentifier (6 ou 8 chiffres généralement) et on le compare avec celui qui nous est transmis.

Synchronisation

La synchronisation est importante pour utiliser l’une ou l’autre des 2 méthodes :

  • hotp : l’utilisateur peut avoir appuyé sur le bouton de son token alors qu’il ne souhaitait pas s’authentifier. Il crée donc un décalage avec le compteur retenu sur le serveur (en plus de la clé privée). Afin de récupérer le décalage, on effectue une synchronisation via une recherche en profondeur sur les clés précédentes et suivantes jusqu’à trouver le bon décalage. Fort heureusement, le calcul est très rapide, on peut donc rechercher sur plusieurs milliers d’itérations sans impact majeur. Certains pourront construire un processus de resynchronisation en autonomie pour l’utilisateur jusqu’à un niveau de profondeur donné, pouvant nécessiter sur échec de passer sur un second processus avec un niveau de profondeur plus important mais un contrôle d’identité plus poussé.

  • totp : puisque le compteur est basé sur le temps, il est important que le serveur d’authentification et surtout le token soient tous les 2 à l’heure. Autant il est simple (et indispensable) d’avoir ses serveurs synchronisés sur une horloge commune et idéalement partagée sur internet ou un GNSS [1], autant certains tokens physiques ne peuvent pas être synchronisés. On procédera donc de la même façon qu’en hotp à une recherche en profondeur afin de conserver la valeur du décalage par token. Même avec des équipements autonomes synchronisés par un petit quartz, les niveaux de précision sont suffisants pour ne pas dépasser quelques décalages (de 30 ou 60 secondes).

On veillera donc à construire une solution côté serveur qui conserve les clés privées de chiffrement dans un espace spécifique et protégé, ainsi que le décalage pour chaque token afin de simplifier les procédures de synchronisation et limiter la profondeur de recherche lors des authentifications.

La pratique

Mettons tout cela en lumière avec un peu de code python très simple.

Après quelques recherches, j’ai trouvé une librairie python qui couvre mes besoins : python-oath de Benjamin Dauvergne. Vous trouverez aussi la librairie pyotp qui fonctionne bien mais ne permet que de construire des secrets à base d’une clé de 16 caractères, ce qui n’était pas suffisant pour mon besoin avec des tokens physiques spécifiques.

Afin de simplifier les choses, vous pouvez utiliser une application token sur smartphone (ou poste de travail) qui sait ajouter des tokens sur la base d’une URI au format otpauth, par exemple Google Authenticator ou Microsoft Authenticator ou encore Authy [2]

Voir le code associé à un token

test01.py
1
2
3
4
5
6
import oath
t = oath.totp('b3615e2a4117655093b9',
format='dec6',
period=30)
print("totp token: {}".format(t))
  • ligne 1 : import de la librairie python-oath
  • ligne 3 : récupération de la valeur du token sur 6 digits, calculée à partir de la clé privée passée en paramètre au format d’une chaine de caractères hexadécimaux
  • ligne 6 : on affiche la valeur en question

le token avec son QR code (sauf si votre mobile est trop petit) et la clé si vous lisez cet article sur votre smartphone et devez le saisir manuellement : WNQV4KSBC5SVBE5Z

Si vous avez un environnement python, vous pourrez valider le code avec la valeur sur votre token :

1
2
3
4
5
6
virtualenv env
source env/bin/activate
pip install oath
python test01.py
deactivate
  • ligne 2 : entrée dans l’environnement virtuel, à remplacer par env\Scripts\activate sur Windows

Vérifier le code associé à un token

Encore mieux et à utiliser de préférence dans votre code d’authentification, la fonction qui permet de valider le code fournit par l’utilisateur. Un des intérêt principal est que cette fonction gère le test dans une fenêtre de valeur dont la taille est paramétrable, par défaut à une unité ; elle valide donc des tokens avec une période de temps avant et après la date actuelle, avec un code valide pendant 30 secondes la fenêtre correspond donc à 90 secondes de battement, largement suffisant dans la majorité des cas pour un totp.

Voici une petite évolution du code précédent afin de valider le principe de fonctionnement :

test02.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import oath
import time
key = 'b3615e2a4117655093b9'
t = oath.totp(key,
format='dec6',
period=30)
print("current totp token: {}".format(t))
print("test the token t="+
"{}: {}".format(t,
oath.accept_totp(key, t,
format='dec6',
period=30)[0]))
print("test the token t="+
"{}: {}".format(int(t)+1,
oath.accept_totp(key, str(int(t)+1),
format='dec6',
period=30,
forward_drift=1)[0]))
print("wait for 30 secs")
time.sleep(30)
print("test the expired token t="+t+
" in the forward window of +1/-1:"
+" {}".format(oath.accept_totp(key, t,
format='dec6',
period=30,
forward_drift=1)[0]))

Un exemple de résultat pourrait être :

1
2
3
4
5
current totp token: 476368
test the token t=476368: True
test the token t=476369: False
wait for 30 secs
test the expired token t=476368 in the forward window of +1/-1: True

Enrollement avec QR code

Un des avantages des solutions de client embarquées sur les smartphones est leur capacité à ajouter simplement un nouveau token via un QR code. Celui-ci représente une URI avec un format spécifique et embarque les informations nécessaires au token, à minima sa clé privée, son type (hotp ou totp) et un identifiant du token permettant de le retrouver simplement sur l’application. Elle peut également emporter l’algorithme ou encore la durée de validité.

Afin de construire cette URI j’ai proposé la méthode GoogleAuthenticatorURI à l’auteur du package python-oath, elle vient en complément de celle déjà présente et qui permet de lire une URI afin de paramétrer le module. Si vous ne trouvez pas directement la méthode dans le package installé par pip, récupérez directement les sources sur le git de l’auteur.

Dans l’exemple ci-dessous on crée un token de type hotp (pour changer) afin de démonter le principe de fenêtre de dérive. L’URI affichée devra être convertie en QR code, il existe des modules simples que l’on peut ajouter à son navigateur préféré. A noter que les token de type hotp ne semblent pas supportés par l’application Microsoft Authenticator, alors que celle de Google les supporte.

test03.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from oath import GoogleAuthenticatorURI as gu
from oath import GoogleAuthenticator as go
from oath import accept_hotp
u = gu().generate('b3615e2a4117655093b9',
type='hotp',
issuer='ACME',
account='John DOE')
print("URI for QR code: "+u)
g = go(u)
print(g.generate())
print(g.generate())
print(g.generate())
print(g.generate())
print(g.generate())
print(accept_hotp('b3615e2a4117655093b9',
'069304',
counter=0,
drift=10))
  • ligne 5 : on construit l’URI pour le QR code
  • ligne 11 : on construit l’objet d’authentification via l’URI plutôt que via la fonction comme précédemment
  • lignes 12 à 16 : on génère les premiers codes du token
  • ligne 18 : on teste un code proposé (le quatrième) en le comparant au premier mais avec une fenêtre de latitude de 10 unités, on constatera que la librairie retourne le fait que le code a été trouvé et qu’il était en position 4, ceci sera utilisé pour les prochaines itération d’authentification par le serveur.

Le résultat est le suivant :

1
2
3
4
5
6
7
URI for QR code: otpauth://hotp/ACME:John%20DOE?secret=WNQV4KSBC5SVBE5Z&type=hotp&period=30&issuer=ACME
079752
743191
418341
069304
654951
(True, 4)

Si vous souhaitez tester avec votre smartphone, vous pouvez ajouter ajouter ce token (WNQV4KSBC5SVBE5Z) et suivre les différentes valeurs proposées, qui devraient en toute rigueur être celles affichées ci-dessus. Vous constaterez que c’est bien à chaque sollicitation sur le token qu’une nouvelle valeur est retournée, à contrario des modèles totp à validité bornée dans le temps.

Conclusion

Quelques lignes de python pour envisager un module d’authentification par token dans votre prochaine application flask, c’était l’objectif de ce billet. Je vous publierai peut-être une application exemple en flask/bootstrap pour démontrer son fonctionnement, on verra s’il y a de l’engouement… (alors RT, share, comment, … si ça te dit).

Bibliographie

Photo Brina Blum


  1. comme keypass password safe, une liste des solutions est maintenue sur Wikipedia (List of password managers) ↩︎

  2. attention, la plupart de ces solutions ne supportent que l’algorithme de hachage SHA1, il n’est donc pas utile de tenter de le modifier dans vos expérimentation ↩︎

Alexandre Chauvin Hameau

2018-11-08T16:38:24