La sécurité est le seul domaine où "installez simplement un package" est véritablement dangereux. Chaque année, des packages npm compromis volent des identifiants. Des dépendances obsolètes exposent des vulnérabilités connues. Des développeurs copient le hachage SHA-256 de mots de passe depuis Stack Overflow, sans savoir que SHA-256 est catastrophiquement inadapté pour les mots de passe. La sécurité exige des valeurs par défaut correctes, et des valeurs par défaut correctes exigent que le langage lui-même les fournisse.
Lors des Sessions 183 et 184, nous avons construit les fonctions de sécurité de FLIN : hachage de mots de passe avec Argon2, création et vérification JWT, signatures HMAC, chiffrement symétrique et génération aléatoire cryptographiquement sécurisée. Tout est intégré. Tout utilise les algorithmes corrects par défaut. Tout rend le chemin sécurisé le chemin facile.
Hachage de mots de passe : Argon2 par défaut
La fonction de sécurité la plus importante dans toute application web est le hachage de mots de passe. Si vous vous trompez, une fuite de base de données expose le mot de passe de chaque utilisateur en clair. Si vous le faites bien, une fuite ne révèle rien d'utile.
FLIN utilise Argon2id -- le vainqueur actuel de la Password Hashing Competition et l'algorithme recommandé par l'OWASP -- comme seule fonction intégrée de hachage de mots de passe :
flin// Hacher un mot de passe (un seul appel de fonction)
hashed = hash_password("user_password_here")
// "$argon2id$v=19$m=19456,t=2,p=1$random_salt$hash_output"
// Vérifier un mot de passe contre un hash
is_valid = verify_password("user_password_here", hashed)
// trueDeux fonctions. C'est toute l'API de mots de passe. Pas de sélection d'algorithme. Pas de réglage de paramètres. Pas de génération de sel. FLIN gère tout avec des valeurs par défaut sécurisées.
La sortie du hash est une seule chaîne au format PHC standard, contenant l'identifiant de l'algorithme, la version, les paramètres, le sel et le hash. Vous stockez cette chaîne dans votre base de données. Lors de la vérification, vous passez le mot de passe en clair et le hash stocké. La fonction extrait les paramètres et le sel de la chaîne de hash et recalcule. S'ils correspondent, elle retourne true.
Pourquoi pas de choix d'algorithme
La plupart des bibliothèques de hachage de mots de passe vous laissent choisir entre bcrypt, scrypt, PBKDF2 et Argon2. C'est un piège. Un développeur qui ne comprend pas les différences pourrait choisir PBKDF2 (l'option la plus faible) parce qu'il a le nom le plus simple, ou bcrypt (adéquat mais obsolète) parce qu'un article de blog de 2015 le recommandait.
FLIN supprime le choix. Vous obtenez Argon2id. C'est le meilleur algorithme disponible. Les paramètres sont calibrés pour un équilibre entre sécurité et performance adapté aux applications web :
- Mémoire : 19 456 Kio (19 Mo)
- Itérations : 2
- Parallélisme : 1
Ces paramètres produisent un hash en environ 100-200 millisecondes sur un serveur moderne. Assez rapide pour que les utilisateurs ne remarquent pas de délai lors de la connexion. Assez lent pour qu'un attaquant forçant des hashs volés ait besoin d'années par mot de passe.
JWT : création et vérification de tokens
Les JSON Web Tokens sont le standard pour l'authentification API dans les applications web modernes. FLIN inclut un support JWT intégré :
flin// Créer un JWT
token = jwt_sign({
user_id: 42,
role: "admin",
email: "[email protected]"
}, env("JWT_SECRET"), {
expires_in: 30.days
})
// "eyJhbGciOiJIUzI1NiIs..."
// Vérifier et décoder un JWT
claims = jwt_verify(token, env("JWT_SECRET"))
{if claims != none}
print("User: {claims['user_id']}, Role: {claims['role']}")
{else}
print("Invalid or expired token")
{/if}jwt_sign prend trois arguments : un map de claims, une clé secrète et des options. Le map de claims peut contenir n'importe quelles données que vous voulez intégrer dans le token. La clé secrète est utilisée pour la signature HMAC-SHA256 (l'algorithme JWT le plus courant pour les applications mono-serveur). Le map d'options supporte expires_in (une durée) et issuer (une chaîne).
jwt_verify retourne le map de claims décodé si le token est valide et non expiré, ou none si la vérification échoue pour quelque raison que ce soit (signature invalide, expiré, malformé). C'est une autre instance de la philosophie "retourner none au lieu de lancer" de FLIN -- le développeur vérifie none au lieu d'envelopper chaque vérification dans un try-catch.
HMAC, chiffrement et génération aléatoire sécurisée
flin// Signer un message
signature = hmac_sha256("message to sign", env("WEBHOOK_SECRET"))
// Comparaison à temps constant
is_valid = secure_compare(expected, received_signature)
// Chiffrement AES-256-GCM
encrypted = encrypt("sensitive data here", key)
decrypted = decrypt(encrypted, key)
// Génération aléatoire sécurisée
token = random_bytes(32)
api_key = random_hex(32)
id = uuid()secure_compare est une fonction de comparaison de chaînes à temps constant qui empêche les attaques par timing. La comparaison de chaînes régulière (==) court-circuite sur le premier caractère différent, ce qui divulgue des informations sur la quantité de signature qui correspondait. secure_compare compare toujours chaque caractère, prenant le même temps quel que soit le nombre de caractères qui correspondent.
Exemple concret : flux d'authentification complet
flinentity User {
email: text where is_email
password_hash: text
name: text
created_at: time = now
}
// Inscription
fn register(email: text, password: text, name: text) {
existing = User.find_by(email: email)
{if existing != none}
return { error: "Email already registered" }
{/if}
user = User.create(
email: email,
password_hash: hash_password(password),
name: name
)
token = jwt_sign({
user_id: user.id,
email: user.email,
role: "user"
}, env("JWT_SECRET"), {
expires_in: 30.days
})
return { user: user, token: token }
}
// Connexion
fn login(email: text, password: text) {
user = User.find_by(email: email)
{if user == none}
return { error: "Invalid credentials" }
{/if}
{if not verify_password(password, user.password_hash)}
return { error: "Invalid credentials" }
{/if}
token = jwt_sign({
user_id: user.id,
email: user.email
}, env("JWT_SECRET"), {
expires_in: 30.days
})
return { user: user, token: token }
}Inscription, connexion et authentification par token en 50 lignes. Hachage de mots de passe Argon2. Tokens JWT avec expiration de 30 jours. Aucun import de bibliothèque de sécurité. Aucune configuration de middleware. Aucun framework d'authentification. Le langage fournit les primitives. Le développeur les compose.
Dix-huit fonctions, zéro vulnérabilité
L'API de sécurité complète :
- Mots de passe :
hash_password,verify_password - JWT :
jwt_sign,jwt_verify - HMAC :
hmac_sha256,hmac_sha512 - Chiffrement :
encrypt,decrypt - Hachage :
sha256,sha512,md5 - Aléatoire :
random_bytes,random_hex,uuid - Comparaison :
secure_compare - Encodage :
base64_encode,base64_decode - Environnement :
env
Dix-huit fonctions qui remplacent bcrypt, jsonwebtoken, crypto-js, uuid, dotenv et une demi-douzaine d'autres packages liés à la sécurité. Toutes utilisant les algorithmes corrects par défaut. Toutes rendant le chemin sécurisé le chemin facile.
Ceci est la partie 76 de la série "How We Built FLIN", documentant comment un CEO à Abidjan et un CTO IA ont construit de la sécurité de niveau production dans un langage de programmation.
Navigation de la série : - [75] HTTP Client Built Into the Language - [76] Security Functions: Crypto, JWT, Argon2 (vous êtes ici) - [77] Introspection and Reflection at Runtime - [78] Reduce, Map, Filter: Higher-Order Functions