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é.
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
Récent
- Un commutateur de base de données avec HAProxy et HAProxy Runtime API
- Docker Swarm rolling updates
- Masquer les clés primaires de la base de données UUID de votre application web
- Don't Repeat Yourself (DRY) avec Jinja2
- SQLAlchemy, PostgreSQL, nombre maximal de lignes par user
- Afficher les valeurs des filtres dynamiques SQLAlchemy
Les plus consultés
- Utilisation des Python's pyOpenSSL pour vérifier les certificats SSL téléchargés d'un hôte
- Utiliser PyInstaller et Cython pour créer un exécutable Python
- Réduire les temps de réponse d'un Flask SQLAlchemy site web
- Connexion à un service sur un hôte Docker à partir d'un conteneur Docker
- SQLAlchemy : Utilisation de Cascade Deletes pour supprimer des objets connexes
- Utiliser UUIDs au lieu de Integer Autoincrement Primary Keys avec SQLAlchemy et MariaDb