Transferencia de datos segura con cifrado de Public Key y pyNaCl
pyNaCl facilitan enormemente la transferencia segura de datos.
Este es un breve post sobre la transferencia de datos de forma segura entre dos personas. Para ello utilizamos el paquete Python pyNaCl para generar claves privadas y públicas y para cifrar y descifrar los datos. También he añadido el paquete Python keyring para almacenar las private_key y public_key. En realidad no es tan difícil. Necesitaba una clase básica para hacer esto y aquí la comparto. Tal vez te sea útil.
Como siempre estoy desarrollando en Ubuntu 22.04.
Como funciona
Hay dos personas, Bob y Alice. Bob quiere enviar algunos datos a través de Internet a Alice de forma segura. En este caso estamos utilizando el cifrado de clave pública.
Bob genera un único private_key y public_key. Mantiene en secreto el private_key y comparte el public_key con el Alice. Alice sigue el mismo procedimiento, mantiene su private_key en secreto y comparte su public_key con Bob.
Para enviar los datos a Alice, Bob hace lo siguiente:
- Bob cifra los datos con el public_key de Alice
- Bob firma los datos con su propio private_key
Cuando Alice recibe los datos cifrados, utiliza su propio private_key para descifrarlos, y utiliza el public_key de Bob para verificar que los datos proceden efectivamente de Bob.
Almacenamiento de private_key y public_key
La mayoría de los sistemas tienen un lugar donde se almacenan las contraseñas de forma segura, llamado servicio keyring del sistema. El paquete Python keyring puede utilizarse para interactuar con el servicio keyring del sistema o utilizar una solución de almacenamiento independiente.
Para cada user de nuestra aplicación, almacenamos el public_key y (opcional) private_key en el campo de contraseña, utilizando un diccionario que se convierte a y desde JSON.
El código
Crea un virtual environment e instala lo siguiente:
> pip install pynacl
> pip install keyring
Puede elegir la codificación Base64 o Hex para las claves y los datos. Base64 es mucho más eficaz. No olvide borrar las claves cuando cambie a otra codificación.
# 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()
Resumen
El paquete Python pyNaCl hace muy fácil implementar el cifrado public_key . El cifrado de clave pública es débil frente a los ataques de tipo man in the middle. Un tercero puede modificar las claves públicas.
Además, recuerda siempre que los datos cifrados pueden copiarse por el camino. Esto significa que una vez que un tercero se hace con las claves, todos (!) los datos que hayan sido copiados por un tercero (durante un largo periodo) pueden ser descifrados. Para evitarlo, puedes renovar las claves al cabo de un tiempo y eliminar las antiguas del sistema.
Enlaces / créditos
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
Leer más
Cryptography
Recientes
- Uso de Ingress para acceder a RabbitMQ en un clúster Microk8s
- Galería de vídeo simple con Flask, Jinja, Bootstrap y JQuery
- Programación básica de trabajos con APScheduler
- Un conmutador de base de datos con HAProxy y el HAProxy Runtime API
- Docker Swarm rolling updates
- Cómo ocultar las claves primarias de la base de datos UUID de su aplicación web
Más vistos
- Usando PyInstaller y Cython para crear un ejecutable de Python
- Reducir los tiempos de respuesta de las páginas de un sitio Flask SQLAlchemy web
- Usando Python's pyOpenSSL para verificar los certificados SSL descargados de un host
- Conectarse a un servicio en un host Docker desde un contenedor Docker
- Usando UUIDs en lugar de Integer Autoincrement Primary Keys con SQLAlchemy y MariaDb
- SQLAlchemy: Uso de Cascade Deletes para eliminar objetos relacionados