php cryptography aes lockbox-3 cbc-mode

PHP implementa Ciphertext Stealing(CTS) con CBC



cryptography aes (2)

He intentado implementar Ciphertext Stealing (CTS) en PHP para CBC.

Refiriéndose a continuación dos enlaces

¿Cómo puedo encriptar / desencriptar datos usando el modo AES CBC + CTS (robo de texto cifrado) en PHP?

y

http://en.wikipedia.org/wiki/Ciphertext_stealing

Estoy confundido y atascado en el último y más simple paso de XOR. Sé que esto es una tontería, pero habiendo probado todas las combinaciones, no sé qué me estoy perdiendo. El código sigue.

// 1. Decrypt the second to last ciphertext block, using zeros as IV. $second_to_last_cipher_block = substr($cipher_text, strlen($cipher_text) - 32, 16); $second_to_last_plain = @mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $second_to_last_cipher_block, MCRYPT_MODE_CBC); // 2. Pad the ciphertext to the nearest multiple of the block size using the last B-M // bits of block cipher decryption of the second-to-last ciphertext block. $n = 16 - (strlen($cipher_text) % 16); $cipher_text .= substr($second_to_last_plain, -$n); // 3. Swap the last two ciphertext blocks. $cipher_block_last = substr($cipher_text, -16); $cipher_block_second_last = substr($cipher_text, -32, 16); $cipher_text = substr($cipher_text, 0, -32) . $cipher_block_last . $cipher_block_second_last; // 4. Decrypt the ciphertext using the standard CBC mode up to the last block. $cipher = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '''', MCRYPT_MODE_CBC, ''''); mcrypt_generic_init($cipher, $key, $iv); $plain_text = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $cipher_text, MCRYPT_MODE_CBC , $iv); // 5. Exclusive-OR the last ciphertext (was already decrypted in step 1) with the second last ciphertext. // ??? // echo $??? ^ $???;


Encuentro que los casos de uso concreto son muy útiles para comprender los algoritmos. Aquí hay 2 casos de uso y un recorrido paso a paso.

Punto de inicio para ambos casos de uso.

Estos casos de uso asumen que usted está descifrando mensajes. Utiliza AES-256 con modo de encadenamiento CBC y robo de texto cifrado para la cuantificación de bloques. Para generar estos casos de uso, utilicé el compilador Delphi 2010 y la biblioteca TurboPower LockBox3 (revisión SVN 243). En lo que sigue, uso una notación como tal ...

IV := [16] 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

... para significar que una variable llamada ''IV'' se asigna para ser igual a una matriz de 16 bytes. El byte más a la izquierda es la representación del byte Menos significativo (dirección más baja) de la matriz y el byte más a la derecha, el más significativo. Estos bytes están escritos en hexadecimal, por lo que, por ejemplo, si uno pone ...

X := [2] 03 10

... significa que el LSB es 3 y el MSB es 16.

Utilice el caso uno

  1. Deje que la clave AES-256 de 32 bytes comprimida (como se define en el estándar AES) sea ...

    key = [32] 0D EE 8F 9F 8B 0B D4 A1 17 59 FA 05 FA 2B 65 4F 23 00 29 26 0D EE 8F 9F 8B 0B D4 A1 17 59 FA 05

    Con TurboPower LockBox 3, esto se puede lograr estableciendo la propiedad de contraseña (''UTF8Password'') del componente TCodec para ...

    password = (UTF-8) ''Your lips are smoother than vasoline.''

  2. El mensaje de texto claro que se enviará será

    Message = (UTF-8) ''Leeeeeeeeeroy Jenkins!''

    Codificado esto es 22 bytes de largo. AES-256 tiene un tamaño de bloque de 16 bytes, por lo que se encuentra en algún lugar entre 1 y 2 bloques de longitud.

  3. Deje que el IV sea 1. (Aparte: Del lado Delphi, esto se puede lograr estableciendo

    TRandomStream.Instance.Seed := 1;

    justo antes del cifrado). Por lo tanto, el mensaje de texto cifrado que se descifrará por PHP será (con 8 byte IV antepuesto a la LockBox3) ...

    ciphertext = [30] 01 00 00 00 00 00 00 00 17 5C C0 97 FF EF 63 5A 88 83 6C 00 62 BF 87 E5 1D 66 DB 97 2E 2C (base64 equivalent =''AQAAAAAAAAAXXMCX/+9jWoiDbABiv4flHWbbly4s'')

    Descomponiendo esto en IV, primer bloque de texto cifrado (c [0]) y último (parcial) bloque de texto cifrado (c [1]) ...

    IV = [16] 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 c[0] = [16] 17 5C C0 97 FF EF 63 5A 88 83 6C 00 62 BF 87 E5 c[1] = [6] 1D 66 DB 97 2E 2C

  4. Ahora veamos el descifrado con el robo de texto cifrado.

    • CV: = IV

      CV = [16] 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

    • En general, el n-ésimo bloque (excepto los últimos 2 bloques), nuestro algoritmo CBC normal es ...

      m[n] := Decrypt( c[n]) XOR CV; CV[n+1] := c[n]

      dónde:

      • m es el bloque de texto claro de salida;
      • Descifrar () significa descifrado AES-256 ECB en ese bloque;
      • CV es nuestro Carry-Vector. El modo de encadenamiento define cómo cambia esto de bloque a bloque.
    • pero para el segundo último bloque (N-1) (N = 2 en el caso de uso uno), la transformación cambia a ... ( Esta excepción se realiza debido a la selección de robo de texto cifrado )

      m[n] := Decrypt( c[n]) XOR CV; CV[n+1] := CV[n] // Unchanged!

    • Aplicando a nuestro caso de uso:

      CV = [16] 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 c[0] = [16] 17 5C C0 97 FF EF 63 5A 88 83 6C 00 62 BF 87 E5 Decrypt(c[0]) = [16] 6F 6B 69 6E 73 21 F0 7B 79 F2 AF 27 B1 52 D6 0B m[0] := Decrypt(c[0]) XOR CV = [16] 6E 6B 69 6E 73 21 F0 7B 79 F2 AF 27 B1 52 D6 0B

  5. Ahora para procesar el último bloque. Es uno parcial, de 6 bytes de largo. En general, el procesamiento del último bloque es así ...

    y := c[N-1] | LastBytes( m[N-2], BlockSize-Length(c[N-1])); m[N-1] := Decrypt( y) XOR CV

    Aplicando al caso de uso uno:

    c[1] = [6] 1D 66 DB 97 2E 2C y := c[1] | LastBytes( m[0], 10) y = [16] 1D 66 DB 97 2E 2C F0 7B 79 F2 AF 27 B1 52 D6 0B Decrypt( y) = [16]= 4D 65 65 65 65 65 65 65 65 65 72 6F 79 20 4A 65 m[1] := Decrypt(y) XOR CV m[1] = [16] 4C 65 65 65 65 65 65 65 65 65 72 6F 79 20 4A 65

  6. El último paso en el proceso de descifrado es la emisión de los dos últimos bloques. Invertimos el orden, emitiendo m [N-1] primero, y luego emitimos la primera parte de m [N-2] (cuya longitud es igual a la longitud de c [N-1]). Aplicando para usar el caso uno ...

    • Emitir m [1]

      m[1] = [16] 4C 65 65 65 65 65 65 65 65 65 72 6F 79 20 4A 65

    • Emite los primeros 6 bytes de m [0]

      FirstBytes( m[0], 6) = 6E 6B 69 6E 73 21

    • Poniéndolo en conjunto, obtenemos un texto plano reconstruido de ...

      [22] 4C 65 65 65 65 65 65 65 65 65 72 6F 79 20 4A 65 6E 6B 69 6E 73 21

    que es la codificación UTF-8 de ''Leeeeeeeeeroy Jenkins!''

Utilice el caso dos

En este caso de uso, el mensaje tiene exactamente 2 bloques de longitud. Esto se llama el caso redondo. En los casos redondos, no hay un bloqueo parcial para cuantificar, por lo que procede como si fuera un CBC normal. La contraseña, la clave y IV son las mismas que en el Caso de uso uno. El mensaje de texto cifrado que se descifrará (incluido el 8 antepuesto IV) es ...

  1. Preparar

    Ciphertext = [40] 01 00 00 00 00 00 00 00 70 76 12 58 4E 38 1C E1 92 CA 34 FB 9A 37 C5 0A 75 F2 0B 46 A1 DF 56 60 D4 5C 76 4B 52 19 DA 83 which is encoded base64 as ''AQAAAAAAAABwdhJYTjgc4ZLKNPuaN8UKdfILRqHfVmDUXHZLUhnagw==''

    Esto se divide en IV, primer bloque y segundo bloque, así que ...

    IV = [16] 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 c[0] = [16] 70 76 12 58 4E 38 1C E1 92 CA 34 FB 9A 37 C5 0A c[1] = [16] 75 F2 0B 46 A1 DF 56 60 D4 5C 76 4B 52 19 DA 83

  2. General y segundo último bloque

    Decrypt(c[0]) = [16] 45 61 6E 63 65 20 74 68 65 6E 2C 20 77 68 65 72 m[0] := Decrypt(c[0]) XOR CV = [16] 44 61 6E 63 65 20 74 68 65 6E 2C 20 77 68 65 72 Next CV := c[0] = [16] 70 76 12 58 4E 38 1C E1 92 CA 34 FB 9A 37 C5 0A

  3. Último bloque:

    Nuestro último bloque es redondo en este caso de uso.

    Decrypt(c[1]) = [16] 75 F2 0B 46 A1 DF 56 60 D4 5C 76 4B 52 19 DA 83 m[1] := Decrypt(c[1]) XOR CV = [16] 65 65 76 65 72 20 79 6F 75 20 6D 61 79 20 62 65

  4. El último paso en el proceso de descifrado es la emisión de los dos últimos bloques. En el caso redondo, no invertimos el orden. Emitimos m [N-2] primero, y luego m [N-1]. Aplicando al caso de uso dos ...

    • Emitir m [0]

      m[0] = [16] 44 61 6E 63 65 20 74 68 65 6E 2C 20 77 68 65 72

    • Emite todo m 1

      m[1] = [16] 65 65 76 65 72 20 79 6F 75 20 6D 61 79 20 62 65

    • Poniéndolo en conjunto, obtenemos un texto plano reconstruido de ...

      [32] 44 61 6E 63 65 20 74 68 65 6E 2C 20 77 68 65 72 65 65 76 65 72 20 79 6F 75 20 6D 61 79 20 62 65

    que es la codificación UTF-8 de ''Dance entonces, donde quiera que estés''

  5. Casos de borde a considerar. Hay dos casos de borde, no ilustrados por los dos casos de uso proporcionados aquí.

    • Mensajes cortos Un mensaje corto es un mensaje, cuya longitud en bytes es:

      • No es cero y
      • Menos de un bloque;
    • Mensajes de longitud cero.

En el caso de mensajes cortos, técnicamente uno podría implementar el robo de texto cifrado utilizando el IV como el bloque anterior de texto cifrado. Sin embargo, en mi humilde opinión, este uso del robo de texto cifrado, de esta manera, no se justifica por la falta de investigación sobre el impacto en la fuerza criptográfica, por no mencionar la complejidad de implementación añadida. En TurboPower LockBox 3, cuando el mensaje es un mensaje corto, y el modo de encadenamiento no es uno de transmisión de claves, entonces el modo de encadenamiento se trata como CFB-8bit. CFB-8 bit es un modo de transmisión de claves.

En el caso de los mensajes de longitud cero, es realmente simple. El mensaje de texto claro de longitud cero mapea mensajes de texto cifrado de uno a uno a cero. No se necesita IV, generado ni agregado. Este mapeo es independiente del modo de encadenamiento y cifrado (en el caso de los cifradores de modo de bloque).

Notas sobre la implementación de PHP

Advertencia

No soy un programador de PHP. No sé PHP. Cualquier cosa que digo aquí debe tomarse con un grano de sal.

Matrices de bytes

Parece que estás usando cadenas de PHP para almacenar matrices de bytes. Esto me parece peligroso. ¿Qué pasa si uno de los valores de bytes es cero? ¿Eso acortaría la cadena? ¿Cómo se comportaría strlen () en ese caso? Si PHP tiene un tipo de datos nativo que era una matriz de bytes, entonces probablemente sería más seguro. Pero realmente no lo sé Solo estoy señalando este punto a su atención, si aún no lo saben. Posiblemente, no es realmente un problema.

biblioteca mcrypt_decrypt

No estoy familiarizado con esta biblioteca. ¿Admite de forma nativa el robo de texto cifrado? Supongo que no. Entonces hay dos estrategias posibles para ti.

  1. Llame al descifrado de la biblioteca para todos los bloques excepto los dos últimos con modo CBC. Procese los dos últimos bloques como te he descrito. Pero esto requiere acceso al CV. ¿La API expone esto? Si no, esta estrategia no es una opción viable para usted.

  2. Llame al descifrado de la biblioteca para todos los bloques menos los dos últimos con modo ECB y despliegue su encadenamiento de CBC. Bastante fácil de implementar y, como definición, tienes acceso al CV.

Cómo hacer XOR en PHP

Alguien más publicó una respuesta a esta pregunta, pero actualmente la ha retirado. Pero él tenía razón. Parece hacer un XOR en PHP en una matriz de bytes, recorrer los caracteres, uno por uno, y hacer un byte de nivel XOR. La técnica se muestra aquí .


Estaba buscando una respuesta similar para Perl. Las bibliotecas de Perl estaban limitadas al modo CBC. Así es como hice que CTS funcionara usando el modo AES 256 CBC y el método CTS 3. Pensé que esto también podría ser útil para PHP.

Aquí está la documentación real de NIST. Id. De documento: NIST800-38A CBC-CS3 Título: Recomendación para los modos de operación de cifrado en bloque; Tres variantes del robo de texto cifrado para el modo CBC Fuente: http://csrc.nist.gov/publications/nistpubs/800-38a/addendum-to-nist_sp800-38A.pdf

Aquí está el código ...

use Crypt::CBC; use Crypt::Cipher::AES; my $key = pack("H*","0000000000000000000000000000000000000000000000000000000000000000"); my $iv = pack("H*","00000000000000000000000000000000"); my $pt = pack("H*","0000000000000000000000000000000000"); my $ct = aes256_cbc_cts_decrypt( $key, $iv, $pt ); #AES 256 CBC with CTS sub aes256_cbc_cts_decrypt { my ($key, $iv, $in) = @_; my $len_in_bytes = length(unpack("H*", $in)) / 2; my $in_idx = 0; my $null_iv = pack( "H32", "00000000000000000000000000000000"); my $cipher = Crypt::CBC->new( -key => $key, -iv => $null_iv, -literal_key => ''1'', -keysize => 32, -blocksize => 16, -header => ''none'', -cipher => ''Crypt::Cipher::AES''); my $out; while ( $len_in_bytes >= 16 ) { my $tmp = substr($in, $in_idx, 16); my $outblock = $cipher->decrypt($tmp); if ( ( ($len_in_bytes % 16) eq 0 ) || ( $len_in_bytes > 32 ) ) { $outblock = $outblock ^ $iv; $iv = $tmp; } $out .= $outblock; $in_idx += 16; $len_in_bytes -= 16; } if ($len_in_bytes) { my $tmp = substr($in,$in_idx,$len_in_bytes); my $out_idx = $in_idx - 16; $tmp .= substr($out,$out_idx + $len_in_bytes, 16 - $len_in_bytes); $out .= substr($out, $out_idx, $len_in_bytes) ^ substr($tmp, 0, $len_in_bytes); substr($out,$out_idx,16) = $iv ^ $cipher->decrypt($tmp); } return $out; }