angle-uparrow-clockwisearrow-counterclockwisearrow-down-uparrow-leftatcalendarcard-listchatcheckenvelopefolderhouseinfo-circlepencilpeoplepersonperson-fillperson-plusphoneplusquestion-circlesearchtagtrashx

Transfert de données sécurisé grâce au cryptage à Public Key et à pyNaCl

pyNaCl facilite la mise en œuvre d'un transfert de données sécurisé.

2 décembre 2023 Mise à jour 2 décembre 2023
post main image
https://www.pexels.com/@reezky11

Il s'agit d'un court billet sur le transfert de données en toute sécurité entre deux personnes. Pour cela, nous utilisons le paquet Python pyNaCl pour générer des clés privées et publiques et pour crypter et décrypter les données. J'ai également ajouté le paquet Python keyring pour stocker les private_key et public_key. Ce n'est pas vraiment difficile. J'ai eu besoin d'une classe de base pour faire cela et je la partage ici. Peut-être la trouverez-vous utile.

Comme toujours, je développe sur Ubuntu 22.04.

Comment cela fonctionne-t-il ?

Il y a deux personnes, Bob et Alice. Bob souhaite envoyer des données à Alice via l'internet de manière sécurisée. Nous utilisons ici un système de cryptage à clé publique.

Bob génère une private_key et une public_key uniques. Il garde le private_key secret et partage le public_key avec Alice. Alice suit la même procédure, garde son private_key secret et partage son public_key avec Bob.

Pour envoyer les données à Alice, Bob procède comme suit :

  • Bob chiffre les données avec le code public_key de Alice.
  • Bob signe les données avec sa propre private_key.

Lorsque Alice reçoit les données cryptées, elle utilise son propre private_key pour décrypter les données et utilise le public_key de Bob pour vérifier que les données proviennent bien de Bob.

Stockage des private_key et public_key

La plupart des systèmes disposent d'un endroit où les mots de passe sont stockés en toute sécurité, appelé service keyring du système. Le paquet Python keyring peut être utilisé pour interfacer avec le service keyring du système ou pour utiliser une solution de stockage autonome.

Pour chaque user de notre application, nous stockons le public_key et (facultatif) le private_key dans le champ du mot de passe, en utilisant un dictionnaire qui est converti de et vers le JSON.

Le code

Créez un virtual environment et installez ce qui suit :

> pip install pynacl
> pip install keyring

Vous pouvez choisir l'encodage Base64 ou Hex pour les clés et les données. Base64 est beaucoup plus efficace. N'oubliez pas de supprimer les clés lorsque vous passez à un autre encodage !

# my_app.py
import json
import os
import sys
import traceback 

import keyring as kr

from nacl.utils import random
from nacl.public import Box, PrivateKey, PublicKey
from nacl.encoding import Base64Encoder, HexEncoder

class PKUtils:
    def __init__(
        self,
        key_encoding='hex',
        data_encoding='hex',
        kr_servicename=None,
    ):
        self.kr_servicename = kr_servicename
        self.key_encoder = Base64Encoder if key_encoding == 'base64' else HexEncoder
        self.data_encoder = Base64Encoder if data_encoding == 'base64' else HexEncoder

    def get_publ_key_from_priv_key(self, priv_key):
        priv_key_obj = PrivateKey(priv_key, encoder=self.key_encoder)
        publ_key_obj = priv_key_obj.public_key
        publ_key_encoded = publ_key_obj.encode(self.key_encoder)
        return publ_key_encoded.decode('ascii')

    def create_key_pair(self):
        priv_key_obj = PrivateKey.generate()
        priv_key_encoded = priv_key_obj.encode(self.key_encoder)
        priv_key = priv_key_encoded.decode('ascii')
        publ_key = self.get_publ_key_from_priv_key(priv_key)
        return priv_key, publ_key

    def get_box(self, sender_priv_key, receiver_publ_key):
        return Box(
            PrivateKey(sender_priv_key, encoder=self.key_encoder),
            PublicKey(receiver_publ_key, encoder=self.key_encoder)
        )

    def encrypt_data(self, sender_priv_key, receiver_publ_key, data):
        if isinstance(data, str):
            data = bytes(data, 'utf-8')
        box = self.get_box(sender_priv_key, receiver_publ_key)
        nonce = random(Box.NONCE_SIZE)
        encrypted_data = box.encrypt(data, nonce, encoder=self.data_encoder)
        return encrypted_data

    def decrypt_data(self, receiver_priv_key, sender_publ_key, encrypted_data, decode=None):
        box = self.get_box(receiver_priv_key, sender_publ_key)
        data = box.decrypt(encrypted_data, encoder=self.data_encoder)
        if decode is not None:
            data = data.decode('utf-8')
        return data

    def delete_key_pair_from_kr(self, username):
        try:
            kr.delete_password(self.kr_servicename, username)
        except:
            pass

    def get_key_pair_from_kr_or_create_new(self, username):
        try:
            keys = json.loads(kr.get_password(self.kr_servicename, username))
            return keys['priv_key'], keys['publ_key']
        except:
            pass
        priv_key, publ_key = self.create_key_pair()
        kr.set_password(
            self.kr_servicename,
            username,
            json.dumps({'priv_key': priv_key, 'publ_key': publ_key})
        )
        return priv_key, publ_key

    def set_public_key_in_kr(self, username, publ_key, keep_priv_key=False):
        priv_key_cur, publ_key_cur = self.get_key_pair_from_kr_or_create_new(username)
        if not keep_priv_key:
            priv_key_cur = None
        kr.set_password(
            self.kr_servicename,
            username,
            json.dumps({'priv_key': priv_key_cur, 'publ_key': publ_key})
        )
       

def main():
    pku = PKUtils(
        #key_encoding='base64',
        #data_encoding='base64',
        kr_servicename='my_app',
    )

    #pku.delete_key_pair_from_kr('bob')
    #pku.delete_key_pair_from_kr('alice')

    # bob gets/generates priv_key and publ_key
    bob_priv_key, bob_publ_key = pku.get_key_pair_from_kr_or_create_new('bob')
    print(f'bob_priv_key = {bob_priv_key}, bob_publ_key = {bob_publ_key}')

    # alice gets/generates priv_key and publ_key
    alice_priv_key, alice_publ_key = pku.get_key_pair_from_kr_or_create_new('alice')
    print(f'alice_priv_key = {alice_priv_key}, alice_publ_key = {alice_publ_key}')

    # bob wants to send this data
    data = """this is line 1
this is line 2
this is line 3
this is line 4
this is line 5
"""
    print(f'data = {data}')

    # bob encrypts the data
    try:
        encrypted_data = pku.encrypt_data(bob_priv_key, alice_publ_key, data)
        print(f'encrypted_data = {encrypted_data}')
    except Exception as e:
        print(f'encryption exception = {type(e).__name__}, e = {e}')
        traceback.print_exc() 

    # bob sends encrypted_data to alice ...

    # verify it is working, inject error
    #alice_priv_key = alice_publ_key
    #bob_publ_key = alice_publ_key

    # alice decrypts the received encrypted_data
    try:
        decrypted_data = pku.decrypt_data(alice_priv_key, bob_publ_key, encrypted_data, decode='utf-8')
        print(f'decrypted_data = {decrypted_data}')
    except Exception as e:
        print(f'decryption exception = {type(e).__name__}, e = {e}')
        traceback.print_exc() 

    print('ready')


if __name__ == '__main__':
    main() 

Résumé

Le package Python pyNaCl permet d'implémenter très facilement le chiffrement public_key . Le chiffrement à clé publique est faible face aux attaques de l'homme du milieu. Un tiers peut modifier les clés publiques.
N'oubliez pas non plus que les données cryptées peuvent être copiées en cours de route. Cela signifie qu'une fois qu'un tiers s'empare des clés, toutes ( !) les données qui ont été copiées par un tiers (sur une longue période) peuvent être décryptées. Pour éviter cela, vous pouvez renouveler vos clés après un certain temps et supprimer les anciennes clés de votre système !

Liens / crédits

C 431: Public-Key Encryption With Sodium (25 extra)
https://samsclass.info/141/proj/C431.htm

Public Key Encryption
https://www.geeksforgeeks.org/public-key-encryption

PyNaCl
https://pynacl.readthedocs.io/en/latest

En savoir plus...

Cryptography

Laissez un commentaire

Commentez anonymement ou connectez-vous pour commenter.

Commentaires

Laissez une réponse

Répondez de manière anonyme ou connectez-vous pour répondre.