Séance 10 : ECDSA#

Dans ce TP, nous allons explorer l’utilisation de l’algorithme de signature ECDSA. Les objectifs sont les suivants :

  1. Implémenter la signature ECDSA grâce à l’implémentation de l’arithmétique des courbes elliptiques réalisée à la séance précédente.

  2. Comprendre l’utilisation de la bibliothèque de cryptographie openssl, libre et multi-plateforme, ainsi que des modules de la bibliothèque python pycryptodome

  3. Faire interagir notre implémentation avec ces bibliothèques, et ainsi vérifier sa validité.

Exercice 1 : utilisation de openssl#

Dans cet exercice, nous allons manipuler la bibliothèque openssl afin de signer le contenu d’un fichier avec la signature ECDSA. Pour cela, notons :

  • {type_de_courbe} une chaîne de caractères représentant une courbe elliptique utilisée dans ECDSA (ici, nous utiliserons la courbe NIST P-256 nommée prime256v1 dans openssl) ;

  • {public_key} un préfixe de fichier pour la clé publique ;

  • {private_key} un préfixe de fichier pour la clé privée ;

  • {fichier} le nom d’un fichier à signer ;

  • {signature} le nom de fichier dans lequel stocker la signature (en binaire).

Le contenu du fichier sera haché avec la fonction de hachage SHA-256. Les commandes listées ci-dessous permettent alors respectivement de :

  • créer les paramètres de la courbe elliptique à utiliser, et les stocker dans le fichier {type_de_courbe}.pem ;

  • créer une paire de clés, et stocker la clé privée dans {private_key}.pem ;

  • dériver la clé publique, et la stocker dans {public_key}.pem ;

  • afficher la paire de clés et les paramètres de la courbe ;

  • signer le fichier ;

  • vérifier la signature obtenue.

openssl ecparam -name {type_de_courbe} -out {type_de_courbe}.pem
openssl ecparam -in {type_de_courbe}.pem -genkey -noout -out {private_key}.pem
openssl ec -in {private_key}.pem -pubout > {public_key}.pem
openssl ec -in {private_key}.pem -text -param_enc explicit -noout
openssl dgst -sha256 -sign {private_key}.pem -out {signature} {fichier}
openssl dgst -sha256 -verify {public_key}.pem -signature {signature} {fichier}

Question 1. Dans un terminal, effectuer ces opérations, en choisissant des noms de fichiers appropriés. Observer les résultats (création de fichiers, affichage des paramètres, etc.).

Exercice 2 : implémentation personnelle de ECDSA#

Dans le but d’implémenter une version d’ECDSA, nous avons de fixer certains paramètres du sytème.

D’abord, nous utiliserons la courbe elliptique NIST P-256, d’équation \(y^2 = x^3 + ax + b\) sur \(\mathbb{F}_p\), qui admet un groupe de points cyclique, d’ordre premier \(q\), et engendré par un point \(G\), avec les valeurs suivantes :

p  = 0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff
a  = 0xffffffff00000001000000000000000000000000fffffffffffffffffffffffc
b  = 0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b
Gx = 0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296
Gy = 0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5
G = (Gx, Gy)
q = 0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551

Ensuite, nous utiliserons la fonction de hachage SHA-256. Pour l’utiliser facilement avec pycryptodome, on pourra utilisera la fonction suivante :

from Crypto.Hash import SHA256

def HASH(text):
    aux = SHA256.new(bytes(text, "utf-8")).hexdigest()
    return int(aux, 16)

Cette fonction prend en entrée une chaîne de caractères text au format utf-8, et retourne l’entier associé au haché de la chaîne par la fonction SHA-256.

Enfin, on utilisera les fonctions d’arithémtique de courbe elliptique qu’on a développées à la séance précédente. Elles sont regroupées dans ce fichier.

Question 1. Implémenter une fonction de génération de clé ECDSA keygen(EC_group), qui prend en entrée une liste de \(5\) éléments EC_group = (p, a, b, G, q) correspondant aux paramètres du système, et qui retourne une paire de clés publique/privée.

# à compléter

Question 2. Implémenter une fonction de signature ECDSA nommée sign(EC_group, message, sk), qui prend en entrée les paramètres systèmes EC_group, le message (au format chaîne de caractères) message et la clé privée sk, et qui retourne la signature associée.

# à compléter

Question 3. Implémenter une fonction de vérification ECDSA verif(EC_group, message, signature, pk), qui prend en entrée les paramètres systèmes EC_group, le message (au format chaîne de caractères) message, la signature signature (un couple d’entiers) et la clé publique pk, et qui teste si la signature donnée est valide.

# à compléter

Question 4. Créer un message, des clés, signer et vérifier avec vos fonctions.

# à compléter

Le module pycryptodome permet aussi de signer et de vérifier des signatures ECDSA. Nous allons voir si le module peut vérifier nos signatures.

Attention ! La fonction verify de vérification de signature de pycryptodome utilise les conventions suivantes :

  • elle prend en entrée le haché du message (et non le message brut) ;

  • la signature doit être fournie sous un format binaire (et non un couple d’entiers), pour lequel sont concaténés les \(32\) octets de chacun des entiers ;

  • si la signature est n’est pas valide, verify retourne une erreur (qu’il faut traiter) ;

  • si la signature est valide, verify retourne False. Elle ne renvoie donc jamais True.

Pour résumer, voici une manière d’utiliser les fonctions du module pycryptodome :

from Crypto.Signature import DSS
from Crypto.PublicKey import ECC

def pair_to_bytes(b, c):
    return b"".join([b.to_bytes(32, 'big'), c.to_bytes(32, 'big')])
    
def bytes_to_pair(signature):
    b = int.from_bytes(signature[:32], 'big')
    c = int.from_bytes(signature[32:], 'big')
    return b, c

key_cryptodome = ECC.construct(curve='p256', d=sk, point_x=pk[0], point_y=pk[1])
signer = DSS.new(key_cryptodome, 'deterministic-rfc6979')

h = SHA256.new(message.encode("utf-8"))
s = pair_to_bytes(b, c)

try:
    signer.verify(h, s)
    print("La signature est valide")
except:
    print("La signature n'est pas valide")

Question 5. Éxécutez les lignes précédentes et vérifiez que la signature construite est valide. Puis, modifiez le message et/ou la signature et observez que la signature n’est plus valide.

# à compléter

Exercice 3 : interaction avec openssl#

Dans l’exercice 1, nous avons observé que openssl produisait des clés et des signatures sous des formats très particuliers. Voici des manières de « parser » ces fichiers :

import os
import base64

# lit une clé publique au format .pem (pour la courbe considérée ici)
# et retourne le point correspondant sous forme de couple d'entiers
def read_public_key(filename):
    with open(filename, "r") as f:
        coded_key = f.readlines()[1:-1]
        coded_key = [ c[:-1] for c in coded_key ]
        key = base64.b64decode("".join(coded_key))
        pk_x = int.from_bytes(key[-64:-32], 'big')
        pk_y = int.from_bytes(key[-32:], 'big')
        return (pk_x, pk_y)

# lit une clé privée au format .pem (pour la courbe considérée ici)
# et retourne l'entier correspondant
def read_private_key(filename):
    with open(filename, "r") as f:
        coded_key = f.readlines()[1:-1]
        coded_key = [ c[:-1] for c in coded_key ]
        key = base64.b64decode("".join(coded_key))
        sk = int.from_bytes(key[7:39], 'big')
        return sk


# lit une signature au format binaire
# et retourne la paire d'entiers correspondant
def read_signature(filename):
    with open(filename, "rb") as f:
        b = f.read()
        r = int.from_bytes(b[-67:-35], "big")   # les octets de r (ou b)
        s = int.from_bytes(b[-32:], "big")      # les octets de s (ou c)
        return (r,s)

Question. Vérifier la signature générée par openssl à l’aide de la fonction de vérification verif implémentée plus haut.

# à compléter