La Cryptographie en 55' chrono

Petit guide du voyageur en terre du chiffrement

@m4d_z
alwaysdata
Ne comptez pas sur vos utilisateur·trice·s pour bien se protéger

Toute donnée sensible doit
être transférée et stockée
de façon chiffrée

Solution ? Cryptographie!

La cryptographie, ça concerne :

  • le hashage
  • le chiffrement
  • l’échange de clefs
  • la signature

Crypto, Histoire, et failles majeures

La crypto est aussi vieille que la guerre

La cryptanalyse est aussi
vieille que le chiffrement !

Faille #1: des clefs trop simples

Shift Cipher Wheel

Solution ?

  • Clefs de grande longueur
  • Clefs complexes à dupliquer

Faille #2: Répétitions

Chiffre de Vigenère

Plain text:  ATTACK AT DAWN
Cipher key:  LEMONL EM ONLE
Cipher text: LXFOPV EF RNHR

Solution ?

  • Éviter tout dénominateur commun
  • Utilisez des clefs uniques

Faille #3: Attaque par force brute

xkcd://538
Enigma, virtuellement incassable

Aucun modèle n’est assez robuste
pour contrer une attaque continue

Hashage, salage, mijotage

Le hashage est une technique
d’obfuscation des données

Oups !

Simple Rainbow Table - Wikimedia

Salage

  • Entropique
  • Prévient les répétitions
  • Doit être unique et aléatoire
from base64 import b64encode
import secrets
import argon2
import hmac
from haslib import sha256

salt = secrets.token_bytes(64)
hash = argon2.low_level.hash_secret_raw('b'*password, salt,
    time_cost=1, memory_cost=8, parallelism=1, hash_len=8,
    type=argon2.low_level.Type.D)

record = "$argon2$t=1,m=8,p=1$%s%s" % (b64encode(salt), b64encode(hash))
ph = argon2.PasswordHasher()
record = ph.hash(password)
digest = hmac.new(server_key, 'b'*record, digestmod=sha256).digest()

Un mot de passe haché avec un sel unique
et un temps d’exécution contrôlé
diminue le risque de pénétration par force brute

Chiffrement par clef

Chiffrement ?

  • Maths
  • Clef
  • Substitutions
  • Hasard
Observons plutôt ces crustacés en période de reproduction

Chiffrement par bloc

  • DES (Data Encryption Standard)
  • AES (Advanced Encryption Standard)
  • IDEA
  • BlowFish

Modes d’opérations par bloc

  • ECB (Electronic Code Book)
  • CBC (Cipher Block Chaining)
  • AEAD (Authenticated Encryption with Associated Data)
    (OCB, EAX, …)

Nos machines ne sont pas
vraiment aléatoires

Padding, aléatoire, Vecteur d’Initialisation

  • Imprévisible et non-déterministe
  • Fonctions CSPRNG
    éviter l’accès direct à /dev/urandom
  • IV (vecteur d’initialisation)
    blocs d’instanciation d’une fonction de chiffrement dans un état unique

Chiffrement par flux

  • RC4
  • ChaCha20 ?
  • Panama ?

Gestion des clefs

Clef symétrique

  • Clef unique
  • Rapide
  • S’adapte au volume de données

Une clef symétrique
doit être échangée,
elle peut donc fuiter

Diffie-Hellman Key Exchange

Secret partagé (de Shamir)

  • Sécurité distribuée
  • Seuil de partage
  • Chiffrement par consensus

Clefs asymétriques

  • Clef privée
  • Une (ou plusieurs) clef(s) publique(s)
  • Clef publique → chiffrement
  • Clef privée → déchiffrement / signature
  • Très sécurisé, et très lent

RSA ou Courbes elliptiques?

RSA

  • Nombres premiers
  • Largement disponible
  • Chiffrement rapide
  • Facile à comprendre
  • Très lent sur le déchiffrement

ECC

  • Logarithme discret
  • Sorties légères
  • Très rapide
  • Complexe à implémenter
  • Lenteur exponentielle à la robustesse

→ Utilisez ECC si disponible

Encapsulation de clef

import secrets
from struct import pack
from Crypto.Cipher import Blowfish
from Crypto.PublicKey import RSA

iv = secrets.randbits(Blowfish.block_size)
plen = Blowfish.block_size - divmod(len(msg), Blowfish.block_size)[1]
padding = [plen]*plen

token = secrets.token_bytes(32)
cipher = Blowfish.new(token, Blowfish.MODE_CBC, iv)

key = RSA.importKey(recipient_key)
encToken = key.public_key.encrypt(token, 32)

wrap = pack(b'---', encToken, iv + cipher.encrypt(msg + pack('b'*plen, *padding)))

Signature

  • Chiffrement asymétrique inversé
  • Dédié à l’authentification
Certificats x.509

Outils et bibliothèques

Protection

  • Réseau SSL / TLS, chaîne de confiance
  • Mots de passe → Fonctions de hashage
  • Donnée → Chiffrement par encaspulation asymétrique, HSM

Langages bas-niveau

  • Modules OS
  • Bibliothèques système (OpenSSL)
  • Matériel (AES-NI, Co-processeur)

Desktop/Server

  • Python: Cryptography / PyCrypto
  • Ruby: RbNaCl
  • Node.js: Crypto built-in module
  • PHP: Mcrypt
  • Java: JCE Framework
  • etc.

Bibliothèques multi-plateformes

  • Bas niveau pour garantir les performances
  • Agnostique
  • Portable
  • Disponible via des bindings

Chiffrement réponse côté serveur

import nacl
from nacl.public import PrivateKey, Box
import User

@app.route("/secret/<username>/<nonce>")
def get_secret(username, nonce):
    skfile = open("sk_server.key", "r")
    skserver = PrivateKey(skfile, nacl.encoding.RawEncoder)

    user = User.query.filter_by(username=username).first()

    box = Box(skserver, user.pk)

    message = b"Never Gonna Give You Up!"
    encrypted = box.encrypt(message, nonce)

    return encrypted

Mobile/Web

  • WebCrypto
  • Bibliothèques navigateur
  • iOS: CryptoKit
  • Libsodium !

Déchiffrement réponse côté client

const _sodium = require('libsodium-wrapper')

(async () => {
  await _sodium.ready
  const sodium = _sodium

  const nonce = sodium.randombytes_buf(sodium.crypto_secretbox_NONCEBYTES)

  await res = fetch(`/secret/${user.name}/${sodium.to_string(nonce)}`)
  const encrypted = res.text()

  const decrypted = sodium.crypto_box_open_easy(encrypted, nonce, SERVER_PK, user.sk)
  const message = sodium.to_string(decrypted)
  // Never Gonna Give You Up!
})

API d’Authentification

API utilisant la cryptographie à clé publique,
permettant une authentification sans mot de passe

WebAuthn: register

@app.route('/webauthn_begin_activate', methods=['POST'])
def webauthn_begin_activate():
    # ...
    rp_name = 'localhost'
    challenge = util.generate_challenge(32)

    make_credential_options = webauthn.WebAuthnMakeCredentialOptions(
        challenge, rp_name, RP_ID, ukey, username, display_name,
        'https://example.com')

    return jsonify(make_credential_options.registration_dict)

WebAuthn: challenge

@app.route('/webauthn_begin_assertion', methods=['POST'])
def webauthn_begin_assertion():
    webauthn_user = webauthn.WebAuthnUser(
        user.ukey, user.username, user.display_name, user.icon_url,
        user.credential_id, user.pub_key, user.sign_count, user.rp_id)

    webauthn_assertion_options = webauthn.WebAuthnAssertionOptions(
        webauthn_user, challenge)

    return jsonify(webauthn_assertion_options.assertion_dict)

WebAuthn: login

@app.route('/verify_assertion', methods=['POST'])
def verify_assertion():
    # ...
    webauthn_user = webauthn.WebAuthnUser(
        user.ukey, user.username, user.display_name, user.icon_url,
        user.credential_id, user.pub_key, user.sign_count, user.rp_id)

    webauthn_assertion_response = webauthn.WebAuthnAssertionResponse(
        webauthn_user, assertion_response, challenge,
        ORIGIN, uv_required=False)  # User Verification

    try:
        sign_count = webauthn_assertion_response.verify()
    except Exception as e:
        return jsonify({'fail': 'Assertion failed. Error: {}'.format(e)})

Pensez chiffré 😎

Ne jouez pas aux apprenti·e…s sorciers·ères

N’oubliez jamais que la sécurité

  1. a une relation inverse à la simplicité d’utilisation
  2. a un coût
m4dz's avatar
m4dz

Paranoïd Web Dino · Tech Evangelist

alwaysdata logo
https://www.alwaysdata.com

Questions ?

Illustrations

m4dz, CC BY-SA 4.0

Interleaf images

Courtesy of Unsplash and Pexels contributors

Icons

  • Layout icons are from Entypo+
  • Content icons are from FontAwesome

Fonts

  • Cover Title: Sinzano
  • Titles: Argentoratum
  • Body: Mohave
  • Code: Fira Code

Tools

Powered by Reveal.js

Source code available at
https://git.madslab.net/talks