C’est quoi une signature ?
Si vous avez lu l’article sur les checksums en javascript (https://www.lavachequicode.fr/calculer-un-checksum-en-javascript) Vous aurez compris qu’un checksum peut être créé par n’importe qui.
Si on l’ajoute à la fin d’un message on peut repérer si il y a eu une erreur pendant le transport. On dit qu’on assure l’intégrité de la donnée. Cependant rien n’empêche un attaquant de modifier le message, puis de modifier le checksum.
La signature électronique en revanche est créé à l’aide d’une donnée secrète, elle ne peut être généré que par l’émetteur du message.
Elle assure donc l’authenticité d’un message, exactement comme une signature sur un document papier.
signature symétrique
Dans le cas d’un chiffrement symétrique, les deux personnes souhaitant échanger un message se partage secrètement une clé. L’émetteur va ensuite calculer la signature du message qu’il veut envoyer au récepteur grâce à la clé secrète et à la fonction de signature f:
s = f(m,k)
puis il transmet le couple (s,m).
le récepteur peut vérifier que le message est authentique en recalculant f(m,k). Si le résultat est S, le message est authentique.
Ainsi un attaquant ne pourra plus modifier le message : il ne peut pas recalculer s sans connaitre la clé k. De plus, s’il modifie simplement m, le récepteur verra que la signature ne correspond pas au message (c’est l’objet de cet article).
Notez que la signature ne protège pas contre le rejeu. un attaquant pourra renvoyer le message à posteriori et le récepteur aura l’impression d’avoir reçu deux fois le même message (embêtant si il s’agit d’un transfert d’argent par exemple).
Pour éviter cela, on peut par exemple inclure un timestamp dans la signature:
si h = now(), on calcule s = f(h+m,k) et on envoie (h,m,k). Le récepteur saura alors quand le message à été signé.
On peut également maintenir un compteur que l’on incrémente à chaque message:
on calcule s = (i+m,k) et on envoie (m,k) car le récepteur connait le numéro du message à venir.
signature asymétrique
Dans le cas d’un algorithme de signature asymétrique, l’émetteur choisi une clé secrète k et il en déduit clé k_public à partir de laquelle il est impossible de retrouver k en un temps raisonnable. Il peut donc la partager publiquement.
Lorsqu’il souhaite émettre un message il va donc signer le message avec une fonction g
s = g(m, k)
puis il diffuse (s,m)
Tout le monde va ensuite pouvoir vérifier l’authenticité du message à l’aide de la fonction de vérification (appelons la v).
Il suffit de calculer v(m, k_public, s)
Les algorithmes
Nous allons nous intéressé ici au cas asymétrique.
Il existe deux principaux algorithmes :
RSA du nom de ses inventeurs : Rivest-Shamir-Adleman.
ECDSA pour elyptic curve digital signature algorithm.
Et en Javascript, ça donne quoi ?
Nous allons voir ici comment vérifier une signature ECDSA ethereum en javascript.
Pour être précis, les messages dont nous souhaitons vérifier la signature sont hachés avec le hash Keccak256 (souvent appelé sha3) puis signés avec l’algorithme ECDSA suivant la courbe secp256k1.
En NodeJS
vous aurez besoin au préalable des librairies web3 et ethereumjs-util.
Pour une utilisation en back (node):
1 2 |
npm install web3 npm install ethereumjs-util |
puis dans votre fichier index.js:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
Utils = require('ethereumjs-util'); Web3 = require('web3'); //l\'objet web3 n\'est pas obligé d\'être connecté à un nœud ethereum, //dans ce cas il nous sert uniquement à calculer le hash sha3. web3 = new Web3(); //le message qui a été signé: msg = 'myjjjjjjjdata'; //la signature: sgn = '0x942d58a085375c43018a5be48d32e23e6d870b038c47ffb01f76a99fa346e43d5ea721982e75216392225d39894c290c666e2916b1ca81b9ce6a7cc0064900e51b'; //on hash le message hash = web3.sha3(msg); r = Utils.toBuffer(sgn.slice(0,66)); s = Utils.toBuffer('0x'+sgn.slice(64,130)); v = Utils.toBuffer('0x'+sgn.slice(130,132)).readIntLE(); m = Utils.toBuffer(hash); console.log(r); console.log(s); console.log(v); pub = Utils.ecrecover(m,v,r,s); //la clé publique qui a signé le message: console.log('ce message a été signé par: 0x' + Utils.pubToAddress(pub).toString('hex')); |
notez qu’il est tout à fait possible d’effectuer la même opération en front.
Dans un navigateur
Pour pouvoir utiliser la librairie ethereumjs-util en front, on peut utiliser browserify.
1 |
sudo npm install browserify -g |
Pour que notre objet ethereumjs soit accessible à l’extérieur de browserify (depuis nos scripts natif) on crée le fichier suivant :
1 |
window.ethereumjs = require('ethereumjs-util'); |
et on crée le bundle:
1 |
browserify index.js -o bundle.js |
il suffit ensuite d’inclure bundle.js dans notre index.html :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
<html> <script src="bundle.js"></script> <script> //on peut utiliser notre objet ethereumjs : console.log(ethereumjs); // on défini une fonction permettant de vérifier une signature localement: var verify = function(signature, message, address) { var hash = web3.sha3(message); var r = ethereumjs.toBuffer(signature.slice(0,66)); var s = ethereumjs.toBuffer('0x'+signature.slice(64,130)); var v = ethereumjs.toBuffer('0x'+signature.slice(130,132)).readIntLE(); var m = ethereumjs.toBuffer(hash); var pub = ethereumjs.ecrecover(m,v,r,s); var msg_address = '0x' + ethereumjs.pubToAddress(pub).toString('hex'); console.log('ce message a été signé par:' + msg_address); return msg_address == address }; //le message qui a été signé: msg = 'myjjjjjjjdata'; //la signature: sgn = '0x942d58a085375c43018a5be48d32e23e6d870b038c47ffb01f76a99fa346e43d5ea721982e75216392225d39894c290c666e2916b1ca81b9ce6a7cc0064900e51b'; //l'adresse originale (ou non) addr = '0x48e76a0808f99b352fa45ce556f24aa4237b9611'; //on vérifie (retourne vrai ou faux): console.log(verify(sgn, msg, addr)) </script> </html> |
Notez qu’il n’est pas forcement nécessaire de créer un nouvel objet web3, il est dans certain cas déjà présent (utilisation du plugin metamask par exemple).
On pourra simplement faire:
1 2 3 |
if(typeof web3 == 'undefined'){ web3 = new Web3(); } |