Nous allons voir dans ce billet comment chiffrer, signer, vérifier une signature et déchiffrer un message avec Go en utilisant l’algorithme RSA. Le but de ce billet étant d’être le plus accessible possible, je vais commencer par quelques rappels et définitions. Attention dans un but de vulgarisation je risque de prendre quelques raccourcis pouvant paraître un peu violents pour certains. Vous voila prévenu ;)
Rappels
Chiffrement
Chiffrer un message va consister à lui appliquer une transformation pour qu’il ne soit plus directement interprétable.
Pour réaliser cette transformation on peut utiliser plusieurs algorithmes, plus ou moins robustes, plus ou moins gourmands en ressource.
Pour déchiffrer le message, il faudra connaitre l’algorithme et, éventuellement, posséder un élément “unique” communément appelé clé.
Le rôle de cette clé est de faire en sorte que le chiffrement soit unique.
Prenons un exemple basique avec un algorithme qui va transformer chaque caractère d’un message par un autre qui sera en décalage de X positions dans la table ASCII.
Notre message d’origine: Hello Crypto
On va définir X, que l’on peut assimiler à notre clé de chiffrement à +1
. On va donc remplacer chaque caratère du message d’origine par celui qui le suit: H
va devenir I
, e
-> f
, etc…
Notre message chiffré sera donc Ifmmp!Dszqup
PS: Inutile de vous dire de ne pas utiliser ce type de chiffrement dans la “vraie vie"©
Signature
Le fait de signer un message va/peut permettre deux choses:
- Authentifier la personne qui l’a émis.
- S’assurer qu’il n’a pas été modifié.
Il était une fois Alice et Bob
Alice et Bob sont deux stars de la cryptographie, non pas qu’ils aient inventé des algorithmes hyper robustes, mais parce qu’ils jouent un rôle dans la plupart des articles de vulgarisation sur la cryptographie. Vous verrez souvent, et entre autre ici, l’histoire d’Alice qui veut envoyer un message chiffré à Bob. On se demande bien ce qu’ils mijotent tous les deux…
Cryptographie assymétrique
Pour illustrer ce principe, prenons donc notre chère Alice qui souhaite envoyer un message signé à Bob, pour une fois ils n’ont rien de secret à se raconter, le message sera juste signé et pas chiffré.
Donc Alice rédige son message, utilise sa clé pour le signer et le transmet à Bob.
Bob reçoit le message et veut s’assurer qu’il vient bien d’Alice (et qu’accessoirement personne ne la modifié sur la route).
Comment va t’il faire ?
Bin il va demander la clé d’Alice, signer le message reçu avec et vérifier si la signature générée colle avec la signature accompagnant le message pardi !
Oui… mais non. Pourquoi ? Parce que dans ce cas, qu’est ce qui va empêcher Bob de générer un message, de le signer avec la clé d’Alice et donc de se faire passer pour Alice ?
Mais alors comment faire Jamy ?!!
Et bien c’est pas sorcier, il suffit d’utiliser deux clés:
- une clé privée que va précieusement garder Alice et qui va lui permettre de signer son message.
- une clé publique, associée à la clé privée, qui va permettre à Bob de vérifier que le message à bien été signé en utilisant la clé privée d’Alice.
Dans le cadre du chiffrement c’est le même principe, si Alice veut envoyer un message chiffré à Bob elle va récupérer la clé publique de Bob, l’utiliser pour chiffrer le message et lui envoyer. De son coté Bob utilisera sa clé privée pour déchiffrer le message. Bien entendu pour que ça fonctionne, il va de soit que seule la clé privée soit en mesure de déchiffrer le message généré en utilisant la clé publique.
Passons à la pratique
Il est temps de mettre les mains dans le cambouis avec l’exemple classique suivant: Alice veut envoyer un message chiffré et signé à Bob en utilisant l’algorithme RSA
Je vous met directement le code complet avec les commentaire pour vous permettre de suivre ce qui est fait.
Pour l’exécuter de votre coté, copier le dans un fichier tuto_rsa.go et exécutez le via la commande go run tuto_rsa.go
package main
import (
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"fmt"
"log"
"os"
)
func main() {
const messageAlice = "Bonjour Bob, c'est Alice, le code secret de la porte de l'immeuble est 1234. A tout de suite ;)"
// On génere une paire de clé pour Alice
AlicePrivKey, AlicePubKey, err := genKeys()
handleERR(err)
// On génére une paire de clés pour Bob
BobPrivKey, BobPubKey, err := genKeys()
handleERR(err)
// Bob à donné se clé publique à Alice
// elle va l'utiliser pour chiffrer son
// message
// hash doit être une focntion de hashage qui va permettre de
// générer de l'entropie (du désordre)
hash := sha256.New()
// un peu plus d'entropie utilisé par la fonction de chiffrement
randReader := rand.Reader
// le label peut contenir une "message" qui ne sera pas chiffrer
label := []byte{}
// on chiffre
messageChiffre, err := rsa.EncryptOAEP(hash, randReader, BobPubKey, []byte(messageAlice), label)
fmt.Printf("Message d'Alice chiffré:\n%x\n", messageChiffre)
// maintenant que le message est chiffré Alice doit à présent
// le signer pour que Bob soit certain que c'est Alice qui l'a
// émis et qu'il n'a pas été altéré
var opts rsa.PSSOptions
opts.SaltLength = rsa.PSSSaltLengthAuto
newhash := crypto.SHA256
pssh := newhash.New()
pssh.Write([]byte(messageAlice))
hashed := pssh.Sum(nil)
signature, err := rsa.SignPSS(
rand.Reader,
AlicePrivKey,
newhash,
hashed,
&opts,
)
handleERR(err)
fmt.Printf("La signature du message d'Alice est:\n%x\n", signature)
// Alice peut a présent transmettre le message chifré et la signature a Bob
// Bob reçoit le message et la signature
// il commence par déchiffre le message
messageDechiffre, err := rsa.DecryptOAEP(
hash,
randReader,
BobPrivKey,
messageChiffre,
label,
)
handleERR(err)
fmt.Printf("Le message dechiffré par Bob est: %s\n", messageDechiffre)
// Bob doit à présent s'assurer que le message vient d'Alice
// et qu'il n'a pas été altérer en route
// pour cela il va valider la signature en utilisant la
// clé publique d'Alice
err = rsa.VerifyPSS(
AlicePubKey,
newhash,
hashed,
signature,
&opts,
)
if err != nil {
fmt.Println("Ce n'est pas Alice qui a envoyé ce message !!!")
os.Exit(1)
} else {
fmt.Println("Le message vient bien d'Alice !!!")
}
}
// genKeys génére et retourne une paire de clés
func genKeys() (privKey *rsa.PrivateKey, pubKey *rsa.PublicKey, err error) {
privKey, err = rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return
}
return privKey, &privKey.PublicKey, nil
}
func handleERR(err error) {
if err != nil {
log.Fatal(err)
}
}
Voila j’espère que ce petit tuto vous aura plus, n’hésitez pas à commenter et partager.