🔐 Standard IETF & Ingénierie Cryptographique ⚡ Code Source : Python, JS & Go

Comprendre le fonctionnement du TOTP : Implémenter la RFC 6238 en Python, JavaScript & Go

Décryptage complet des fondements mathématiques et étapes d'implémentation de la RFC 6238 : décodage Base32, calcul du pas temporel, signature HMAC-SHA1 et troncature dynamique avec vérification sur vecteurs de test.

📌 Résumé Théorique & Principes Clés

Le protocole TOTP (Time-Based One-Time Password) spécifié par l'IETF dans la RFC 6238 est une extension du standard HOTP (RFC 4226). Au lieu d'incrémenter un compteur événementiel, TOTP dérive la valeur du compteur à partir de l'heure Unix courante divisée par une période fixe (généralement 30 secondes). Aucun échange réseau n'est nécessaire entre le client et le serveur : la synchronisation temporelle globale suffit à produire des codes identiques.

1

01. La formule mathématique TOTP

// 1. Pas de temps (Time Step) :
T = ⌊(Current Unix Time - T0) / X⌋ (avec T0 = 0 et X = 30)
// 2. Code TOTP dynamique :
TOTP = Truncate(HMAC-SHA-1(K, T)) mod 10^Digits
  • K : Clé secrète partagée, encodée en Base32 selon la RFC 4648.
  • T : Entier non signé de 64 bits (8 octets, Big-Endian) représentant le nombre de tranches de 30 secondes depuis l'époque Unix.
  • Truncate : Algorithme de troncature dynamique qui extrait un entier de 31 bits à partir du digest HMAC de 20 octets.
2

02. Les 4 étapes de génération du code

Étape 1 - Décodage Base32 : Nettoyage de la clé (suppression des espaces et tirets) puis décodage de la chaîne Base32 en tableau d'octets bruts.

Étape 2 - Encodage du compteur temporel : Division du timestamp Unix en secondes par 30, conversion en entier 64 bits puis empaquetage dans un tampon de 8 octets au format Big-Endian.

Étape 3 - Signature HMAC-SHA1 : Calcul du hachage HMAC avec la clé secrète décodée sur le tampon temporel de 8 octets pour produire 20 octets de sortie.

Étape 4 - Troncature dynamique et modulo : Lecture de l'offset via les 4 derniers bits, extraction de 4 octets, masquage du bit de signe (0x7FFFFFFF), modulo 1 000 000 et formatage avec 6 chiffres (remplissage par des zéros à gauche).

3

03. Implémentation en Python pur (Sans dépendance tierce)

Voici le code complet en Python 3 utilisant exclusivement les modules de la bibliothèque standard (hmac, hashlib, time, struct, base64) :

import hmac
import hashlib
import time
import struct
import base64

def generate_totp(secret_base32: str, interval: int = 30, digits: int = 6) -> str:

1. Nettoyage et complétion du padding Base32

cleaned_secret = secret_base32.replace(” ”, "").upper() missing_padding = len(cleaned_secret) % 8 if missing_padding: cleaned_secret += ’=’ * (8 - missing_padding) key = base64.b32decode(cleaned_secret, casefold=True)

2. Calcul du pas temporel (8 octets, Big-Endian)

time_step = int(time.time()) // interval time_bytes = struct.pack(“>Q”, time_step)

3. Calcul HMAC-SHA1

hmac_digest = hmac.new(key, time_bytes, hashlib.sha1).digest()

4. Troncature dynamique (RFC 4226 section 5.3)

offset = hmac_digest[-1] & 0x0F code_int = struct.unpack(“>I”, hmac_digest[offset:offset + 4])[0] & 0x7FFFFFFF

5. Modulo et formatage

code = code_int % (10 ** digits) return str(code).zfill(digits)

if name == “main”: secret = “JBSWY3DPEHPK3PXP” print(“Code TOTP généré :”, generate_totp(secret))

4

04. Implémentation JavaScript Web Crypto (Navigateur moderne / Node.js)

Cette implémentation exploite l'API standardisée window.crypto.subtle pour exécuter les opérations cryptographiques à vitesse native dans le navigateur sans fuite de mémoire :

function base32ToUint8Array(base32) {
const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
const clean = base32.replace(/\s/g, '').toUpperCase();
let bits = 0, value = 0, index = 0;
const output = new Uint8Array(Math.floor((clean.length * 5) / 8));
for (let i = 0; i < clean.length; i++) {
const val = alphabet.indexOf(clean[i]);
if (val === -1) continue;
value = (value << 5) | val;
bits += 5;
if (bits >= 8) {
output[index++] = (value >>> (bits - 8)) & 255;
bits -= 8;
}
}
return output;
}

async function getTOTPCode(secretBase32, interval = 30) { const keyBytes = base32ToUint8Array(secretBase32); const cryptoKey = await crypto.subtle.importKey( ‘raw’, keyBytes, { name: ‘HMAC’, hash: ‘SHA-1’ }, false, [‘sign’] );

const timeStep = Math.floor(Date.now() / 1000 / interval); const timeBuffer = new ArrayBuffer(8); new DataView(timeBuffer).setBigUint64(0, BigInt(timeStep), false);

const signature = await crypto.subtle.sign(‘HMAC’, cryptoKey, timeBuffer); const hash = new Uint8Array(signature);

const offset = hash[hash.length - 1] & 0x0F; const dv = new DataView(hash.buffer, offset, 4); const codeInt = (dv.getUint32(0, false) & 0x7FFFFFFF) % 1000000;

return codeInt.toString().padStart(6, ‘0’); }

5

05. Implémentation native en Go (Golang)

package main

import ( “crypto/hmac” “crypto/sha1” “encoding/base32” “encoding/binary” “fmt” “strings” “time” )

func GenerateTOTP(secret string, interval int64, digits int) (string, error) { cleanSecret := strings.ToUpper(strings.ReplaceAll(secret, ” ”, "")) key, err := base32.StdEncoding.WithPadding(base32.NoPadding).DecodeString(cleanSecret) if err != nil { return "", err }

counter := uint64(time.Now().Unix() / interval) buf := make([]byte, 8) binary.BigEndian.PutUint64(buf, counter)

mac := hmac.New(sha1.New, key) mac.Write(buf) hash := mac.Sum(nil)

offset := hash[len(hash)-1] & 0x0f binaryCode := binary.BigEndian.Uint32(hash[offset:offset+4]) & 0x7fffffff

modulo := uint32(1) for i := 0; i < digits; i++ { modulo *= 10 } code := binaryCode % modulo

format := fmt.Sprintf(“%%0%dd”, digits) return fmt.Sprintf(format, code), nil }

6

06. Vecteurs de test officiels de la RFC 6238

L'annexe B de la spécification RFC 6238 fournit des vecteurs de test officiels basés sur la chaîne ASCII 12345678901234567890 (20 octets, soit la clé Base32 GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ) :

Temps Unix (Secondes) Date et Heure UTC Algorithme Code TOTP officiel
59 1970-01-01 00:00:59 SHA1 287082
1111111109 2005-03-18 01:58:29 SHA1 081804
1234567890 2009-02-13 23:31:30 SHA1 005924
2000000000 2033-05-18 03:33:20 SHA1 279037
7

07. Bonnes pratiques de sécurité pour la mise en production

  • Fenêtre de dérive d'horloge (Tolerance Window) : Validez toujours les pas temporels [T-1, T, T+1] pour compenser les légers retards d'horloge entre le smartphone et le serveur NTP.
  • Protection anti-rejeu (Anti-Replay) : Marquez chaque code vérifié avec succès comme consommé dans un cache (ex. Redis avec un TTL de 90 secondes) pour interdire qu'un code intercepté ne soit rejoué.
  • Stockage sécurisé des secrets : Chiffrez les secrets Base32 au repos en base de données avec AES-GCM-256 ou enveloppez-les via un module HSM/KMS.
  • Limitation de débit (Rate Limiting) : Bloquez temporairement l'adresse IP et le compte après 5 tentatives échouées consécutives pour neutraliser les attaques par force brute.
8

08. Foire aux questions (FAQ)

Pourquoi la plupart des services utilisent-ils 30 secondes d'intervalle ?

30 secondes est le compromis idéal recommandé par l'IETF entre la sécurité (durée de validité courte du jeton) et l'ergonomie utilisateur (temps suffisant pour lire le code et le recopier au clavier).

Peut-on utiliser 8 chiffres au lieu de 6 chiffres en TOTP ?

Oui. Il suffit de modifier le modulo en 10^8 (100 000 000). Des services de haute sécurité ou bancaires utilisent parfois des codes à 8 chiffres pour réduire encore la probabilité de prédiction statistique.

Testez vos implémentations en direct

Comparez immédiatement les codes générés par votre code Python, JS ou Go avec notre générateur TOTP client officiel certifié RFC 6238.

Lancer le Générateur Gratuit →