Volver al inicio

Cripto Python para USB: SecureBytes y cifrado streaming

El artículo describe un motor cripto para USB en Python con la clase SecureBytes para almacenamiento seguro de claves, cifrado streaming de archivos grandes, y mecanismos de tolerancia a fallos. Soporte para AES-GCM, ChaCha20. Enfoque en eliminar vulnerabilidades de memoria y problemas de fallos de energía.

Creando un motor cripto para unidades flash: Python sin fugas de memoria
Advertisement 728x90

Cifrado USB Seguro en Python: Gestión de Memoria y Procesamiento por Flujos

Para transferir archivos con seguridad mediante USB, la simplicidad es clave: inserta la unidad, ingresa una contraseña y accede a los datos cifrados. VeraCrypt utiliza archivos contenedores, lo que complica el manejo directo de archivos entre distintos sistemas operativos. El principal desafío? Proteger contra la corrupción de datos durante cortes de energía inesperados cuando se extrae la memoria flash durante el cifrado.

Un motor criptográfico construido en Python aprovecha AES-GCM y ChaCha20. El enfoque está en almacenar claves de forma segura en memoria, cifrar archivos de gigabytes sin agotar la RAM y mecanismos robustos de integridad de datos.

La clase SecureBytes elimina una vulnerabilidad crítica en Python y Java: la recolección de basura (GC) copia datos sensibles (contraseñas, claves) a nuevas ubicaciones de memoria, dejando residuos en RAM. Así funciona:

Google AdInline article slot
class SecureBytes:
    def __init__(self, data: Union[bytes, bytearray, int]):
        if isinstance(data, int):
            self._buffer = bytearray(data)
        else:
            self._buffer = bytearray(data)
        self._finalized = False
        # Registrar un finalizador débil
        self._weak_ref = weakref.ref(self, self._cleanup_callback)

    def wipe(self, passes: int = 3):
        if self._finalized or len(self._buffer) == 0:
            return
        # Paso 1: bytes aleatorios
        self._buffer[:] = secrets.token_bytes(len(self._buffer))
        # Paso 2: ceros
        self._buffer[:] = b'\x00' * len(self._buffer)
        self._finalized = True
        gc.collect()

    def __del__(self):
        if not self._finalized:
            self.wipe()

Características clave:

  • Usa bytearray para sobrescribir in situ, a diferencia de bytes inmutables.
  • Limpieza múltiple: datos aleatorios seguidos de ceros (según NIST SP 800-88).
  • Diseñado para usarse con manejadores de contexto (with secure_key(...)) para garantizar limpieza incluso ante excepciones.

Esto minimiza la exposición de claves en memoria y evita fugas por GC.

Cifrado por Flujo: MemorySensitiveReader

Cifrar un archivo de 10 GB en una máquina con solo 4 GB de RAM requiere un enfoque por flujos. La clase MemorySensitiveReader cambia automáticamente de modo según el tamaño del archivo y la memoria disponible:

Google AdInline article slot
class MemorySensitiveReader:
    def __init__(self, file_path: str, memory_threshold: int = 100 * 1024 * 1024):
        self.file_size = os.path.getsize(file_path)
        # Umbral para activar el modo por flujo
        self.use_streaming = self.file_size > memory_threshold 

    def iter_chunks(self, chunk_size: int = 8192):
        # Leer y cifrar por fragmentos
        ...

Solución al problema del nonce: Reutilizar el mismo nonce con una misma clave en AES-GCM o ChaCha20 es un fallo catastrófico. Para evitarlo, cada bloque tiene un nonce único derivado de un nonce base y el índice del bloque:

def _derive_block_nonce_12bit(base_nonce: bytes, block_index: int) -> bytes:
    # Primeros 8 bytes = prefijo, últimos 4 = contador de bloque
    prefix = base_nonce[:8]
    block_counter = block_index.to_bytes(4, byteorder='big')
    return prefix + block_counter

Los bloques se leen en fragmentos de 8 KB, con nonces generados a partir del valor base y el índice, asegurando solidez criptográfica sin importar el tamaño del archivo.

Tolerancia a Fallos en las Operaciones

El cifrado tradicional que elimina el archivo original tras un error provoca pérdida permanente de datos. Este sistema incluye:

Google AdInline article slot
  • Archivo de bloqueo .encryption_lock.json: Rastrea el estado como in_progress y lista los archivos procesados.
  • Archivos temporales .tmp: Cifra en una copia; elimina el original solo tras verificación exitosa.
  • Verificaciones de integridad: Descifra un bloque, compara HMAC y hashes SHA-256.
  • Mecanismo de reversión: En caso de interrupción, restaura automáticamente los originales desde los archivos cifrados.

Esto garantiza que tras un corte de energía, los datos estén completamente cifrados o totalmente sin tocar —sin estados parciales.

Algoritmos Soportados

  • AES-256-GCM: Aprovecha aceleración de hardware mediante AES-NI.
  • ChaCha20-Poly1305: Rendimiento óptimo en dispositivos ARM sin AES-NI.
  • XChaCha20-Poly1305: Nonce de 24 bytes ideal para cifrados a gran escala.

La paralelización con ThreadPoolExecutor mejora el rendimiento I/O para muchos archivos pequeños, superando la limitación del GIL.

Lo más importante:

  • SecureBytes evita fugas de claves por GC mediante limpieza múltiple.
  • Cifrado por flujo con nonces únicos para archivos mayores a 100 MB —riesgo cero de colisiones.
  • Tolerancia total a fallos: archivos de bloqueo y reversión automática tras corte de energía.
  • Soporte para AES-GCM, ChaCha20, XChaCha20 —sin comprometer seguridad.
  • Metadatos guardados en .usb_crypt_meta.json, contraseñas ≥12 caracteres con validación.

Limitaciones y Modelo de Amenazas

La herramienta protege contra unidades perdidas o robadas, pero no oculta nombres de archivos ni estructura de directorios. No ofrece protección contra keyloggers en la máquina anfitriona. Ideal para desarrolladores intermedios a senior que necesitan cifrado USB personalizado con énfasis fuerte en seguridad de memoria y resiliencia.

— Editorial Team

Advertisement 728x90

Leer después