Ver índice
 Cifrado con clave simétrica 

        Ocultar índice  

   Índice de contenidos
   Instalación en Windows
   Instalación en Ubuntu
   Servidores seguros
   Páginas dinámicas
   Sintaxis básica
   Operaciones
   Arrays
   Formatos de presentación
   Operadores
   Bucles
   Extraer y ord. información
   Funciones
   Ficheros externos
   Imágenes dinámicas
   Gestión de directorios
   Cookies y sesiones
   Clases y objetos
   Ficheros en formato PDF
   Bases de datos MySQL
   PHP y XML
   PDO - Bases SQLite / MySQL
   MySQL a traves de misqli
   Algo de JavaScript y AJAX


Comunicación segura

El objetivo de la comunicación segura es que la confidencialidad de información transmitida evitando también el riesgo de que pueda ser modificada o manipulada durante el proceso de intercambio. Por medio del protocolo SSLSecure Socket Layer– (una de las opciones más populares) se puede ocultar a ojos de teceros la información que fluye, de forma bidireccional, entre el servidor y el cliente. Para lograr tales propósitos es necesario recurrir a alguno de procedimientos criptográficos que describiremos a continuación.

Cifrado simétrico

Supongamos que A y B convienen enviarse mensajes cifrados y acuerdan reemplazar las vocales por los números 1 al 5. De esa forma cuando uno de ellos pretenda escribir la palabra poetisa utilizará la grafía: p42t3s1. El receptor del mensaje podrá reemplazar los números por su vocal equivalente y recomponer el mensaje. Este sería un ejemplo muy simple de cifrado simétrico o con clave privada.


El grado de inseguridad de un sistema de cifrado simétrico está condicionado por dos factores. Uno de ellos, común a todos los sistemas de cifrado, es la mayor o menor resistencia que pueda presentar a los intentos de descubrir la clave a base de ensayo y error («fuerza bruta»). Normalmente, un aumento del tamaño de la clave representa un incremento la seguridad.

El otro problema, el más grave, es la falta de certeza sobre la identidad de remitente. No hay nada que nos de garantías sobre quien ha cifrado el documento. De todas formas veremos un poco más adelante como subsanar estos inconvenientes ya que este es el método de cifrado que se utiliza habitualmente cuando se navega por páginas seguras.

Existen varios algoritmos de cifrado simétrico. Uno de los más populares es el conocido como DES adoptado como un estándar, para comunicaciones no clasificadas, por el Gobierno de los EE.UU. en 1976. Su grado de eficiencia quedó muy cuestionado cuando, en 1998, Electronic Frontier Foundation logró fabricar una máquina capaz de descifrarlo en tres días. Esta circunstancia forzó la aparición de versiones mejoradas tales como el DES múltiple y otras que han logrado paliar en gran medida sus deficiencias iniciales y hacer de DES múltiple un algoritmo de uso bastante frecuente.

El algoritmo IDEA (International Data Encryption Algorithm) es otro de los que aperecen, en este caso en 1992, como opciones alternativas al uso de DES múltiple. No fué el único. En octubre de 2000 el National Institute for Standards and Technology (NIST) adoptó del algoritmo RIJNDAEL como nuevo Estándar Avanzado de Cifrado (AES) para su empleo en aplicaciones criptográficas no militares.

PHP5 dispone de funciones de cifrado para una gran variedad de algoritmos. Aquí tienes una lista de los algoritmos de cifrado que soporta actualmente.

Además de los distintos algoritmos de cifrado hay otro elemento diferenciador. Se trata de lo conocido como modo de cifrado. Los algoritmos de cifrado suelen trabajar con bloques de longitud fija. La cadena objeto del cifrado es descompuesta en bloques de igual tamaño y tratada de diferentes modos. En los llamados ECB (Electronic CodeBook) cada bloque es cifrado de forma independiente. En otros modos, por ejemplo el CBC (Cipher Block Chaining), a cada bloque de texto plano se le aplica una operación que requiere el uso del texto de bloques anteriores. En estos casos, para hacer cada mensaje único ha de utilizarse un vector de inicialización. Aquí tienes la lista de los modos de cifrado soportados por tu versión de PHP.

Funciones PHP de cifrado simétrico

Configuración de PHP

PHP dispone de funciones que permiten efectuar algunos tipos de cifrado. Antes de nada y como siempre en estos casos es conveniente comprobar en el fichero info.php si están activadas estas opciones.

En el caso de la versión PHP 5.3.6 para Windows esta opción viene activada por defecto. Igual ocurre en el caso de PHP 5.3 en Ubuntu 10.10. Por el contrario, en versiones anteriores de PHP es necesario, además de seguir el proceso de configuración que se indica en el enlace, descomentar de su fichero php.ini, quitando el punto y coma (;) que por defecto lleva delante la extension=php_mcrypt.dll.

Si en alguna versión de Ubuntu no viniera configurado por defecto podría instalarse ejecutando desde la consola el siguiente comando:

sudo apt-get install php5-mcrypt

Proceso de cifrado y descifrado

Los procesos de cifrado y descifrado mediante PHP son muy similares y requieren, en ambos casos, la siguiente secuencia de funciones:

$identificador=mcrypt_module_open(algoritmo, ubicacion, modo, directorio)

Abre un nuevo módulo de cifrado identificado por el valor la variable $identificador. Requiere estos parámetros: algoritmo es una cadena que ha de contener, entre comillas, el nombre del algoritmo que pretendemos utilizar. Será uno de los nombres de la lista antes mencionada y que puedes ver nuevamente desde este enlace. El parámetro ubicacion permite especificar el directorio con la eventual ubicación del algoritmo de cifrado en el caso de que se usara uno distinto a los incluidos en la distribución de PHP. Lo habitual será que pongamos como valor una cadena vacía de la forma "". El parámetro modo será una cadena en la que se especifique el modo de cifrado que pretendamos utilizar y habrá de contener uno de los valores (escrito entre comillas) que tenemos en este enlace. Por último, el parámetro directorio será habitualmente una cadena vacía "" ya que está definida para establecer el modo del directorio eventualmente establecido en el parámetro ubicacion.

Iniciado el módulo de cifrado y según el modo de cifrado establecido puede resultar necesario crear un vector de inicialización cuya longitud se determina por medio de la función:

$longitud=mcrypt_enc_get_iv_size($identificador)

en la que $identificador es el identificador de recurso establecido por la función anterior y $longitud la longitud de del mencionado vector de inicialización.

Conocida la dimensión $longitud del vector de inicialización (puede ser cero en el caso de que no sea requerido su uso) debemos crearlo invocando la función:

$vector=mcrypt_create_iv($longitud, constante)

donde $longitud es resultado de la función anterior y constante es la constante MCRYPT_RAND que debemos incluir sin comillas. El resultado, $vector, será el vector de inicialización.

El paso siguiente será iniciar todos los buffers necesarios para el posterior proceso de cifrado o de descifrado. De esa labor se encarga la función:

mcrypt_generic_init($identificador, clave, $vector)

en la que $identificador es el identificador del recurso, clave es una cadena que se usará como CLAVE DE CIFRADO y $identificador el identificador del recurso y $vector es el vector de inicialización.

El último paso ya será el cifrado o descifrado propiamente dichos. Si se trata de cifrar utilizaremos:

$cifrado=mcrypt_generic($identificador, texto)

donde texto es una cadena (o variable) que contiene el texto que pretendemos cifrar y donde $identificador sigue siendo el identificador del recurso. El resultado del cifrado es recogido en la variable $cifrado

Si se trata de descifrar la función anterior debe ser sustituida por

$descifrado=mdecrypt_generic($identificador, $cifrado)

siendo $cifrado la variable (ocadena) cifrada que pretendemos desencriptar y $descifrado el resultado de la desencriptación. Llegados a este punto, solo nos restaría liberar los buffer reservados para el cifrado y cerrar el recurso mediante las funciones:

mcrypt_generic_deinit($identificador)
y
mcrypt_generic_close($identificador)

De esta forma se consigue un método de cifrado y descifrado aplicable con cualquiera de los algoritmos y modos de cifrado disponibles.

Ejemplos de cifrado y descifrado simétrico

Un ejemplo de como puede cifrarse con clave simétrica podría ser este:

<?php
# establecemos la clave y la cadena a encriptar
  $clave = "clave";
  $texto ="Esta es una cadena de prueba";
     /*creamos un identificador de encriptado en el que indicamos
      el tipo de cifrador (cast-128) y el modo de cifrado (ecb) */
  $ident = mcrypt_module_open('cast-128', '', 'ecb', '');
     /* dado que algunas funciones requieren de un vector de inicializacion
      acorde con sus especificaciones esta función determina el tamaño
      de ese vector atendiendo al tipo de identificador */
  $long_iniciador=mcrypt_enc_get_iv_size($ident);
     /* crea el vector de inicialización con valores aleatorios
      y dándole la dimensión precalculada en la función anterior */
  $inicializador = mcrypt_create_iv ($long_iniciador, MCRYPT_RAND);
     /* hacemos algunas comprobaciones innecesarias para ejecutar el script.
        Simplemente son descriptores de algunas funciones complementarias */
                /* comprobamos el tamaño maximo (en bytes)
                que puede tener la clave para este algoritmo de cifra*/
                print "La clave no puede sobrepasar los ";
                print mcrypt_enc_get_key_size ($ident)." bytes<br>";
                /* escribimos el tamaño del bloque del
                   algoritmo que estamos usando*/
                 print "El tamaño del bloque de cifrado es ";
                  print mcrypt_enc_get_block_size($ident)." bytes<br>";
                  print "El modo de cifrado es ";
                  print mcrypt_enc_get_modes_name($ident)."<br>";
                  print "El algoritmo  de cifrado es ";
                  print mcrypt_enc_get_algorithms_name($ident)."<br>";
                  print "El tamaño del vector de inicialización es ";
                  print mcrypt_enc_get_iv_size ($ident)."<br>";

    /* Contimuamos la secuencia de encriptado incializando todos los buffer
       necesarios para llevar a cabo las labores de encriptado */
  mcrypt_generic_init($ident, $clave, $inicializador);
    /* realiza el encriptado proopiamente dicho */
  $texto_encriptado = mcrypt_generic($ident, $texto);
    /* libera los buffer pero no cierra el modulo */
  mcrypt_generic_deinit($ident);
    /* esta instruccion es necesaria para cerrar el modulo de encriptado*/
  mcrypt_module_close($ident);
     # imprimimos el resultado de la encriptación
     # en este caso añadimos una codificación de ese resultado en base 64
     print "La cadena encriptada es: ".base64_encode ($texto_encriptado);
     /* guardamos la cadena encriptada en un fichero con nombre encriptado */
     file_put_contents('encriptado',$texto_encriptado);
     print "<br> está codificada en base 64";
?>
Ejecutar ejemplo de encriptado

El proceso inverso, la desencriptación de una cadena codificada puede hacerse de la forma que se indica en el siguiente ejemplo.

 <?php
    /* hemos de usar la misma clave con la que ha sido encriptado */
  $clave = "clave"; 
   /* leemos el fichero encriptado creado por el script anterior */
 $texto_encriptado =file_get_contents('encriptado');
      /*creamos un identificador de encriptado que ha de ser el mismo con
  el que hemos realizado la encriptación */
 $ident = mcrypt_module_open('cast-128', '', 'ecb', '');
   /* dado que algunas funciones requieren de un vector de inicializacion
     acorde con sus especificaciones esta función determina el tamaño de ese
     vector atendiendo al tipo de identificador anterior*/
 $long_iniciador=mcrypt_enc_get_iv_size($ident);
   /* crea el vector de inicialización con valores aleatorios
     y dándole la dimensión precalculada en la función anterior */
 $inicializador = mcrypt_create_iv ($long_iniciador, MCRYPT_RAND);
   /* incializa todos los buffer necesarios para llevar
    a cabo las labores de encriptado */
 mcrypt_generic_init($ident, $clave, $inicializador);
    /* realiza el desencriptado proopiamente dicho. Realmente es la unica
       diferencia básica entre este script y el ejemplo anterior */
 $desencriptado = mdecrypt_generic($ident, $texto_encriptado); 
    /* libera los buffer pero no cierra el modulo */
 mcrypt_generic_deinit($ident);
    /* esta instruccion es necesaria para cerrar el modulo de encriptado*/
 mcrypt_module_close($ident);
    # imprimimos el resultado de la encriptación
    # en este caso añadimos una codificación de ese resultado en base 64
 print $desencriptado;
?>
Ejecutar ejemplo de descifrado

  ¡Cuidado!  

Si al intentar ejecutar el ejemplo anterior sobre Ubuntu observas sólo una página en blanco comprueba que el directorio en el que estés ejecutando el script tenga permisos de lectura/escritura. De no disponer de ellos no se guardaría el fichero con los datos encriptados y por es misma razón tampoco se visualizaría.

El protocolo de Diffie-Hellman

Una de las debilidades del cifrado con clave simétrica reside en la necesidad de que ambas partes intercambien su clave de cifrado de forma confidencial. Esa confidencialidad puede conseguirse, incluso en comunicaciones de forma no segura, mediante una técnica conocida como protocolo de Diffie-Hellman.

Números y sus raíces primitivas

Este protocolo requiere la utilización de dos números que llamaremos p y g. El número p habrá de ser un número primo, por lo general muy grande, mientras que g ha de ser una raíz primitiva de p. Sin abundar en aspectos demasiado técnicos podemos decir que g es raiz primitiva de p si el conjunto de los restos de dividir entre p cada de las sucesivas potencias de g desde 1 hasta p-1 (g1, g1, g2, g3, ... gp-1) contiene a todos los naturales {1,2,3,4..., p-1}.

    Eso significaría que para cualquier entero b menor que p y una raíz primitiva g del número primo p, se puede encontrar un único exponente i tal que b = gi (mod p) donde 0 ≤ i ≤ (p-1). El exponente i se conoce como el logaritmo discreto o índice de b para la base g, mod p. Este valor se representa como indg,p(b).

En la tabla que tienes a continuación puedes ver el proceso de comprobación de raices primitivas desarrollado para dos supuestos. Observa que en el caso de g=13 los restos de las divisiones se repiten (lo cual significa que hay valores que no aparecen) y por tanto 13 no es raíz primitiva de 23. Por el contrario, al comprobar el comportamiento de g=7 puedes ver los restos son, de forma no ordenada, los números naturales comprendidos entre 1 y 22. Por tanto g=7 si es raíz primitiva de 23.

Comprobación de las raíces primitivas
zp=23, g=7 (7 ES raíz primitiva de 23)p=23, g=13 (13 NO ES raíz primitiva de 23)
gzCociente entero gz/pResto gz/pgzCociente entero gz/pResto gz/p
170713013
2494631691618
3343322212197218512
4240123929285612854318
51680716790173712933712894
61176491176454482680948268036
7823543823538562748517627485089
857648015764789128157307218157307192
940353607403535921510604499373106044993703
102824752492824752361313785849184913785849183316
111977326743197732672122179216039403717921603940361
12138412872011384128718516232980851224812329808512246813
139688901040796889010387203028751065922533028751065922458
1467822307284967822307284723937376385699289393737638569927712
154747561509943474756150992914511858930140907575118589301409073918
16332329305696013323293056959566654166091831798416654166091831798374
1723263051398720723263051398718819865041591938133793386504159193813379276
1816284135979104491628413597910431181124554069519573931291124554069519573931209
19113988951853731431139889518537313211146192029037544611067714619202903754461106752
207979226629761200179792266297611993819004963774880799438801190049637748807994387983
215585458640832840075585458640832839971024706452907345039270441324706452907345039270439716
22390982104858298804939098210485829880481321183887795485510515736932118388779548551051573681

Obtención e intercambio de claves

Intecambiados de forma pública dos números p y g tales que g es raíz primitiva de p, cada uno de los dos usuarios que pretenden encontrar una clave común y secreta genera sendos números XA=gx (mod p) y XB=gy (mod p) donde x e y son números cualesquiera (secretos, ya que no serán intercambiados), menores que p. Ambos usuarios intercambian sus números. «A» facilita a «B» su XA y «B» proporciona a «A» el valor XB.
Cada usuario eleva a su clave secreta el valor público recibido del otro usuario y obtiene el resto de dividir el resultado entre el número p. Es decir, el usuario «A» efectúa la operación: KA=(XB)x (mod p) mientras que «B» calcula: Kb=(XA)y (mod p). Ambos resultados son iguales dado que se verifica que:

KA= (XB)x (mod p)= (gy)x (mod p)=(gyx)(mod p), y también que:

KB= (XA)y (mod p)= (gx)y (mod p)=(gxy)(mod p)

confirmádonse por tanto que ambos resultados han de ser iguales y que su valor será la clave simétrica buscada.

La práctica imposibilidad de que un tercero pueda obtener la clave radica en el hecho de que para calcularla es necesario conocer, al menos, uno de los números que hemos llamado secretos (x ó y) y aunque es cierto que pueden ser conocidos XA, XB, p y g (se transmiten o pueden transmitir de forma no segura) para romper la clave sería preciso buscar la solución a XA=gx (mod p) (logaritmo discreto de XA para la base g módulo p) tarea de muy alta dificultad (nada resulta imposible) cuando se trata de valores de p muy grandes. En esa dificultad radica precisamente la robustez de este protocolo desarrollado en 1976 por Whitfield Diffie y Martin Hellman.

Este es un ejemplo del procedimiento de cálculo de una clave de cifrado simétrico mediante el protocolo de Diffie-Hellman.

Obtención de una clave común y secreta para cifrado simétrico
Usuario «A»Usuario «B»
Acuerdan, de forma pública, un número y una de sus raíces primitivas clave y un generador.
Establezcamos esos números como:

p=23 y g=7
Elige un número natural secreto (puede hacerlo al azar)Elige un número natural secreto (puede hacerlo al azar)
123
Eleva g al numero elegido, lo divide entre p y determina el resto de esa divisiónEleva g al numero elegido, lo divide entre p y determina el resto de esa división
Resto de 712 entre 23 =16Resto de 73 entre 23 =21
Envía el número anterior a «B»Envía el número anterior a «A»
Eleva el valor recibido (21) a su clave secreta y calcula el resto de dividir ese resultado entre p (23).Eleva el valor recibido (16) a su clave secreta y calcula el resto de dividir ese resultado entre p (23)
Resto de 2112 entre 23=2Resto de 163 entre 23=2
Ambos obtienen el mismo resultado. Ese valor, solo conocido por ellos, será la clave de cifrado simétrico

La identidad del comunicante como problema de seguridad

Admitiendo la invulnerabilidad (que nunca lo es del todo) del protocolo de Diffie-Hellman y la robustez de los algoritmos de cifrado simétrico tendríamos una más que aceptable seguridad del carácter confidencial (e incluso la integridad) de la información intercambiada. Pero queda en el aire una pregunta muy importante. ¿Qué garantías nos ofrecen estas técnicas de que los comunicantes son los que dicen ser? ¿Quien nos garantiza que no han sido suplantadas sus identidades? ¿Podemos evitar que uno de los comunicantes pueda negar, aún habiéndolo hecho, haber enviado una determinada información?. Por el momento la respuesta es no. Serán necesarios otros recursos para solventar ese problema. Tenemos pendiente resolver la autentificación ( que cada parte de la comunicación pueda asegurarse de que la otra parte es realmente quien dice ser) y el no repudio (permite a cada uno de los comunicantes probar de forma fehaciente que el otro ha participado en la comunicación de forma que el remitente del mensaje no pueda negar haberlo enviado o (caso de no repudio de destino), el destinatario del mensaje no puede negar haberlo recibido.

Para resolver esos problemas hemos de recurrir a procedimientos bastante similares a los de la vida cotidiana. Lo primero de todo sería firmar la información de la misma forma que se firma un cheque, una carta, un certificado o una solicitud. Esa acción no sería otra cosa que firmar digitalmente nuestros mensajes.

Ni en la comunicación cotidiana ni en la digital resulta de suficiente garantía la firma de un documento. Todos sabemos que puede ser más o menos hábilmente imitada. La firma manuscrita puede ser falsificada. Necesitamos algún tipo de garantía más.

Cuando en un centro de enseñanza se expide un certificado lo habitual es que vaya con la firma del secretario, el visto bueno del director y el sello del centro. Es decir, la firma del director es una garantía de la fidelidad de la del secretario.

Puede que no confiemos lo suficiente en la firma del director –Autoridad Certificadora– y que demandemos la legitimación notarial de la firma del secretario. La única diferencia sería el mayor rango de la Autoridad Certificadora. Desde luego siempre podemos seguir desconfiando. De estos asuntos trataremos en los temas siguientes.