On peut être amené à ne crypter qu’une partie d’un message envoyé à un serveur. Par exemple avec un formulaire de connexion l’identifiant peut passer en clair, mais pas le mot de passe. Aujourd’hui, les hébergements avec du SSL ou un certificat auto-signé sont monnaie courante, mais comment faire avec une application Google Go hébergée sur le Cloud Google App Engine ?
Rappel concernant RSA
Voici un exemple de chiffrement partiel d’un contenu à l’aide de l’algorithme asymétrique RSA. On rappelle qu’avec RSA il y a deux clés :
- Une clé publique avec laquelle le client peut crypter des messages, mais pas les décrypter.
- Une clé privée avec laquelle le serveur peut crypter et décrypter des messages (puisqu’une clé privée RSA contient également la clé publique).
Générer les clés avec openssl
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 (allez voir sur le site, il existe maintenant des versions pour tous les OS) avec les commandes suivantes :
openssl genrsa -out private.pem 1024
openssl rsa -pubout -in private.pem -out public.pem
Template et code Golang du formulaire de connexion
Puis on créera un template pour un formulaire de login. Ce template utilisera la bibliothèque 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).
- 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}}</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>
Le gestion de la page est ultra 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)
}
}
Décryptage du message chiffré avec RSA côté serveur
Là où les choses se compliquent, c’est pour décoder le message côté serveur. Le code est assez long parce qu’il effectue toutes les actions suivantes :
- Lire le fichier contenu la clé privée.
- Interpréter le format de fichier PEM et tester sa validité.
- Décoder la clé privée.
- Convertir le message crypté passé dans le formulaire de la base64 à du binaire.
- Déchiffrer le message crypté (le mot de passe).
func sendHandler(w http.ResponseWriter, r *http.Request) {
// Read the private key
pemData, err := ioutil.ReadFile(privateKey)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// Extract the PEM-encoded data block
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
}
// Decode the RSA private key
priv, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// Decode the Base64 into binary
cipheredValue, err := base64.StdEncoding.DecodeString(r.FormValue(« CipheredValue »))
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// Decrypt the data
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 votre gestionnaire des utilisateurs. Par exemple, en vérifiant le mot de passe avec une valeur stockée en base de données.