[ESPHome] [Tuto] RF 433 MHz + la sécurité du rolling code

Bonjour à toutes et tous

L’interception d’une trame RF433 Mhz n’est pas inconnue. Entre RF link, RFcomm, RF ceci et cela, il y a pléthore de solutions.

Même notre ESPHome s’y est mis, avec le module remote_receiver.

Cependant, j’ai toujours trouvé une certaine limitation à ces dispositifs : la sécurité.

Si nos portes de garage sont on ne peut plus sécurisées par un code tourant (rolling code), il n’en n’est pas de même pour nos bricolages.

Je cherchais une solution pour utiliser une télécommande, mais de manière plus “safe”, de sorte que le rejeu d’une trame ne puisse pas provoquer la commande.

Et force est de constater qu’il n’y a pas beaucoup de solutions. J’ai donc essayé de faire la mienne.

ATTENTION

La première des choses à savoir est que je ne cherche en aucun cas à “craquer” une clef de sécurité de ces télécommandes. Donc si l’objectif est d’ouvrir le garage de votre voisin à qui vous avez prêté la tondeuse depuis 2 ans, passez votre chemin ! Ici, nous allons créer NOTRE clef de chiffrement et NOTRE télécommande.

MISE EN GARDE

L’utilisation de télécommande pour l’ouverture de votre maison se fait à vos risques et périls ! Le concept du rolling-code est robuste, mais rien n’est incraquable …

Ceci étant dit, il va falloir un minimum de matos pour pouvoir bosser.

1 - Avoir une télécommande rolling-code. J’avais un ancien dispositif avec 3 télécommandes, j’ai décidé d’en sacrifier une “pour la science”. Equipée d’une puce Microchip HCS301, c’est l’idéal pour ce que je veux en faire. Attention cependant, tous les modèles ne sont pas vendu compatibles Keeloq. Il faut vraiment s’assurer que la puce utilisée est une HCS301

2 - Avoir un ESP (32 idéalement) ainsi qu’un module de réception radio (SRX882 pour ma part) accordé à la fréquence des télécommande. Il existe plusieurs fréquences, il faut bien choisir (433,92 MHz idéalement, Somfy utilisant le 433.42 MHz. Attention aux télécommandes US ou chinoise, c’est parfois du 315 MHz, mais c’est une fréquence non publique réservée aux militaire…). Attention pour le module de réception : ne pas négliger l’antenne ! Les antennes livrées en forme de ressort ne sont pas forcément les meilleures, parfois un simple fil rigide fait mieux ! Dans ce cas, il faut l’accorder à la fréquence (la taille compte ici :laughing: ).

Pour ma part, j’ai choisi un ESP32 mini D1. La prise USC-C permet l’alimentation et la programmation, ce qui est un gros plus selon moi. J’utilise le 3V3 de l’ESP32 mini D1 pour alimenter le module RF. A l’avenir, j’utiliserai sans doute l’ESP pour piloter les relais. Comme cela, même en cas de crash du serveur domotique, l’ESP gère tout en local.

3 - Avoir un programmateur pour le HCS301. Pour ma part, un vieil Arduino Uno qui trainait avec le bon soft, quelques résistance 10k et c’était bon (enfin presque, voir ci-dessous).

4 - Une solution pour dessouder/ressouder les composants :sweat_smile: . Après plusieurs tentatives pour programmer mon HCS301 directement sur la télécommande, je me suis résigné à le dessouder avec un décapeur thermique (avec réglage de température, c’est pas le moment de cramer la télécommande). Un support pour faciliter la connexion avec le programmateur est également grandement appréciable car le composant n’est pas très gros !

Le matos, c’est bien, mais sans les soft, ça ne sert pas à grand chose !

1 - Firmware pour l’Arduino : Sur Github

2 - Programmateur pour le HCS : Github encore et toujours. Il est déjà disponible compilé, mais les sources sont dispo (C#)

La version en ma possession (v0.2.8) ne me permet pas de modifier le manufacturer code, mais j’ai la main sur la clef (la clef est généralement définie par le manufacturer code et le serial number)… Key = la clef de chiffrement 64 bits), SER = le numéro de série que vous souhaitez donner à votre télécommande (28 bits), SYNC permet de fixer un point de départ de votre compteur, SEED = je crois que c’est pour l’apprentissage/appairage, mais je n’ai pas creusé cette partie. Laisser cocher “auto gen from SER LSB” pour le “discrimination”, cela permettra de s’assurer que la trame est correctement déchiffrée. Sinon, vous pouvez mettre le discriminant que vous souhaitez. Shutoff timer permet d’arrêter l’émission si celle-ci dure plus de 25 secondes (fait partie du code SER, c’est le MSB), et OVR Set permet d’étendre le nombre max du compteur de synchronisation, mais c’est un fonctionnement assez particulier, je ne le traite pas ici (à décocher donc). Le VDD permet de sélectionner la tension d’alimentation, ce qui permettra d’en extraire la valeur de tension passe transmise par le bit vlow. Et enfin, dans le menu option, il est possible de sélectionner le type de puce (rappel, je n’ai fais de tests que sur le HCS301), et de désactiver la fonction d’apprentissage (car là encore, je ne l’ai pas implémentée).

Si vous souhaitez aller plus loin, un analyseur logique peut être intéressant à utiliser (pour “voir” les trames émises ou reçues), un banc de test à pointes de touches (photo) … Tout cela m’a été indispensable pour le débogage, car ce ne fut pas un long fleuve tranquille :sweat_smile:

Un peu de technique:

D’un point de vue simplifié, une télécommande envoie un message “c’est moi, et quelqu’un a appuyé sur le bouton n°2”. L’inconvénient, c’est que cette télécommande, ça peut être celle du portail, d’un des 2 garages, d’un appareil Chacon etc … Et comme toutes les télécommandes (ou presque) utilisent la gamme de fréquence 433.92 MHz, ça risque d’être un peu délicat si l’allumage de la lampe d’ambiance du salon provoque l’ouverture du garage du voisin (… sauf s’il a toujours la tondeuse prêtée depuis 2 ans :joy: ). Donc, les fabriquant ont développés des “protocoles” de communication pour pouvoir parler à leurs interfaces et pas celles du voisin.

Mais une télécommande a un avantage qui est également un inconvénient : elle émet tout autour d’elle. Avantage ? Cela permet de ne pas être obligé de “pointer” la télécommande vers ce que l’on veut piloter. De plus, elle a une portée de plusieurs mètres, ce qui peut être très utile. Inconvénient ? Cette portée peut être mise à profit par quelqu’un de malveillant pour copier le signal que la télécommande émet. Et réémettre celui-ci une fois que vous êtes parti, pour ouvrir le garage par exemple (et prendre la débroussailleuse en plus de la tondeuse …). Bref : pas glop !

Il y a plusieurs techniques pour limiter les risques, et le code tourant en est une. En gros, lorsque le récepteur reçoit une commande, il va la déchiffrer avec une clef connue de lui seul et de la télécommande. A l’issue de cette opération, il va avoir une valeur qui correspond à un compteur. Il va vérifier si la commande fait partie des 16 prochaines valeurs (cette valeur dépend des fabricants) qu’il est en droit d’attendre (on appelle cela le hoping code). Si cela correspond, il exécute la commande et il modifie son compteur pour prendre en compte celui envoyé par la télécommande. Le compteur ne revient jamais en arrière sauf au bout de 65536 appuis. En fonctionnement “normal” (soit 10 appuis par jour), il reviendra à 0 dans 18 ans. Sachant qu’en plus, il y a des infos transmises en clair et en chiffré (ce qui permet de les comparer pour valider que le déchiffrement a été correctement effectué), qu’il y a un délai minimal entre 2 appuis etc … un éventuel malveillant mettra un certain temps avant de pouvoir avoir la bonne combinaison. Et il suffit de pas grand chose dans nos systèmes domotiques pour surveiller ce genre d’attaque en force …

Pourquoi les 16 prochaines valeurs ? Ben si vous appuyez par mégarde sur la télécommande dans votre poche ou votre sac, alors que vous êtes loin de chez vous …. Bah c’est ballot, en arrivant, impossible d’ouvrir : le rolling code a fait son office dans la télécommande, mais le récepteur n’est plus synchronisé …. D’où les 16 “chances” de ne pas activer la télécommande par erreur. Bon, et si vraiment vous n’avez pas de chance, il y a encore un système qui permet de resynchroniser les codes avec 2 appuis à proximité du récepteur … Mais si vous avez dépassé la marge de resynchronisation, c’est terminé : soit vous reprogrammez tout, soit c’est poubelle ! ces opérations ne sont pas encore implémentées dans le tuto que je vous livre …

Bon, ça, c’était la partie facile, on va rentrer dans le vif du sujet maintenant !

Voici le plus important à savoir sur le protocole Keeloq de Microchip. Je vous présente une trame:

Sachant que les 0 et 1 sont codés comme suit :

Et voici ce qu’elle contient:

2 zones principales, une chiffrée et une “en clair”. On peut constater que:

  • l’état des boutons est envoyé aussi bien en clair qu’en chiffré : cela permet 2 choses : s’assurer que le message est correctement déchiffré (les code bouton déchiffrés et “en clair” ne doivent pas être différents, sinon c’est que quelque chose s’est mal passé), et de pouvoir fonctionner sans tenir compte du rolling-code (le serial-number permet de différencier les télécommande et donc, les applications, et le code bouton permet de différencier les actions). Moins sécurisé certes, mais fonctionnel …
  • le serial number est en clair, cela permet d’identifier immédiatement (et donc de ne pas perdre de temps et d’énergie) à tenter de déchiffrer un message qui ne nous concerne pas. De plus, cela permet (via la zone DISC) de vérifier également que le code a été correctement déchiffré
  • le vlow indique l’état de la batterie de la télécommande … utile !
  • le repeat indique si le message est une réémission ou non (si on reste appuyé sur le bouton, la trame est émise en boucle. Cela permet de ne pas prendre en compte les trame suivantes si la première a été acceptée par exemple)
  • Le fameux compteur de synchronisation (chiffré) qui est le pilier du rolling code.

Avec tout cela, il y a de quoi faire pas mal de chose ! A condition de pouvoir déchiffrer la partie cryptée !

L’intégration Remote_receiver de nos ESPHome permet de faire une grosse partie du boulot : récupérer les trames chiffrées et en clair.

remote_receiver:
  id: rf_rx
  pin: GPIO26
  dump: keeloq
  on_keeloq:
    then:
      - lambda: |-

Le dump: keeloq permet de ne récupérer que les trames identifiées comme correspondant au protocole keeloq, ce qui permet de ne pas surcharger notre applicatif.

Le on_keeloq permet de réagir à l’arrivée d’une trame. A ce moment, une variable x est transmise pour pouvoir être utilisée dans une automation de type lambda. Et voici ce que contient cette variable :

On retrouve donc :

  • address, qui correspond à SER (pour serial) dans le logiciel “HCS301 Programmer”. C’est ce qui permet de différencier les télécommandes

  • command, qui correspond au code de la touche appuyée. 4 bits, avec normalement un bit = un bouton, mais certaines télécommandes peuvent combiner plusieurs bits simultanément pour augmenter le nombre d’actions possibles.

  • repeat : Si on reste appuyé sur un bouton, la télécommande va sans arrêt envoyer la même trame (sans incrémenter le compteur donc), hormis ce bit. A 0 pour la première trame, il passe à 1 dès la seconde et reste à 1 tant que le bouton est maintenu. Pratique pour ne pas exécuter plusieurs fois une automation, comme déjà évoqué.

    vlow : bit indiquant si la tension de la pile de la télécommande est considérée comme basse.

  • encrypted : une valeur chiffrée par une clef, connue de la télécommande et du récepteur seulement. Cette valeur est décomposée en plusieurs parties:

    • 16 bits de compteur de synchronisation (sync counter)
    • DISC : c’est un discriminant extrait du numéro de série (10 bits) et qui permet de s’assurer que la trame, une fois déchiffrée, correspond bien au numéro de série (address) codé en clair (ou à une valeur déterminée au moment de la programmation).
    • OVR : overflow du compteur de sync, qui permet a priori d’augmenter le nombre de combinaison possible du compteur
    • Et enfin, la répétition des 4 bits de ‘command’

Prochaine étape : déchiffrer tout cela !

A partir de maintenant, je vais aborder le processus de déchiffrement du protocole Keeloq. J’ai utilisé pour cela une note Microchip : AN642. Cette note étant classée “confidentielle”, je ne diffuserai ici que le strict minimum. Je ne sais pas quels risques j’encoure à diffuser ces informations, aussi je vais faire au strict minimum. Si vous souhaitez obtenir cette note, sachez que je l’ai trouvée sur le Net sans trop de difficultés, mais je ne la diffuserai pas.

Le déchiffrement est une succession de 528 calculs correspondant à une extraction de différents bits du CSR, qui permet d’extraire un bit d’une fonction non linéaire suivi d’un OU exclusif avec d’autres bit du CSR et de la clef. Celle-ci subit ensuite une de rotation de 1 bit à gauche et le CSR subit un décalage, avec insertion du résultat du XOR. Limpide, non ? :sweat_smile:

On va essayer de décoder tout cela. Le CSR, c’est la zone chiffrée (32 bits). Le key register, c’est la clef de chiffrement (64 bits). On récupère donc en premier 5 bits du CSR (0, 8, 19, 25 et 30) qui vont nous servir à “trouver” le résultat dans une table appelée NLF ou Non Linear Fonction. Je ne mets pas cette table volontairement, pour des raisons expliquées ci-dessus.

Cela va donner 1 bit, qui va être associé en OU exclusif (une petite recherche sur internet vous donnera la table de vérité de cette fonction… je vais pas tout vous dire non plus :joy: ) à 3 autres bits : les bits 15 et 31 du CSR et le bit 15 de la clef. Là encore on récupère 1 bit. Et on va permuter les registres : le CSR subit un décalage à gauche (on perd donc le MSB) en insérant le bit obtenu par calcul à la place du LSB, et la clef subit quant à elle une rotation à gauche (le MSB devient le LSB).

J’espère que c’est plus clair :slightly_smiling_face:

Et on fait cela 528 fois. Et à la fin, si tout s’est bien passé, le CSR correspond à la valeur déchiffrée !

Bon, comment intégrer tout cela dans notre ESP maintenant ?

Et bien grâce aux includes ! Dans l’entête du yaml, il est possible d’insérer un fichier include (fichier .h en C/C++)

esphome:
  name: recepteur-rf
  friendly_name: "Recepteur RF"
  includes: 
    - my_components/keeloq_secure_receiver/keeloq_utils.h

Et donc, de définir des fonctions spécifiques. Par exemple :

#pragma once
#include <stdint.h>


// Calcul NLF (Non Linear Fonction)
inline bool nlf(uint8_t csr0, uint8_t csr8, uint8_t csr19, uint8_t csr25, uint8_t csr31)
{
    // Calcul de l'équation logique
	uint32_t idx = (csr0 & 1)
		| ((csr8 & 1) << 1)
		| ((csr19 & 1) << 2)
		| ((csr25 & 1) << 3)
		| ((csr31 & 1) << 4);
	return (0x3A5C742Eu >> idx) & 1u;
}

inline uint64_t rollOnceLeft64(uint64_t value)
{
	// Extraire le bit MSB (bit 63)
	uint64_t msb = (value >> 63) & 1ULL;
	// Décaler à gauche
	value <<= 1;
	// Réinjecter le MSB dans le LSB
	value |= msb;
	return value;
}

inline uint32_t keeloq_decrypt(uint32_t data, uint64_t key) 
{
    bool xor_result{ 0 };
    for (int i = 0; i < 528; i++) 
    {
        // Récupération des 7 bits du CSR et celui de la clé
        bool csr0 = data & 1;
        bool csr8 = (data >> 8) & 1;
        bool csr15 = (data >> 15) & 1;
        bool csr19 = (data >> 19) & 1;
        bool csr25 = (data >> 25) & 1;
        bool csr30 = (data >> 30) & 1;
        bool csr31 = (data >> 31) & 1;
        bool key15 = (key >> 15) & 1;

        // Détermination du nouveau bit à insérer via la table de vérité
        xor_result = csr15 ^ csr31 ^ key15 ^ nlf(csr0, csr8, csr19, csr25, csr30);

        // Rotation de la clef
        key = rollOnceLeft64(key);

        // Rotation de la trame chiffrée et insertion du nouveau bit
        data = data << 1;
        data |= xor_result;
    }
  return data;
}

Bon, normalement, on ne met pas de code dans un .h, seulement des déclarations … Mais ESPHome est ainsi fait :laughing:

Les plus observateur auront vu qu’il y a un truc qui s’appelle NLF …. j’avais dit que je ne le mettrais pas ici … :roll_eyes:

Il suffit d’appeler cela dans notre YAML :

substitutions:
  keeloq_key: !secret keeloq_key

remote_receiver:
  id: rf_rx
  pin: GPIO26
  dump: keeloq
  on_keeloq:
    then:
      - lambda: |-
          uint64_t key = ${keeloq_key};
          uint32_t decrypted = keeloq_decrypt(x.encrypted, key);

Et decrypted contient alors les données déchiffrées. A vous d’en faire ce que vous voulez :grin:

Pour ma part, je vais ajouter le contrôle de cohérence du déchiffrement dans le fichier .h (en comparant DISC avec une partie du SERIAL) et gérer chaque télécommande avec son propre numéro de série. L’avantage ? Pouvoir révoquer une télécommande si on en perd une sans devoir tout recoder.

J’espère que cela pourra servir à d’autres. N’hésitez pas à poser des questions, et à demander si vous souhaitez voir la solution évoluer vers autre chose que ce tuto :slightly_smiling_face:

Remerciements :

Evidemment, sans le travail de nombreux autres passionnés, je n’aurais pas pu aller bien loin. Donc voici une liste que j’espère exhaustive des sources utilisées pour me documenter :

  • IOElectro pour sons travail sur le programmeur de puces HCS (via le firmware Arduino et le logiciel de programmation)
  • Le site Datelec.fr pour l’explication du protocole
  • Microchip, pour ses puces vraiment bien conçues et ses documentations, parfois fouillis mais toujours complètes.
  • Toutes les personnes ayant travaillé de près ou de loin à ESPHome et à l’intégration Remote_receiver.
2 « J'aime »