“Clé privée, Clé publique… Laquelle chiffre ? Laquelle déchiffre ?”  Telle est LA question!

Ces derniers temps, j’ai entendu une question revenir lors des discussions crypto avec mes congénères développeurs: « Attends…On chiffre avec la clé publique, ou la clé privée ? ». Tout aussi fréquemment, une réponse suit: « Avec la clé publique. Tu ne te souviens pas de tes cours ?!? »  

Petit rappel de cryptographie

Voici schématiquement comment fonctionne un système de chiffrement. Un message, souvent intelligible, est passé à une fonction de chiffrement. Cette dernière reçoit également une “clé”. A la sortie, on obtient un charabia inintelligible.

Fonction de chiffrement

Réciproquement, nous pouvons déchiffrer le charabia inintelligible à l’aide d’une clé et d’une fonction de déchiffrement, pour retrouver le message original.

Fonction de déchiffrement

En cryptographie dite symétrique, on utilise la même clé pour chiffrer et déchiffrer. En crypto asymétrique en revanche, on a une paire de clés (C1, C2), communément appelées clé publique et clé privée. Celles-ci sont mathémagiquement liées, de sorte que  lorsqu’une clé est utilisée pour le chiffrement, c’est l’autre qui doit être utilisée pour déchiffrer. Comme c’est toujours plus clair en image, voici un petit dessin

cryptographie asymetrique

* Oui, c’est bientôt mon entretien annuel

Codons un peu

Les plus curieux d’entre vous souhaiteront sans doute expérimenter avec Visual Studio. Nous allons donc créer un projet de type “class library” et des tests associés.

Une fois le projet créé, nous devons ajouter une référence à la bibliothèque qui contiendra les différents outils de cryptographie asymétrique dont nous aurons besoin. Pour l’expérience, je vous propose d’installer via Nuget le package BouncyCastle, une librairie qui revient fréquemment dans les recommandations.

Nuget BouncyCastle

Maintenant que tout est prêt, commençons. Dans un premier temps, voici les instructions “using” dont nous aurons besoin.

using System;
using System.Text;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.Security;

Et pour plus de lisibilité dans la suite du code, définissons deux constantes

private const bool UseForEncryption = true;
private const bool UseForDecryption = false;

Ensuite, il nous faut une fonction qui va générer une paire de clés (privée, publique)

public AsymmetricCipherKeyPair GenerateRsaKeys()
{
    var keyGenerator = new RsaKeyPairGenerator();

    var random = new SecureRandom();
    var strength = 1024; 

    var keyGenerationParameters = new KeyGenerationParameters(random, strength);

    keyGenerator.Init(keyGenerationParameters);
    return keyGenerator.GenerateKeyPair();
}

Une fois notre paire de clés en main, il nous faut pouvoir chiffrer un message…

public string Encrypt(string message, ICipherParameters key)
{
    var engine = new RsaEngine();
    engine.Init(UseForEncryption, key);
    var messageBytes = Encoding.UTF8.GetBytes(message);

    var encryptedBytes = engine.ProcessBlock(messageBytes, 0, messageBytes.Length);

    var encryptedText = Convert.ToBase64String(encryptedBytes);
    return encryptedText;
}

…puis  de déchiffrer

public string Decrypt(string base64Message, ICipherParameters key)
{
    var engine = new RsaEngine();
    engine.Init(UseForDecryption, key);
    var messageBytes = Convert.FromBase64String(base64Message);

    var decryptedBytes = engine.ProcessBlock(messageBytes, 0, messageBytes.Length);

    var decryptedText = Encoding.UTF8.GetString(decryptedBytes);
    return decryptedText;
}

Voici le code complet pour les plus paresseux

using System;
using System.Text;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.Security;

namespace Mystere2Cles
{
    public class RsaExplorer
    {
        private const bool UseForEncryption = true;
        private const bool UseForDecryption = false;

        public AsymmetricCipherKeyPair GenerateRsaKeys()
        {
            var keyGenerator = new RsaKeyPairGenerator();

            var random = new SecureRandom();
            var strength = 1024; // Petites clés, pour rendre les tests rapides.

            var keyGenerationParameters = new KeyGenerationParameters(random, strength);

            keyGenerator.Init(keyGenerationParameters);
            return keyGenerator.GenerateKeyPair();
        }

        public string Encrypt(string message, ICipherParameters key)
        {
            var engine = new RsaEngine();
            engine.Init(UseForEncryption, key);
            var messageBytes = Encoding.UTF8.GetBytes(message);

            var encryptedBytes = engine.ProcessBlock(messageBytes, 0, messageBytes.Length);

            var encryptedText = Convert.ToBase64String(encryptedBytes);
            return encryptedText;
        }

        public string Decrypt(string base64Message, ICipherParameters key)
        {
            var engine = new RsaEngine();
            engine.Init(UseForDecryption, key);
            var messageBytes = Convert.FromBase64String(base64Message);

            var decryptedBytes = engine.ProcessBlock(messageBytes, 0, messageBytes.Length);

            var decryptedText = Encoding.UTF8.GetString(decryptedBytes);
            return decryptedText;
        }
    }
}

Nous pouvons maintenant tester le chiffrement avec la clé publique, et le déchiffrement avec la clé privée.

[TestMethod]
public void Should_decrypt_with_private_key_when_encrypted_with_public_key()
{
    var explorer = new RsaExplorer();
    var rsaKeys = explorer.GenerateRsaKeys();
    var message = "Arnaud est un boss génial!!"

    var gibberish = explorer.Encrypt(message, rsaKeys.Public);
    Assert.AreNotEqual(message, gibberish);

    var messageRetrieved = explorer.Decrypt(gibberish, rsaKeys.Private);
    Assert.AreEqual(message, messageRetrieved);
}

Maintenant, testons le chiffrement avec la clé privée, et le déchiffrement avec la clé publique.

[TestMethod]
public void Should_decrypt_with_public_key_when_encrypted_with_private_key()
{
    var explorer = new RsaExplorer();
    var rsaKeys = explorer.GenerateRsaKeys();
    var message = "Fathi est le meilleur manager du monde!!"

    var gibberish = explorer.Encrypt(message, rsaKeys.Private);
    Assert.AreNotEqual(message, gibberish);

    var messageRetrieved = explorer.Decrypt(gibberish, rsaKeys.Public);
    Assert.AreEqual(message, messageRetrieved);
}

la vraie réponse est donc…

…42!« ça dépend ! »

Effectivement, tout dépend de l’intention du chiffrement. Ce qu’il faut savoir c’est que la clé privée est – par définition – celle qui permet d’effectuer l’opération que seul le propriétaire est censé faire.

Ainsi, on distingue 2 cas d’utilisation.

La transmission d’informations confidentielles

Ici, on veut que n’importe qui puisse nous envoyer un message secret – en d’autres termes “chiffré” –  et on veut être le seul à pouvoir le déchiffrer. N’importe qui doit donc pouvoir chiffrer un message. Ca tombe bien car, par définition, n’importe qui peut avoir la clé publique!

Dans ce contexte donc,  on chiffre avec la clé publique et, par conséquent, déchiffre avec la clé privée

La signature électronique

Ici, on veut être le seul à pouvoir signer le message de manière infalsifiable. Pour ce faire, on le chiffre à l’aide de notre clé privée. N’importe quelle personne doit pouvoir vérifier l’authenticité de la signature. N’importe qui doit donc pouvoir déchiffrer le message. Et ça tombe bien, car n’importe qui peut avoir la clé publique!

Dans ce contexte donc, on déchiffre avec la clé publique et, par conséquent, chiffre avec la clé privée.

En conclusion

Pour ne plus jamais se tromper en répondant à LA question – autrement que par “42” –  on peut se demander :

Qui peut voir l’information originale (déchiffrer) ?

  • Le récepteur seul  (transmission de secret) ? => on déchiffre avec la clé privée
  • N’importe qui (signature électronique) ? => on déchiffre avec la clé publique

On en déduit celle qui chiffre et le tour est joué!