miércoles, 20 de octubre de 2010

Improvisando un cifrado de flujo/generador de números aleatorios

A veces hay que hacer un pequeño sistema de cifrado para la ocasión que no tiene porque ser criptográficamente seguro, una chapuzilla vamos, veremos como improvisar uno.


La idea de un cifrado de flujo es hacer un generador de números pseudo-aleatorios, que funcione en base a una semilla (la contraseña), y utilizar los números generados para cifrar/descifrar el mensaje, en este caso utilizaremos la operación XOR con cada byte generado y cada byte del mensaje, lo que se llama un cifrado Vernam, de una forma similar a como hace ARC4

Una vez decidido esto, ya podemos comenzar con el código

class sample_cipher:

def cipherStream(self, stream):
s = "" # Se parte de un flujo vacio
for i in stream: # Cada byte del flujo original
s += chr(ord(i) ^ self.nextByte()) # Se le hace XOR con el aleatorio generado
# Y se anhade al flujo cifrado
return s # El flujo cifrado se devuelve

def nextByte(self):
if (len(self.buffer) < 1 ): # Si ya no hay elementos en el buffer
self.fillBuffer() # Se rellena
return self.buffer.pop(0) # Se extrae y devuelve el primer elemento


Como se puede ver, el cifrado en si es simple, pero hay que añadirle una fuente de aleatoriedad controlable que llenará el buffer, yo por ejemplo he utilizado sha256 y sha512.
Importante: Al parecer las funciones hash (como SHA, el utilizado) no son una buena fuente de aleatoriedad, ya que su funcion es comprimir información, no expandirla, avisados estáis.
Para utilizar fácilmente las funciones sha256 y sha512 las importaremos así:

from hashlib import sha256, sha512


Aviso: antes de empezar con lo realmente escabroso repito que no soy criptografo y se más bien poco de eso, y que esto es solo un ejemplo de un sistema que se supone inseguro desde un principio.

Esta es una forma de manejar el buffer, si encuentras otra que te guste más, pues mejor :). Lo primero sería tener en cuenta el buffer al inicializar el objeto, tomando una clave de 64 bytes, 512 bits, (o haciendole un sha512 posterior a la clave) y dividir la contraseña en dos partes y volver a hashearla (con sha256, ya que serían de 32 bytes, 256 bits cada parte), después se llenará el buffer:

def __init__(self, key): # Inicializacion del objeto
self.h1 = sha256(key[ : 32 ]).digest() # Se hace sha256 de los 256 primeros bits
self.h2 = sha256(key[ 32 : ]).digest() # Se hace lo mismo con los ultimos bits
self.fillBuffer() # Y se rellena el buffer


Para rellenar el buffer se juntan los dos hash que se obtuvieron a partir de la contraseña y se pasan por un sha512. A los primeros y a los segundos 32 bytes se les pasa por un sha256 por separado y se almacenan en los hashes, substituyendo a los que se obtuvieron de la contraseña, por último se limpia el buffer y se rellena haciendo XOR de cada byte de los hashes:

def fillBuffer(self):
key = sha512(self.h1 + self.h2).digest() # Se hashean las dos cadenas juntas

self.h1 = sha256(key[ : 32 ]).digest() # Se hace sha256 de los primeros bytes
self.h2 = sha256(key[ 32 : ]).digest() # Se tambien con los ultimos

self.buffer = [] # Se limpia el buffer
for i in xrange(32): # Se rellena con el XOR de las dos cadenas de hash
self.buffer.append(ord(self.h1[i]) ^ ord(self.h2[i]))


Y ya está, aquí [sample_cipher.py] completo, si se lanza sin argumentos mostrará las instruciones, la generación de números aleatorios usa el time como semilla.

Uso: ./sample_cipher.py <clave> <nombre del archivo> <archivo de salida> | randtest
Si se utiliza randtest solo generará los números pseudo-aleatorios
Clave de 512 bits (64 caracteres)


[Referencias]
https://secure.wikimedia.org/wikipedia/es/wiki/Cifrado_XOR
https://secure.wikimedia.org/wikipedia/es/wiki/Cifrado_Vernam
https://secure.wikimedia.org/wikipedia/en/wiki/SHA2

No hay comentarios:

Publicar un comentario