2010-07-01 21 views
7

Estoy escribiendo un fragmento de código para cifrar un texto utilizando cifrado simétrico. Pero no regresa con el resultado correcto ...Problema de PyCrypto con AES + CTR

from Crypto.Cipher import AES 
import os 

crypto = AES.new(os.urandom(32), AES.MODE_CTR, counter = lambda : os.urandom(16)) 
encrypted = crypto.encrypt("aaaaaaaaaaaaaaaa") 
print crypto.decrypt(encrypted) 

Aquí, el texto descifrado es diferente del original.

Realmente no entiendo mucho acerca de la criptografía, así que por favor tengan paciencia conmigo. Entiendo que el modo CTR requiere una función "contador" para suministrar un contador aleatorio cada vez, pero ¿por qué necesita que sea de 16 bytes cuando mi clave es de 32 bytes e insiste en que mi mensaje también está en múltiplos de 16 bytes? ¿Esto es normal?

Supongo que no volverá al mensaje original porque el contador cambió entre cifrar y descifrar. Pero entonces, ¿cómo se supone que funciona teóricamente de todos modos? ¿Qué estoy haciendo mal? De todos modos, me veo obligado a recurrir al BCE hasta que resuelva esto :(

Respuesta

10

El counter debe devolver la misma en el descifrado como lo hizo en el cifrado, como se intuye, por lo que, una (NO segura en todo) manera de hacerlo es:

>>> secret = os.urandom(16) 
>>> crypto = AES.new(os.urandom(32), AES.MODE_CTR, counter=lambda: secret) 
>>> encrypted = crypto.encrypt("aaaaaaaaaaaaaaaa") 
>>> print crypto.decrypt(encrypted) 
aaaaaaaaaaaaaaaa 

CTR es un bloquea el cifrado, por lo que la restricción "16 a la vez" que parece sorprenderte es bastante natural.

Por supuesto, un llamado "contador" devuelve mismo valor en cada llamada is grossly insecure. No se necesita mucho para hacerlo mejor, por ejemplo ....:

import array 

class Secret(object): 
    def __init__(self, secret=None): 
    if secret is None: secret = os.urandom(16) 
    self.secret = secret 
    self.reset() 
    def counter(self): 
    for i, c in enumerate(self.current): 
     self.current[i] = c + 1 
     if self.current: break 
    return self.current.tostring() 
    def reset(self): 
    self.current = array.array('B', self.secret) 

secret = Secret() 
crypto = AES.new(os.urandom(32), AES.MODE_CTR, counter=secret.counter) 
encrypted = crypto.encrypt(16*'a' + 16*'b' + 16*'c') 
secret.reset() 
print crypto.decrypt(encrypted) 
+0

impresionante. Ok, lo entiendo. ¿Entonces, esencialmente, CTR no tiene ninguna ventaja sobre ECB si solo quiero encriptar una o muy pocas cosas? Solo quiero almacenar algunas contraseñas en todas las sesiones. ¿Necesito AES o debería usar algo más simple? – xster

+3

En realidad, CTR puede encriptar cualquier cantidad arbitraria de texto; convierte un cifrado de bloque en un generador de flujo de clave. No existe una razón real para la restricción de que la entrada sea múltiplos de un tamaño de bloque en este caso. –

+0

PyCrypto parece dar un error cuando la entrada no es un múltiplo de 16 bytes, sin embargo, – xster

1

El vector de inicialización ("contador") debe permanecer igual, al igual que la clave, entre el cifrado y el descifrado. para que pueda codificar el mismo texto un millón de veces, y obtener texto cifrado diferente cada vez (evitando algunos ataques conocidos de texto plano y coincidencias/ataques de patrones). Todavía necesita usar el mismo IV al descifrarlo que al encriptar. Generalmente cuando comienza a descifrar una corriente, que inicializar el IV del mismo valor con el que comenzó cuando comenzó la encriptación de esa corriente.

Ver http://en.wikipedia.org/wiki/Initialization_vector para obtener información sobre los vectores de inicialización.

Tenga en cuenta que os.urandom (16) no es "determinista", que es un requisito para las funciones de contador. Sugiero que use la función de incremento, ya que así es como está diseñado el modo CTR. El valor del contador inicial debe ser aleatorio, pero los valores sucesivos deben ser totalmente predecibles a partir del valor inicial (determinista). El valor inicial puede incluso ser atendido por usted (no sé los detalles)

Acerca de la clave, IV y tamaños de entrada, parece que el cifrado que eligió tiene un tamaño de bloque de 16 bytes. Todo lo que describes se ajusta a eso y me parece normal.

1

why does it need it to be 16 bytes when my key is 32 bytes

Tiene que ser la misma longitud que el tamaño de bloque de la cifra. El modo CTR solo encripta el contador y XORs el texto plano con el bloque de contador encriptado.

Notas:

  1. el valor del contador debe ser único - si se utiliza siempre el mismo valor del contador para cifrar dos textos planos diferentes bajo la misma clave, que acaba de dar su clave de distancia.
  2. como IV, el contador NO es secreto, simplemente envíelo junto con el texto cifrado. Si haces el código más complicado tratando de mantenerlo en secreto, probablemente te pegues un tiro en el pie.
  3. el valor del contador no tiene que ser impredecible, comenzar con cero y agregar uno para cada bloque está perfectamente bien. Pero tenga en cuenta que si encripta varios mensajes, necesita realizar un seguimiento de los valores del contador que ya se han consumido, es decir, necesita realizar un seguimiento de cuántos bloques ya se han cifrado con esa clave (y no puede usar el mismo clave en diferentes instancias de su programa o en diferentes máquinas).
  4. el texto sin formato puede tener cualquier longitud: el modo CTR convierte un cifrado de bloque en un cifrado de flujo.

Descargo de responsabilidad: Crypto is hard. Si no entiende lo que está haciendo, se equivocará.

I just want to store some passwords across sessions.

Usar scrypt. scrypt incluye encrypt y decrypt que usan AES-CTR con una clave derivada de contraseña.

$ pip install scrypt 

$ python 
>>> import scrypt 
>>> import getpass 
>>> pw = getpass.getpass("enter password:") 
enter password: 
>>> encrypted = scrypt.encrypt("Guido is a space alien.",pw) 
>>> out = scrypt.decrypt(encrypted,pw) 
>>> out 
'Guido is a space alien.' 
0

Puede que sea definitivamente tarde y que puede haber pasado por alto las respuestas anteriores, pero no encontré una declaración clara de cómo se debe (al menos en mi humilde opinión) se hará de acuerdo con los paquetes pycrypto.

El paquete Crypto.Util.Counter proporciona contadores de estado invocables, que son muy útiles, pero al menos fue fácil para mí usarlos de forma incorrecta.

Tienes que crear un contador, con p. Ej. ctr = Counter.new('parameters here'). Cada vez que su contador llama al contador para cifrar el mensaje, se incrementa. Esto es necesario para buenas prácticas de criptografía, de lo contrario la información sobre bloques iguales puede filtrarse del texto cifrado.

Ahora no puede llamar a la función de descifrado en el mismo objeto de cifrado, ya que volvería a llamar al mismo contador, que mientras tanto se ha incrementado, posiblemente varias veces. Lo que debe hacer es crear un nuevo objeto de cifrado con un contador diferente inicializado con los mismos parámetros. De esta forma, el descifrado funciona correctamente, comenzando el contador desde el mismo punto en que se realizó el cifrado.

Ejemplo de trabajo a continuación:

# Import modules 
from Crypto.Cipher import AES 
from Crypto import Random 
from Crypto.Util import Counter 


# Pad for short keys 
pad = '# constant pad for short keys ##' 

# Generate a random initialization vector, to be used by both encryptor and decryptor 
# This may be sent in clear in a real communication 

random_generator = Random.new() 
IV = random_generator.read(8) 


# Encryption steps 

# Ask user for input and pad or truncate to a 32 bytes (256 bits) key 
prompt = 'Input your key. It will padded or truncated at 32 bytes (256 bits).\n-: ' 
user_keye = raw_input(prompt) 
keye = (user_keye + pad)[:32] 

# Create counter for encryptor 
ctr_e = Counter.new(64, prefix=IV) 

# Create encryptor, ask for plaintext to encrypt, then encrypt and print ciphertext 
encryptor = AES.new(keye, AES.MODE_CTR, counter=ctr_e) 
plaintext = raw_input('Enter message to cipher: ') 
ciphertext = encryptor.encrypt(plaintext) 
print ciphertext 
print 


# Decryption steps 

# Ask user for key: it must be equal to that used for encryption 
prompt = 'Input your key. It will padded or truncated at 32 bytes (256 bits).\n-: ' 
user_keyd = raw_input(prompt) 
keyd = (user_keyd + pad)[:32] 

# Create counter for decryptor: it is equal to the encryptor, but restarts from the beginning 

ctr_d = Counter.new(64, prefix=IV) 

# Create decryptor, then decrypt and print decoded text 
decryptor = AES.new(keyd, AES.MODE_CTR, counter=ctr_d) 
decoded_text = decryptor.decrypt(ciphertext) 
print decoded_text 
3

AES es un block cipher: es un algoritmo (más precisamente, un par de algoritmos) que toma una clave y un bloque de mensaje y, o bien encripta o desencripta el bloque. El tamaño de un bloque siempre es de 16 bytes, independientemente del tamaño de la clave.

CTR es un mode of operation. Es un par de algoritmos que se basa en un cifrado de bloques para producir un cifrado de flujo, que puede cifrar y descifrar mensajes de longitudes arbitrarias.

CTR funciona combinando sucesivos bloques de mensajes con el cifrado de valores sucesivos de un contador. El tamaño del contador debe ser de un bloque para que sea una entrada válida para el cifrado de bloque.

  • Funcionalmente, no importa cuáles sean los valores sucesivos del contador, siempre que el lado de cifrado y descifrado use la misma secuencia.Por lo general, el contador se trata como un número de 256 bits y se incrementa para cada bloque sucesivo, con un valor inicial elegido al azar. Por lo tanto, generalmente, el método de incremento se procesa en el código, pero el lado de descifrado necesita saber cuál es el valor inicial, por lo que el lado de cifrado envía o almacena el valor del contador inicial al comienzo del mensaje cifrado.
  • Por razones de seguridad, es vital que nunca repita el mismo valor de contador con una clave dada. Entonces, para una clave de un solo uso, está bien comenzar con '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'. Pero si la clave se usa varias veces, no se permite que el segundo mensaje reutilice ninguno de los valores del contador utilizados por el primer mensaje, y la manera más fácil de garantizar que es generar el valor del contador inicial al azar (con un 2^128 espacio, las posibilidades de una colisión son aceptablemente insignificantes).

Al permitir que la persona que llama elija una función de contador, la biblioteca PyCrypto le ofrece una gran cantidad de sogas para ahorcarse. Debe usar Crypto.Util.Counter, no solo para "un mejor rendimiento" como lo indica la documentación, sino porque es más fácil construir algo seguro que lo que probablemente se le ocurra. Y aún así, tenga cuidado de usar un valor inicial aleatorio, que no es el predeterminado.

import binascii 
import os 
from Crypto.Cipher import AES 
from Crypto.Util import Counter 
def int_of_string(s): 
    return int(binascii.hexlify(iv), 16) 
def encrypt_message(key, plaintext): 
    iv = os.urandom(16) 
    ctr = Counter.new(128, initial_value=int_of_string(iv)) 
    aes = AES.new(key, AES.MODE_CTR, counter=ctr) 
    return iv + aes.encrypt(plaintext) 
def decrypt_message(key, ciphertext): 
    iv = ciphertext[:16] 
    ctr = Counter.new(128, initial_value=int_of_string(iv)) 
    aes = AES.new(key, AES.MODE_CTR, counter=ctr) 
    return aes.decrypt(ciphertext[16:]) 
Cuestiones relacionadas