Encryption des messages Sigfox

Ce post va se focaliser sur l’encryption des données qui transite par le réseau Sigfox. A savoir l’encryption des données sur l’objet et la décryption de ces mêmes données dans votre backend applicatif.

Sigfox, un réseau dédié à l’IoT

Sigfox est un réseau LPWAN (Low Power Wide Area Network) dédié aux objets connectés. Ce réseau a la particularité de n’autoriser qu’au plus 144 messages uplink (depuis l’objet vers le cloud) de 12 octets maximum et 4 messages downlink (du cloud vers l’objet) de 8 octets maximum, et ce, par jour.

La vie d’un message Sigfox envoyé par un objet se déroule en trois étapes.

  1. Le message est envoyé sur une bande de fréquence (868MHz en Europe) depuis l’objet vers une antenne (ou plusieurs) du réseau Sigfox.
  2. Le message transite de l’antenne vers le backend Sigfox. Il n’existe pas d’information sur le réseau utilisé mais il est probable que l’on parle de connexions dédiées et/ou VPN.
  3. Sigfox transfère ensuite le message vers les différents Callback (HTTP, SMTP,  AWS, Azure,……..) que vous aurez configurés dans le backend Sigfox.

sigfox-network

Le but à atteindre et les contraintes

  1. Le but est assez simple à exprimer, nous voulons encrypter les données depuis l’objet et les garder encryptées jusqu’au niveau du backend applicatif.
  2. Etant donné que le nombre de messages qu’un objet peut envoyer par jour est limité et que chaque message est lui-même contraint de ne pas dépasser une taille maximum, il est important que l’encrytion ne consomme pas de messages ni de data. Autrement dit, le fait d’envoyer un message encrypté doit être transparent d’un point de vue de la consommation en messages et en data par rapport à l’envoi du même message non encrypté.
  3. D’un point de vue de l’objet, les capacités de calcul sont limitées et il faut réduire au maximum la consommation d’énergie. Une encryption gourmande en processing aura inévitablement une influence sur l’autonomie de celui-ci. Il faut donc une encryption peu gourmande en processing, qui sera par définition plus rapide à exécuter.

Choix de la méthode d’encryption

Afin de garder une méthode d’encryption peu gourmande en ressources, j’ai fait le choix d’utiliser le principe du “One-Time Pad”, que j’ai dû tordre un petit peu pour coller au modèle Sigfox. L’idée est d’appliquer une clef binaire sur les données à encrypter. Nous devons nous poser deux questions.

    1. Quel opérateur binaire faut-il appliquer entre les data et la clef pour obtenir la meilleure encryption? La question est donc de savoir si, entre le AND, OR et XOR, un des opérateurs donne une meilleure encryption. Petit rappel des opérations binaires :

0 AND 0 = 0          0 OR 0 = 0          0 XOR 0 = 0

0 AND 1 = 0          0 OR 1 = 1          0 XOR 1 = 1

1 AND 0 = 0          1 OR 0 = 1          1 XOR 0 = 1

1 AND 1 = 1          1 OR 1 = 1          1 XOR 1 = 0

Comme on peut le voir ci-dessus, l’opérateur AND va donner 75% de 0 et 25% de 1, l’opérateur OR va donner 25% de 0 et 75% de 1 et enfin, XOR aura pour résultat 50% de 0 et 50% de 1. Nous allons donc faire le choix de l’opérateur XOR qui donnera le plus de bruit, donc une meilleure encryption. Les images (de Charles BABBAGE) suivantes donneront une explication plus simple à comprendre. La première image est l’image d’origine non encryptée, la deuxième image est encryptée par l’opérateur AND, la troisième par l’opérateur OR et la dernière par l’opérateur XOR. Cet exemple montre clairement  que seul l’opérateur binaire XOR donne un résultat satisfaisant. (merci pour les images)

xor-notencryptedxor-encrypted-andxor-encrypted-orxor-encrypted-xor

    1. Quelle longueur de clef choisir ? Deux contraintes sont à prendre en considération pour répondre à cette question.
        1. Etant donné que nous encryptons 1 bit par 1 bit, il faut que la clef utilisée soit d’au moins la longueur du message à encrypter, sans quoi une partie du message resterait “clair” après l’encryption.
        2. Pour des raisons évidentes de sécurité, nous souhaitons avoir la possibilité de mettre la clef d’encryption à jour de façon régulière. Or nous savons que les messages Sigfox en downlink (depuis Sigfox vers l’objet) ne peuvent transporter que 8 bytes (64 bits), maximum 4 fois par jour. Il ne sera donc pas possible pour le backend d’envoyer une clef ou un morceau de clef de plus de 8 bytes (64 bits) par le réseau Sigfox.

      Etant données ces deux contraintes, nous allons choisir une clef d’encryption de 96 bits (12 bytes), soit la longueur maximale d’un message uplink Sigfox. La clef devra être connue de l’objet comme du backend avant la mise en service de l’objet. Il n’est en effet pas concevable, d’un point de vue sécurité, d’envoyer une clef d’encryption non cryptée elle-même sur le réseau. La mise à jour de la clef se fera par un échange de message uplink/downlink (ce sujet fera l’objet d’un article prochain).

Ce système d’encryption par la méthode du XOR avec une clef de longueur égale à la taille maximale d’un message uplink sera peu gourmande d’un point de vue processing au niveau de l’objet (encryption symétrique), et par conséquent également peu gourmande au niveau énergétique.

 

Encryption des données

Prenons un exemple :

Les données à envoyer par le réseau Sigfox sont: “1A2B3C” (sous format hexadécimal)

Notre clef d’encryption est: “ABCDEF1234567890ABCDEF12” (96 bits sous format hexadécimal). Dans l’exemple ci-dessous je n’ai utilisé que la fin de la clef puisque nous encodons 1bit vers 1bit.

Le résultat de l’encryption (en appliquant l’opérateur binaire XOR entre les données et la clef) est donc : 17C42E (toujours en format hexadécimal).

 

encryption

Décryption des données :

Il suffira de conduire la même opération sur les données encryptées avec la même clef pour obtenir les données non encryptées.

decryption

 

Conclusion:

L’opération binaire XOR permet d’encrypter les données avec un bon niveau de bruit sans avoir une trop grande consommation en processing et donc en énergie. En appliquant l’opérateur binaire XOR entre les données claires et la clef, nous obtenons les données encryptées. En appliquant la même opération entre les données encryptées et la clef nous obtenons les données en clair.

Comme vos données sont encryptées avant d’être envoyées par votre objet et qu’elle seront décryptées par votre backend applicatif, les données ont donc bien été encryptées de bout en bout.

Remarque :

Comme les données qui passent par le réseau Sigfox sont encryptées, il ne sera pas efficace/possible d’utiliser le “Custom Payload Config” proposé par Sigfox étant donné que celui-ci doit être capable d’analyser vos données.

Exemple de code :

Le code python ci-dessous permet d’encrypter et de décrypter des données suivant le modèle exposé ci-dessus:

def sigfoxEncrypt(data,key):
    encryptedData = "";
    binData = bin(int(data,16))
    binKey  = bin(int(key,16))
    intKey  = int(key,16)
    intData = int(data,16)
    encodedData =  bin(intData ^ intKey)
    strEncodedData =  str(encodedData)
    startPoint = len(encodedData) - len(binData) + 2
    dataLength     = len(binData) + startPoint
    encryptedData = strEncodedData[startPoint:dataLength] #il est important de ne garder que le nombre de bits encryptés équivalent au nombre de bits des données avant encryption.
    return hex(int(encryptedData,2))


def sigfoxDecrypt(data,key,dataLength):
    keyLength = len(bin(int(key,16)))
    decryptedData = "";
    binData = bin(int(data,16))
    binKey  = bin(int(key,16))
    intKey  = int(key,16)
    intData = int(data,16)
    encodedData =  bin(intData ^ intKey)
    strEncodedData =  str(encodedData)
    startPoint = keyLength - len(binData)
    decryptedData = strEncodedData[startPoint+2:]
    return hex(int(decryptedData,2))


data="1A2B3C"
key="ABCDEF1234567890ABCDEF12"

print "clear data:     " + data
print "key:            " + key

dataLength = (len(bin(int(data,16))))-2

encoded = sigfoxEncrypt(data,key)
print "encoded data:   " + encoded
decoded = sigfoxDecrypt(encoded,key,dataLength)
print "decoded data:   " + decoded</pre>

Le code arduino (en C) ci-dessous montre comment encrypter les données suivant le modèle expliqué et en reprenant l’exemple ci-dessus.

void setup() {

Serial.begin(9600);

unsigned long data = 0x1A2B3C;
unsigned long key = 0xABCDEF1234567890ABCDEF12;

unsigned long encodedData = data ^ key;

int lenBinData = String(data,BIN).length();
int lenBinKey = String(key,BIN).length();
String binEncodedData = String(encodedData,BIN);
binEncodedData = binEncodedData.substring(lenBinKey-lenBinData); // la variable binEncodedData est la variable contenant les données encryptées
Serial.println(binEncodedData);

}

void loop() {

}

Le code ci-dessus est fourni sans aucune garantie ni assurance. Vous devez l’adapter suivant votre cas d’usage et vos besoins.