Comment utiliser l'algorithme à clef publique RSA ?

Introduction

Cet article n’a pas vraiment d’application concrète, il sert surtout à illustrer comment utiliser les différents paquets golang liés au chiffrement, encodage et hashage. Imaginons le cas d’usage suivant. L’ensemble de votre application est en clair (c’est-à-dire que vous n’accédez pas à votre application en HTTPS, mais en HTPP). Comme votre application n’expose pas de données sensibles, cela vous convient. Maintenant vous souhaiteriez que personne ne puisse découvrir certaines des données échangées telles que le mot de passe au moment de la connexion.

Les différentes méthodes de chiffrement

Je ne vais pas entrer dans les détails, mais il existe deux méthodes de chiffrement des données : Dans notre cas d’usage, la cryptographie symétrique ne nous intéresse pas. Car il faudrait, qu’en plus de saisir son mot de passe, l’utilisateur saisisse la clé de chiffrement ou qu’elle soit échangée entre le client et le serveur (donc en clair). Soit ce n’est pas pratique, soit elle serait découverte par un tiers. Nous allons plutôt mettre en œuvre un algorithme de chiffrement asymétrique nommé RSA. Cet algorithme exploite deux clés :
  • Une clé publique qui est connue de tout le monde, mais qui ne sert qu’à chiffrer les informations. On ne peut pas utiliser cette clé pour déchiffrer les informations.
  • Une clé privée qui est conservée à l’abri des regards indiscrets. Cette clé peut servir aussi bien à chiffrer qu’à déchiffrer les informations.

Mise en œuvre

Prérecquis

La première étape consiste à générer une paire de clé publique et privée, puis à les stocker dans votre arborescence projet. On peut bricoler quelque chose avec golang ou utiliser openssl avec les commandes suivantes :
openssl genrsa -out private.pem 1024
openssl rsa -pubout -in private.pem -out public.pem

Le code côté client

On crée un template contenant un formulaire de login. Ce template utilisera la bibliothèque jsencrypt (https://github.com/travist/jsencrypt) qui est une implémentation de l’algorithme RSA en Javascript. Le principe du code est le suivant :
  • A l’appel de la page, Go remplira la variable public_key avec le contenu de la clé publique stockée côté serveur (et que vous avez générée plus tôt avec openssl).
  • Côté client, on chiffre le mot de passe avec la clé publique et on envoie le contenu chiffré au serveur. Notez que le mot de passe en clair n’est pas transmis puisque le champ est à l’extérieur du formulaire. La bibliothèque jsencrypt se charge également d’encoder le contenu chiffré (qui est binaire) en base64 de manière à ce qu’il puisse transiter en tant que valeur du formulaire sans problème.
<!doctype html>
<html>
<head>
    <meta charset="utf-8">
    <title></title>
    <script src="/js/jsencrypt.min.js"></script>
    <script src="/js/jquery-1.10.2.min.js"></script>
</head>
<body>

<form id="target" action="/send" method="POST">
    <label for="User">User:</label>
    <input type="text" name="User" autofocus required /><br />
    <input type="hidden" name="CipheredValue" id="CipheredValue" /><br />
</form>
<label for="Password">Password:</label>
<input type="password" name="Password" id="Password" required /><br />
<input id="encrypt" type="button" value="send" />
        
    <script>
    var public_key = "{{printf "%s" .Value}}";
    
    $(function() {
        $('#encrypt').click(function() {
        var encrypt = new JSEncrypt();
        encrypt.setPublicKey(public_key);
        var encrypted = encrypt.encrypt($('#Password').val());
        $('#CipheredValue').val(encrypted);
        $('#target').submit();
        });
    });    
    </script>
    
</body>
</html>
Voici le code du handler qui gère la page de connexion. Le code est simple puisqu’il s’agit simplement de servir le template et de remplir la variable contenant la clé publique :
type Page struct {
    Title string
    Value []byte
}
func homeHandler(w http.ResponseWriter, r *http.Request) {
    // Read the public key
    pemData, err := ioutil.ReadFile(publicKey)
    if err != nil {
        log.Fatalf("read key file: %s", err)
    }
    var p = Page{Title: "Login", Value: pemData}
    err = templates.ExecuteTemplate(w, "home.html", p)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
    }
}

Le code côté serveur

Le code côté serveur est assez long parce qu’il effectue toutes les actions suivantes :
  1. Lire le fichier contenant la clé privée.
  2. Décoder le fichier contenant la clé privée (au format PEM) et tester sa validité.
  3. Charger la clé privée.
  4. Convertir le message crypté passé dans le formulaire de la base64 à du binaire.
  5. Déchiffrer le message crypté (le mot de passe) et l’afficher dans une autre page.
func sendHandler(w http.ResponseWriter, r *http.Request) {

    // Lire le fichier contenant la clé privée
    pemData, err := ioutil.ReadFile(privateKey)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

    // Décoder le fichier de la clé privée (au format PEM) et tester sa validité
    block, _ := pem.Decode(pemData)
    if block == nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    if got, want := block.Type, "RSA PRIVATE KEY"; got != want {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

    // Charger la clé privée
    priv, err := x509.ParsePKCS1PrivateKey(block.Bytes)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

    // Convertir le message crypté (du formulaire) de la base64 à du binaire
    cipheredValue, err := base64.StdEncoding.DecodeString(r.FormValue("CipheredValue"))
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

    // Déchiffrer le message crypté (le mot de passe)
    var out []byte
    out, err = rsa.DecryptPKCS1v15(rand.Reader, priv, cipheredValue)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
    }

    var p = Page{Title: "Decrypt value", Value: out}

    err = templates.ExecuteTemplate(w, "send.html", p)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
    }
}
Il ne vous reste plus qu’à compléter cet extrait de code avec un système de gestion des utilisateurs. Par exemple, en vérifiant le mot de passe avec une valeur stockée en base de données.

Étiquettes :   cryptographie 
Portrait de Benjamin BALET
Benjamin BALET
Consultant APM

Retrouvez mes cooordonées

Benjamin BALET sur viadeo






Vous aimerez aussi

Les lois de la réflexion

Une traduction du blog officiel de golang expliquant le mécanisme de la réflexion en Go.   Lire »

Gobs le format natif d'échange de données en Go

Traduction d'un article du blog officiel expliquant comment échanger des données entre deux programmes golang grâce à un format natif   Lire »

Gérer les informations de session avec Gorilla

La bibliothèque standard de go ne gère pas les variables de session d'une application. Il existe une solution avec le toolkit Gorilla   Lire »

Comment gérer efficacement les erreurs en golang ?

Préconisations officielles pour la gestion des erreurs dans un programme golang. Cet article complète les explications sur panic, defer et recover   Lire »

Canaux et go routines avec ou sans état

Exemples d'utilisation du type chan et des go routines stateful et stateless   Lire »

Commentaires

Soyez le premier à commenter cet article

Publier un commentaires

Tous les commentaires sont soumis à modération. Les balises HTML sont pour la plupart autorisées. Les liens contextuels et intéressants sont en follow.

(requis)
(requis)
(requis, mais non publié)
(recommandé si vous souhaitez être publié)