Séance 10 : ECDSA#
Dans ce TP, nous allons explorer l’utilisation de l’algorithme de signature ECDSA. Les objectifs sont les suivants :
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.
Comprendre l’utilisation de la bibliothèque de cryptographie
openssl, libre et multi-plateforme, ainsi que des modules de la bibliothèque pythonpycryptodomeFaire 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éeprime256v1dans 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,
verifyretourne une erreur (qu’il faut traiter) ;si la signature est valide,
verifyretourneFalse. Elle ne renvoie donc jamaisTrue.
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