Introducción a las técnicas modernas de criptografía con ejemplos en Java

Page 1


Introducción a las técnicas modernas de criptografía con ejemplos en Java

Carlos Eduardo Gómez Montoya Profesor adscrito al Programa Ingeniería de Sistemas y Computación Facultad de Ingeniería Universidad del Quindío


Introducción a las técnicas modernas de criptografía con ejemplos en Java Este trabajo ha sido escrito con la intención de permitir su libre distribución dentro de la comunidad académica sin necesidad de solicitar autorización escrita por parte del autor. Diciembre de 2009 Armenia, Quindío – Colombia


Agradecimientos Llevar a feliz término este trabajo ha sido una labor de dos años de duración. No puedo finalizar sin mencionar algunas personas que contribuyeron para que saliera adelante. Quiero expresar mis agradecimientos: A Julián Esteban Gutiérrez Posada, compañero y amigo, por su constante apoyo. Igualmente al profesor Leonardo Hernández Rodríguez por sus ideas y aportes. A todos los estudiantes a quienes he tenido la oportunidad de orientar en el curso de Seguridad Informática en la Universidad del Quindío, principales colaboradores y a quienes les he preparado este material. Finalmente a mi Familia, Olga Lucía, Pablo y Martín, las personas más sacrificadas por la cantidad de tiempo en el que no los he acompañado por estar dedicado a este trabajo.


Presentación Introducción al desarrollo de aplicaciones de red en Java es un trabajo

académico presentado como requisito de ascenso en el escalafón docente de la Universidad del Quindío, de la categoría Profesor Asociado a la categoría Profesor Titular. Este trabajo se ha desarrollado durante los últimos cinco semestres con el fin de facilitar el desarrollo de aplicaciones de red a los estudiantes del programa Ingeniería de Sistemas y Computación. Este material educativo sirve de apoyo en diferentes cursos, especialmente en Redes de Computadores I y II, asignatura dentro del plan de estudios del programa Ingeniería de Sistemas y Computación de la Universidad del Quindío en la ciudad de Armenia. El trabajo desarrollado es la materialización de una propuesta presentada al Consejo de la Facultad de Ingeniería de la Universidad del Quindío, la cual fue aprobada sin modificaciones. Esta propuesta se incluye en el trabajo como capítulo 1 para que el evaluador la tenga a disposición para determinar si el trabajo cumple con los objetivos propuestos y se puede considerar un aporte a la docencia como lo establece el Estatuto Docente de la Universidad del Quindío.


A partir del capítulo dos, inicia el desarrollo de los temas con una introducción general, seguido por los temas Introducción a la criptografía, Criptografía de llave simétrica, Codificación Base64, Criptografía de llave pública, Sistema criptográfico híbrido, Control de integridad y Firmas digitales. Para cada uno de los temas mencionados se hace una breve descripción para contextualizar el tema para llegar a los talleres de programación en Java, donde está realmente concentrado el aporte del autor. Los talleres de programación están pensados para llevar al estudiante a poner en práctica los conceptos más importantes de la criptografía a través de ejercicios en Java. Para el docente, estos talleres de programación pueden ser el punto de partida para el desarrollo de proyectos más complejos. Es importante que quienes van a utilizar este libro tengan conocimientos de programación orientada a objetos y desarrollo de aplicaciones de red, específicamente en lenguaje Java Por otra parte, tomé la decisión de desarrollar este trabajo porque me parece que se trata de un tema que cada vez toma más vigencia y se ha convertido en una necesidad en todo tipo de aplicaciones.


Acerca del autor Carlos Eduardo Gómez Montoya. Licenciado en Matemáticas y Computación de la Universidad del Quindío, Especialista en Redes de Comunicación de la Universidad del Valle y Magíster en Ingeniería de la Universidad de los Andes en el área de Sistemas y Computación. Profesor de planta, tiempo completo, de la Universidad del Quindío adscrito al programa Ingeniería de Sistemas y Computación. Fundador y Director del Grupo de Investigación en Redes, Información y Distribución – GRID de la Facultad de Ingeniería de Universidad del Quindío. Autor de diferentes obras en el área de la Ingeniería de Sistemas e informática. carloseg@uniquindio.edu.co.


Contenido Página 1 1.1

Propuesta .................................................................................................. 1 INTRODUCCIÓN.................................................................................... 3

1.2 1.3

JUSTIFICACIÓN ..................................................................................... 6 OBJETIVOS ............................................................................................. 7

1.3.1 Objetivo general .................................................................................... 7 1.3.2 Objetivos específicos ............................................................................ 7 1.4 TEMÁTICA ............................................................................................. 8 1.5

REFERENCIAS BIBLIOGRÁFICAS ..................................................... 8

2 3

Introducción ............................................................................................ 11 Introducción a la criptografía ................................................................. 15

3.1 Principios básicos de la criptografía ....................................................... 16 3.2 Criptografía tradicional .......................................................................... 17 3.2.1 Cifrados por sustitución ...................................................................... 17 3.2.2 3.3 4 4.1 4.1.1

Cifrados por transposición .................................................................. 19 Criptografía moderna ............................................................................. 21 Criptografía de llave simétrica ............................................................... 23 Sistema criptográfico por bloques .......................................................... 25 Modos de cifrado ................................................................................ 25

4.1.2 Esquema de Padding ........................................................................... 26 4.2 Algoritmos simétricos ............................................................................ 28


Página 4.2.1

DES – Data Encryption Standard ....................................................... 28

4.2.2 4.2.3 4.2.4

Triple DES (3DES) ............................................................................. 28 AES - Advanced Encryption Standard ............................................... 29 RC4 ..................................................................................................... 29

4.3

Taller de programación .......................................................................... 30

4.3.1 4.3.2

Actividad 1. API Java de seguridad y criptografía ............................. 30 Actividad 2. Cifrado de mensajes de texto ......................................... 33

4.3.3 4.3.4

Actividad 3. Cifrado de objetos .......................................................... 41 Actividad 4. Miniproyecto .................................................................. 47

4.3.5 Actividad 5. Requisito adicional miniproyecto .................................. 49 5 Codificación Base64 ............................................................................... 51 5.1 5.2 5.3

Alfabeto Base 64 .................................................................................... 51 Transformación ...................................................................................... 52 Ejemplo 1 ............................................................................................... 53

5.4

Ejemplo 2 ............................................................................................... 56

5.5 5.6

Ejemplo 3 ............................................................................................... 57 Aplicaciones de la codificación Base 64 ................................................ 58

5.7 Taller de codificación ............................................................................. 59 5.8 Taller de programación .......................................................................... 64 5.8.1 Clase Base64.java ............................................................................... 64 5.8.2

Pruebas de la codificación Base64...................................................... 67

6 Criptografía de llave pública .................................................................. 69 6.1 Algoritmos de llave pública ................................................................... 70 6.1.1 RSA..................................................................................................... 70 6.2

Taller de cálculo de clave pública y privada .......................................... 72

6.3 Taller de programación .......................................................................... 73 6.3.1 Actividad 1. API Java de seguridad y criptografía ............................. 73


Página 6.3.2

Actividad 2. Cifrado de mensajes de texto ......................................... 75

6.3.3 6.3.4 6.3.5

Actividad 3. Cifrado de Objetos ......................................................... 80 Actividad 4. Miniproyecto .................................................................. 83 Actividad 5. Restricciones de RSA .................................................... 85

6.3.6

Actividad 6. Método splitByteArray ( ) .............................................. 87

6.3.7 6.3.8

Actividad 7. Método joinByteArray ( ) .............................................. 91 Actividad 8. Prueba de los métodos de dividir y unir......................... 92

6.3.9 Actividad 9. Cifrar un objeto mayor de 117 bytes .............................. 93 7 Sistema criptográfico híbrido ................................................................. 97 7.1 Taller de criptografía en aplicaciones Cliente-Servidor ......................... 98 7.1.1 Actividad 1. Envío de objetos por la red ............................................ 98 7.1.2

Actividad 2. Envío de objetos cifrados por la red usando criptografía

de llave simétrica ............................................................................................. 103 7.1.3 Actividad 3. Envío de objetos cifrados por la red usando criptografía de llave pública ................................................................................................ 108 7.1.4 Actividad 4. Miniproyecto ................................................................ 114 8 Control de integridad ............................................................................ 117 8.1 8.2 8.3 8.3.1

MD5...................................................................................................... 118 SHA-1 ................................................................................................... 119 Taller de programación ........................................................................ 119 Actividad 1. Manejo de una herramienta para el cálculo de una cifra de

control de integridad en Windows................................................................... 119 8.3.2 Actividad 2. Control de integridad de objetos en memoria .............. 122 8.3.3 Actividad 3. Control de integridad de archivos binarios .................. 127 8.3.4 9 9.1

Actividad 4. Miniproyecto ................................................................ 128 Firmas digitales .................................................................................... 135 Generación de la firma digital .............................................................. 137


P谩gina 9.2

Verificaci贸n de la firma digital ............................................................ 137

9.3 Firmas digitales en Java ....................................................................... 137 9.3.1 Generar una firma ............................................................................. 139 9.3.2 Verificar una firma ........................................................................... 140 9.4

Certificados digitales ............................................................................ 141

9.5 Taller de programaci贸n ........................................................................ 142 9.5.1 Actividad 1. Firmar un archivo y verificar la firma.......................... 142 9.5.2 9.5.3 10

Actividad 2. Firma digital en aplicaciones cliente - servidor ........... 146 Actividad 3. Miniproyecto ................................................................ 146 Referencias ........................................................................................... 147


Introducción a las técnicas modernas de criptografía con ejemplos en Java

1

1 Propuesta

El Acuerdo 049 de Junio 22 de 1995 del Consejo Superior de la Universidad del Quindío establece el Estatuto Docente, el cual reglamenta la relación laboral entre la Universidad y los docentes. El artículo 54 del Estatuto Docente (reglamentado por el acuerdo 010 de Marzo 4 de 1996), en el numeral b, establece que para ser profesor titular se requiere elaborar dos trabajos diferentes que constituyan un aporte significativo a la docencia, a las ciencias, a las artes o a las humanidades. Para cumplir con el procedimiento establecido en el Estatuto Docente de la Universidad del Quindío, el 16 de diciembre de 2008 presenté dos propuestas para trabajo de ascenso: “Introducción al desarrollo de aplicaciones de red en Java” e “Introducción a la criptografía con aplicaciones en Java”. Las dos propuestas presentadas corresponden a aportes a la docencia porque ponen al alcance de los estudiantes y otros docentes material académico que facilitan el desarrollo de cursos con temáticas actuales como son las aplicaciones de red y la seguridad informática. Estas propuestas fueron aprobadas sin modificaciones por el Consejo de Facultad de Ingeniería el día 30


2 de marzo de 2009. A continuación, la propuesta “Introducción a la criptografía con aplicaciones en Java”.


Introducción a las técnicas modernas de criptografía con ejemplos en Java

3

1.1 INTRODUCCIÓN En un principio, la seguridad en redes no era una motivo de preocupación, porque el acceso a las redes era restringido y las aplicaciones eran principalmente educativas y de investigación [6]. Sin embargo, con el paso del tiempo, cada vez más personas que hacen uso de aplicaciones donde los datos viajan por la red y se exponen a peligros tales como acceso no autorizado a información confidencial, modificación no autorizada de datos, la suplantación de identidad, entre otros, que hacen necesario que se considere la seguridad como un aspecto crítico que debe ser analizado desde diferentes puntos de vista, para tratar de mitigar los riesgos en el uso de aplicaciones que usan las redes de computadores y otros dispositivos. La seguridad informática es importante porque involucra cada vez

a más

personas y entidades quienes de una u otra forma acceden o publican información que puede ser consultada o utilizada por canales electrónicos. Esta información está expuesta a riesgos que en su mayoría no están bajo el control de sus dueños. La seguridad es un área de enormes proporciones: está relacionada con la confidencialidad de los datos, la integridad, el control de acceso no autorizado, la verificación de identidad de quien origina un mensaje, y de la disponibilidad de la información, entre muchos otros. Los problemas de seguridad, generalmente son generados por personas malintencionadas que intentan obtener beneficios o hacer daño a otros. Estas personas comúnmente tienen recursos técnicos y económicos, y son personas dedicadas a buscar la forma de explotar las vulnerabilidades, por lo que es


4 necesario estar preparados y atender con seriedad las recomendaciones de los expertos. Los problemas de seguridad se pueden clasificar fundamentalmente en: confidencialidad, integridad y disponibilidad [1]. La confidencialidad, consiste en mantener en secreto información sensible con el fin de prevenir que usuarios no autorizados puedan acceder a ella. La integridad intenta prevenir: la modificación de la información por usuarios no autorizados, la modificación no autorizada o no intencionada de usuarios autorizados y la preservación de la consistencia de la información. La disponibilidad asegura que los usuarios autorizados de un sistema tengan acceso a la información en el sistema y a la red en forma oportuna y sin interrupciones. Dependiendo de la aplicación y del contexto, uno de estos principios podría ser más importante que los otros. Otros aspectos importantes son la autenticación, la autorización y el no repudio. La autenticación se encarga de validar la identidad del interlocutor antes de revelar información sensible. La autorización es el conjunto de privilegios que tiene un usuario de un sistema lo que determina las acciones y el comportamiento para usuarios individuales o por categorías. El no repudio permite comprobar que un mensaje fue realmente emitido por quien lo firma para evitar que alguien pueda afirmar que desconoce un mensaje enviado previamente. Una parte importante de la seguridad en las redes de computadores está basada en la criptografía, la cual ofrece los servicios de confidencialidad, autenticación y control de integridad. La criptografía ofrece una solución frente a estas necesidades que son fundamentales en las transacciones electrónicas del mundo de hoy.


Introducción a las técnicas modernas de criptografía con ejemplos en Java

5

La criptografía es la ciencia de la escritura secreta, una herramienta fundamental en los sistemas distribuidos y el comercio electrónico. La criptografía proporciona tres servicios que son cruciales en la programación segura: un cifrado criptográfico que protege el secreto de los datos; los certificados criptográficos, los cuales proporcionan autenticación de identidad; y las firmas digitales, las cuales aseguran que los datos no han sido falsificados o dañados.

La aplicación de la criptografía consiste en tomar un mensaje en texto claro, aplicarle una función parametrizada mediante una clave para obtener un mensaje cifrado. Como es de suponer, la clave solo es conocida el destinatario, quien es el único que podría descifrar el mensaje y obtener el mensaje en texto claro. Sin embargo, esto no siempre es cierto, porque es posible que personas malintencionadas (intrusos) escuchen e intercepten el mensaje para intentar acceder a información confidencial. El criptoanálisis es realizado por un intruso cuando intenta descifrar los mensajes que previamente ha interceptado. Los métodos de cifrado actuales son muy sofisticados, por lo que mantenerlos en secreto es inconveniente, especialmente por la dificultad de encontrar métodos nuevos. Además, al hacerse público un método o algoritmo de cifrado, mucha gente puede contribuir con pruebas o desafíos para hacer el método cada vez más robusto. La clave, por otra parte, no solo se debe mantener en secreto, sino que debe ser cambiada con frecuencia y tener una longitud lo suficientemente larga que haga muy difícil su descubrimiento al examinar todo el espacio posible de claves, conocido como ataque de fuerza bruta. Los métodos de cifrado actuales, usan llaves de 64 bits, 128 bits, 256 bits y más.


6 El propósito fundamental de este trabajo es producir un documento de apoyo a una asignatura de Seguridad Informática, para permitirle a los estudiantes crear aplicaciones que utilicen las técnicas modernas de criptografía aprovechando las librerías de recursos de Criptografía y seguridad que proporciona el lenguaje Java. Actualmente la asignatura en mención no hace parte del plan de estudios obligatorio del Programa Ingeniería de Sistemas y Computación de la Universidad del Quindío, es un curso que se ha ofrecido en forma de Electiva durante los dos períodos académicos del año 2008. Sin embargo, los temas que serán incluidos en este trabajo son absolutamente necesarios para los estudiantes, así como para los egresados de nuestro programa. En el tratamiento de los temas no se hará énfasis en la complejidad matemática de los principios criptográficos sino en la aplicación de ellos en el desarrollo de aplicaciones, sin perder de vista la comunidad a la cual está orientada el trabajo.

1.2 JUSTIFICACIÓN Actualmente son muy pocos o no hay estudiantes del programa de Ingeniería de Sistemas y Computación de la Universidad del Quindío que estén saliendo al mercado laboral con conocimientos en Seguridad informática. Para todos ellos, el uso de técnicas modernas de criptografía en el desarrollo de aplicaciones es fundamental, no importa si el área de desempeño es la construcción de software, el desarrollo de sistemas de información o la infraestructura de redes. Aunque existe bibliografía que puede ser utilizada por profesores y estudiantes, el proceso de selección de los ejemplos o adaptación de ellos es bastante dispendioso. En muchos casos los ejemplos son muy difíciles de seguir o las técnicas de programación no son las mejores. Este material puede contribuir notablemente en el desarrollo y proyección de este tema, porque aporta


Introducción a las técnicas modernas de criptografía con ejemplos en Java

7

conocimientos de mucha actualidad a los estudiantes del programa Ingeniería de Sistemas y Computación de la Universidad del Quindío y fortalece el área de sistemas distribuidos una línea de profundización que está naciendo en el programa. Por otra parte, la producción académica permite a los docentes afianzar sus conocimientos y adquirir reconocimiento entre los estudiantes, y la comunidad académica lo que puede contribuir en gran medida a la acreditación del programa.

1.3 OBJETIVOS 1.3.1 Objetivo general Crear un documento con los fundamentos básicos de la criptografía junto con los ejemplos y ejercicios que apoyen la labor docente de un curso de Seguridad Informática orientado al desarrollo de aplicaciones que usen técnicas modernas de criptografía.

1.3.2 Objetivos específicos •

Recopilar la información teórica necesaria acerca de las técnicas modernas de criptografía y el desarrollo de aplicaciones que las utilicen.

Elaborar ejemplos de cada tema tratado cuidadosamente seleccionados y

detalladamente documentados. Proponer ejercicios y proyectos que apoyen la realización de un curso de Seguridad Informática orientado al desarrollo de aplicaciones que usen técnicas modernas de criptografía.


8

1.4 TEMÁTICA Los temas a tratar en este trabajo son: 1. Introducción 2. Introducción a la criptografía 3. Principios básicos de la criptografía 4. Criptografía tradicional 5. Criptografía simétrica 6. Criptografía de llave pública 7. Sistema criptográfico híbrido 8. Control de integridad 9. Firmas digitales

De cada tema se hará una descripción general sin entrar en detalles acerca de la complejidad matemática. A partir del numeral 5 (Criptografía Simétrica), cada sección tendrá una revisión de los principales algoritmos usados, ejemplos desarrollados en Java, ejercicios y proyectos que faciliten la retroalimentación de los temas tratados.

1.5 REFERENCIAS BIBLIOGRÁFICAS [1] COLE, E. KRUTZ, R. y CONLEY, J. Network Security Bible. Wiley Publishing, Inc.

[2] COULOURIS, G., DOLLIMORE, J. y KINDBERG, T. Distributed Systems: Concepts and designs. Fourth Edition. Addison Wesley. 2005.


Introducción a las técnicas modernas de criptografía con ejemplos en Java

9

[3] Java Platform, Standard Edition 6 API Specification. Disponible en: http://java.sun.com/javase/6/docs/api/index.html.

[4] KNUDSEN, J. Java criptography. O’Reilly. 1998.

[5] KUROSE, J. y ROSS, K. Computer networking: A top-down approach . Fourth Edition. Addison Wesley. 2008.

[6] TANENBAUM, A. “Computer Networks”. Prentice Hall. 4th edition, 2004.


10


Introducción a las técnicas modernas de criptografía con ejemplos en Java

11

2 Introducción

En un principio, la seguridad en redes de computadores no era considerada, porque el acceso a las redes era restringido y las aplicaciones eran principalmente educativas y de investigación [7]. Sin embargo, con el paso del tiempo, cada vez más personas que hacen uso de aplicaciones donde los datos viajan por la red, en las cuales la seguridad es un aspecto crítico que debe ser analizado desde diferentes puntos de vista, para tratar de hacer más seguras las aplicaciones de las redes de computadores. La seguridad es un área de enormes proporciones: está relacionada con la confidencialidad de los datos, la integridad, el control de acceso no autorizado, la verificación de identidad de quien origina un mensaje, y de la disponibilidad de la información, entre muchos otros. Los problemas de seguridad, generalmente son generados por personas malintencionadas que intentan obtener beneficios o hacer daño a otros. Estas personas comúnmente tienen recursos técnicos y económicos, y son personas dedicadas a buscar la forma de explotar las vulnerabilidades, por lo que es necesario estar preparados y atender con seriedad las recomendaciones de los expertos.


12 Los problemas de seguridad se pueden clasificar fundamentalmente en: confidencialidad, integridad y disponibilidad. La confidencialidad, consiste en mantener en secreto información sensible con el fin de prevenir que usuarios no autorizados puedan acceder a ella. La integridad intenta prevenir: la modificación de la información por usuarios no autorizados, la modificación no autorizada o no intencionada de usuarios autorizados y la preservación de la consistencia de la información. La disponibilidad asegura que los usuarios autorizados de un sistema tengan acceso a la información en el sistema y a la red en forma oportuna y sin interrupciones. Dependiendo de la aplicación y del contexto, uno de estos principios podría ser más importante que los otros. Otros aspectos importantes son la autenticación, la autorización y el no repudio. La autenticación se encarga de validar la identidad del interlocutor antes de revelar información sensible. La autorización es el conjunto de privilegios que tiene un usuario de un sistema lo que determina las acciones y el comportamiento para usuarios individuales o por categorías. El no repudio permite comprobar que un mensaje fue realmente emitido por quien lo firma. Una parte importante de la seguridad en las redes de computadores está basada en la criptografía, la cual ofrece los servicios de confidencialidad, autenticación y control de integridad. La criptografía ofrece una solución frente a estas necesidades que son fundamentales en las transacciones electrónicas del mundo de hoy. El cifrado es una herramienta para proteger secretos. Es posible cifrar archivos almacenados en el disco duro de un computador para evitar que el robo o la pérdida del computador pudieran comprometer los datos del propietario [1]. También es posible cifrar los datos que se envían a través de la red de


Introducción a las técnicas modernas de criptografía con ejemplos en Java

13

computadores, especialmente la que se envía a una entidad financiera o la información relacionada con la información médica. La criptografía moderna tiene fundamentos matemáticos avanzados. Sin embargo, este trabajo hace énfasis en el desarrollo de aplicaciones en Java con base en las APIs que vienen disponibles en el JDK. De este modo los estudiantes pueden crear aplicaciones de software y proteger datos sensibles aplicando técnicas modernas de criptografía.


14


Introducción a las técnicas modernas de criptografía con ejemplos en Java

15

3 Introducción a la criptografía La criptografía es la ciencia de la escritura secreta, una herramienta fundamental en el desarrollo de todo tipo de aplicaciones que usen la red para transportar información. La aplicación más conocida de la criptografía consiste en la protección de datos para mantener en secreto información que no debe ser conocida por cualquier persona. En este caso se toma un mensaje en texto claro, se le aplica una función parametrizada mediante una clave y se obtiene un mensaje cifrado. Como es de suponer, la clave debe ser conocida por el destinatario, para que pueda descifrar el mensaje y obtener el mensaje original. La siguiente notación será utilizada para indicar el procedimiento de cifrado o descifrado de un mensaje: m será el mensaje original o en texto claro; E será utilizado para indicar el proceso de cifrado; D es el proceso inverso; K será la clave o llave y C será el texto cifrado producido [7]. • C = E K (m ) • m = DK (C ) • m = DK (E K (m ))


16 Los métodos de cifrado actuales son muy sofisticados, por lo que mantenerlos en secreto es inconveniente, especialmente por la dificultad de encontrar métodos nuevos. Además, al hacerse público un método o algoritmo de cifrado, mucha gente puede contribuir con pruebas o desafíos para hacer el método cada vez más robusto. La clave, por otra parte, no solo se debe mantener en secreto, sino que debe ser cambiada con frecuencia [7]. La longitud de la clave es un aspecto fundamental que debe ser tenido en cuenta al momento de usar algoritmos de cifrado. En la longitud de la clave está la robustez de un método de cifrado, ya que considerando el peor caso, se puede contabilizar el tiempo que tardaría en examinar todas las posibles combinaciones para obtener el texto claro. Esta forma de examinar todo el espacio posible de claves se conoce como ataque de fuerza bruta. Los métodos de cifrado actuales, usan llaves de 64 bits, 128 bits, 256 bits y más [7].

3.1 Principios básicos de la criptografía Los métodos de cifrado han sido divididos históricamente en dos categorías: cifrados por sustitución y cifrados por transposición [7]. Cifrados por sustitución: En un cifrado por sustitución, cada símbolo del lenguaje con el que se escriben los mensajes es reemplazado por otro. Cifrados de transposición: Los cifrados por sustitución conservan el orden de los símbolos que trae el texto claro, pero disfraza cada uno de ellos. Los cifrados de transposición, no disfrazan los símbolos, pero les cambian el orden en una forma sistemática a partir de una clave que determina la forma como se


Introducción a las técnicas modernas de criptografía con ejemplos en Java

17

hizo el procedimiento y permite hacer el procedimiento inverso para obtener el texto claro. A continuación, una breve descripción de la criptografía tradicional y la criptografía moderna.

3.2 Criptografía tradicional Los algoritmos criptográficos tradicionales están basados en la sustitución y la transposición [7].

3.2.1 Cifrados por sustitución Uno de los algoritmos más antiguos, llamado cifrado de César, consiste en tomar un alfabeto en orden normal y a continuación tomar otro alfabeto equivalente con un desplazamiento constante [1], tal como se puede apreciar en la figura 1. a b c d e f g h i j k l m n o P q r s t u v w x y z t u v w x y z a b c d e f g h I j k l m n o p q r s

Figura 1. Tabla de equivalencia para el algoritmo de César con K = 7 En este caso, cualquier letra en el texto original tendrá una equivalencia en el texto cifrado. Para un ciftado de César con clave K = 7, la letra a se convierte en t, la b se convierte en u, la c se convierte en v, y así sucesivamente. Por ejemplo, si se desea cifrar el texto seguridad usando un ciftado de César con clave K = 7, entonces se debe leer la tabla de equivalcia de arriba hacia abajo. El texto cifrado se puede observar en la figura 2.


18 s e g u r i d a d l x z n k b w t w

Figura 2. Ejemplo de cifrado de César con K = 7 Entonces, el texto lx znkbwt w es el mensaje cifrado. Para descifrar el mensaje, el destinatario debe conocer la clave K = 7 y con esa información debe saber cómo construir la tabla de equivalencias que se utilizó para cifrar el mensaje y de este modo puede descifrarlo, simplemente leyendo la tabla de abajo hacia arriba. Este algoritmo se puede generalizar para obtener un algoritmo de cifrado por sustitución monoalfabética [7], donde la tabla de equivalencias en sí misma sea la clave y no un número que sea el desplazamiento, debido principalmente a la fragilidad del cifrado de César. El cifrado de sustitución monoalfabética consiste entonces en establecer un orden determinado para la construcción de la tabla de equivalencia, como el presentado en la figura 3. a b c d e f g h i j k l m n o p q r s t u v w x y z m x y a k w h l v b i d o u c t g p e z n r q f s j

Figura 3. Ejemplo de una tabla de equivalencia para el algoritmo de cifrado por sustitución monoalfabética Al aplicar el algoritmo de sustitución monoalfabética de la figura 3 para cifrar el texto seguridad se tiene el resultado que se muestra en la figura 4.


Introducción a las técnicas modernas de criptografía con ejemplos en Java

s e g u r i d e k h n p v a

19

a d m a

Figura 4. Ejemplo de cifrado por sustitución monoalfabética El texto seguridad cifrado usando la tabla de equivalencias de la figura 3 corresponde a ekhnpvama. De igual manera, se puede usar la tabla de equivalencias para descifrar el mensaje, solo que en este caso, la cadena de 26 caracteres es la clave: mxyakwhlvbidouctgpeznrqfsj.

3.2.2 Cifrados por transposición A diferencia de los cifrados por sustitución, los cifrados por transposición no modifican los símbolos, sino que los cambian de posición. Un método de cifrado por transposición clásico consiste en establecer como clave una palabra que no tenga letras repetidas, por ejemplo cripto. Entonces se debe obtener la cantidad de letras de la clave (en este caso 6) y formar una tabla de seis columnas para disponer en esta tabla el texto que se desea cifrar ocupando las filas necesarias. Al finalizar, se debe utilizar un mensaje predeterminado para rellenar hasta completar la última fila [7]. El texto cifrado entonces es leer columna por columna, iniciando por aquella que esté más cerca del inicio del alfabeto. Suponga que desea cifrar el texto: la seguridad es una sensacion de confianza1. Observe en la figura 5 la tabla con el mensaje en texto claro y en la

1

Se han omitido las tildes a propósito para facilitar el ejercicio.


20 figura 6 el texto cifrado, leyendo de arriba abajo, de acuerdo con el n煤mero asignado a la columna. c 1 l r s n n f

r 5 a i u s d i

i 2 s d n a e a

p 4 e a a c c n

t 6 g d s i o z

o 3 u e e o n a

Figura 5. Ejemplo de cifrado por transposici贸n Al ordenar las columnas, se obtiene el texto cifrado, pero debe leerse en forma de columnas. c 1 l r s n n f

i 2 s d n a e a

o 3 u e e o n a

p 4 e a a c c n

r 5 a i u s d i

t 6 g d s i o z

Figura 6. Ejemplo de cifrado por transposici贸n El texto la seguridad es una sensacion de confianza cifrado corresponde a lrsnnfsdnaeaueeonaeaaccnaiusdigdsioz y la clave usada fue la palabra cripto. Observe que en este ejemplo no se presentaron caracteres de relleno. Para descifrar, suponiendo que el destinatario conoce el m茅todo, entonces debe calcular la cantidad de filas de la tabla dividiendo la cantidad de letras del texto cifrado entre la cantidad de letras de la clave. Luego, debe escribir el texto


Introducción a las técnicas modernas de criptografía con ejemplos en Java

21

cifrado en la tabla de arriba a abajo llenando las columnas hasta terminar y luego debe transponer las columnas de acuerdo con el orden que determina la palabra clave.

3.3 Criptografía moderna La criptografía tradicional tenía como principal limitación la cantidad de información que debía ser procesada manualmente. Con la llegada de los computadores, se dio paso a la criptografía moderna [7]. La criptografía moderna usa las mismas ideas básicas de la criptografía tradicional (la sustitución y la transposición), pero su orientación es distinta. Sin embargo, en la actualidad se utilizan algoritmos mucho más sofisticados los cuales son muy difíciles de seguir usando procedimientos manuales [1]. Un sistema criptográfico es usado para cifrar o descifrar datos. Los sistemas criptográficos modernos vienen en tres sabores: • Sistema criptográfico de llave simétrica, el cual usa una llave simétrica tanto para cifrar como para descifrar datos. Es apropiado cuando el mismo usuario desempeña las dos funciones (cifrar y descifrar), por ejemplo para cifrar los archivos del disco duro. • Sistema criptográfico de llave pública o sistema criptográfico de llave asimétrica, donde se usa un par de llaves, una pública que puede ser libremente distribuida y otra privada que debe mantenerse en secreto. En este caso, es posible cifrar con una llave y descifrar con la otra. • Un sistema criptográfico híbrido es la combinación de los dos anteriores y se utiliza con el fin de intercambiar una llave simétrica (usualmente de


22 sesión) entre dos usuarios que están conectados a través de una red de computadores.


Introducción a las técnicas modernas de criptografía con ejemplos en Java

23

4 Criptografía de llave simétrica Los algoritmos de cifrado de llave simétrica utilizan una sola llave tanto para cifrar como apra descifrar y, como ya se ha mencionado, se construyen a partir de operaciones de transposición y sustitución de bloques de bits. El cifrado de César o el de sustitución monoalfabética son ejemplos de sistemas criptográficos de llave simétrica. La idea general es que con la misma llave se cifra y descifra un mensaje. El cifrado simétrico se usa cuando el emisor de un mensaje, por ejemplo Ana quiere proporcionar confidencialidad a un mensaje enviado a un destinatario, por ejemplo Bernardo. De este modo Ana quiere que el mensaje o datos permanezcan en secreto para todos menos para Bernardo. La mejor analogía de un sistema criptográfico simétrico es un cajón con una cerradura. Las cosas que se guardan en el cajón estarán protegidas para que solo quien tenga la llave correcta pueda tener acceso a lo que hay en su interior. De igual manera, se puede usar un sistema criptográfico para proteger datos y quien tenga la llave apropiada puede abrirlo. En el mundo real la llave es un objeto metálico, mientras que en el mundo de la criptografía, la llave es un conjunto de bits [1].


24 Así como en la vida real hay personas que pueden intentar abrir el cajón sin tener la llave apropiada, tal vez intentado con cualquier llave, hay personas malintencionadas que intentan descifrar un mensaje cifrado sin ser los destinatarios del mensaje y sin tener la llave, posiblemente intentando todas las posibles combinaciones para obtener una llave, intentando descifrar el mensaje cifrado. El número total de combinaciones posibles depende del algoritmo de cifrado utilizado y específicamente de la longitud de la llave. En la criptografía simétrica hay dos clases principales de sistemas criptográficos, de acuerdo con la forma en la que se procesan lo datos: los sistemas criptográficos por flujo y por bloque. Antes de los computadores, los sistemas criptográficos usaban flujos, cifrando un carácter de un mensaje a la vez. En este caso, el mensaje es considerado como un flujo de bytes porque cada byte es procesado con los bytes anteriores, y porque el orden es importante. Si hay algún cambio en el orden de los bytes del texto claro, el texto cifrado cambia a partir de la posición en la que se realiza el cambio [1]. Los sistemas criptográficos por flujo normalmente no requieren ningún esquema de relleno (padding) dado que pueden trabajar con mensajes de cualquier longitud. Uno de los sistemas criptográficos de llave simétrica por bloques más conocido es el algoritmo RC4. Otros algoritmos son: SEAL, ISAAC, PANAMA y Helix [1].


Introducción a las técnicas modernas de criptografía con ejemplos en Java

25

4.1 Sistema criptográfico por bloques Un sistema criptográfico por bloque es otra clase de algoritmo de cifrado de llave simétrica. Este tipo de sistemas criptográficos usan una sola llave para cifrar y para descifrar un mensaje, pero el procedimiento se aplica un bloque a la vez. Se denomina bloque a un número de bits determinado por cada algoritmo [1]. En su forma original, cada bloque se procesa en forma independiente de los demás bloques y no hay ninguna relación entre los bloques. Sin embargo, cifrar bloques independientes de texto claro permite obtener bloques de texto cifrado independientes, por lo que dos bloques iguales de texto claro conducirán a dos bloques iguales de texto cifrado. Esta situación puede ser aprovechada por personas malintencionadas bien sea para deducir la llave o para intercambiar el orden de los bloques afectando la integridad del mensaje cifrado. Para evitar que dos bloques iguales de texto claro produzcan dos bloques iguales de texto cifrado se encandena un bloque de texto claro con el bloque anterior de texto cifrado mediante la operación booleana XOR.

4.1.1 Modos de cifrado Se denomina modo de cifrado a la forma como se encadenan estos bloques. Los modos principales son ECB, CBC y CFB los cuales se describen a continuación [1], [5] y [7]. Otros modos ECB: Electronic Code Book. Es el caso tipico donde los bloques de texto claro son cifrados en forma independiente sin ninguna clase de encadenamiento. El mensaje es cifrado un bloque a la vez y dos bloques iguales de texto claro


26 producen dos bloques iguales de texto cifrado. Un error en cualquier bloque solo produce error en un bloque cuando se descifre el mensaje. Si un bloque completo se pierde durante la transmisión, ninguno de los demás bloques se ven afectados. CBC: Cipher Block Chaining. Este modo permite superar la debilidad del modo EBC. En este modo, cada bloque de texto claro se combina con el bloque anterior de texto cifrado usando la operación XOR bit a bit. El resultado es un bloque de texto cifrado. Si se produce un error en un bloque el error se propaga en los dos siguientes bloques que son descifrados. Si un bloque completo se pierde durante la transmisión solo el siguiente bloque se ve afectado cuando se vaya a descifrar el mensaje. Debido a que el primer bloque de texto no tiene bloque anterior cifrado, se usa un vector de inicialización el cual usualmente tiene datos aleatorios. El vector de inicialización debe ser transmitido al receptor. CFB: Cipher Feedback. Permite a un sistema criptográfico por bloques actuar como un sistema por flujos, haciendo posible cifrar datos que son más pequeños que el tamaño del bloque. Es similar al CBC y usa un vector de inicialización. Sin embargo, la operación XOR ocurre después de cifrar el bloque de texto anterior. Usualmente el modo CFB es usado para cifrar un byte a la vez por lo que también es conocido como CFB8. Si ocurre un error en un bloque, el error es propagado en un número de bloques que depende del tamaño del bloque.

4.1.2 Esquema de Padding La implementación de un sistema criptográfico por bloques origina un problema: La longitud del mensaje en texto claro no siempre es múltiplo del tamaño del bloque (usualmente 64 bits). En ocasiones el último bloque debe


Introducción a las técnicas modernas de criptografía con ejemplos en Java

27

ser rellenado para formar un bloque que pueda ser cifrado. Un esquema de padding (relleno) especifica exactamente cómo debe ser completado el último bloque antes de cifrar los datos. Cuando se va a descifrar es necesario remover el padding para recuperar el texto original, respetando su longitud. El esquema de padding más utilizado es el PCKS#5 [5]. PKCS#5: Public-Key Cryptography Standard #5 es un posible esquema de padding. PKCS es un estándar autoproclamado por RSA Data Security, Inc. El método de padding es bastante sencillo. Se debe completar el último bloque con bytes que contienen el número de bloques que faltan para completar el bloque final. Por ejemplo, en un bloque de 64 bits, si hay cuatro bytes en el último bloque, entonces se rellena con cuatro bytes con valor 4. Si el último bloque es de un byte de longitud, entonces se debe rellenar con siete bytes con valor 7. Para evitar la ambigüedad cuando la longitud del mensaje es múltiplo de 64 bits, entonces se debe rellenar con ocho bytes con valor 8. De este modo se puede remover el padding sin ambigüedad recuperando el tamaño original del texto claro. La figura 7 muestra el último bloque de un texto claro y el relleno de acuerdo con el esqueam de padding PKCS#5.

8

4

4

4

4

7

7

7

7

7

7

7

8

8

8

8

8

8

8

Figura 7. Esquema de padding PKC#5


28

4.2 Algoritmos simétricos Los algoritmos de cifrado de clave simétrica utilizan la misma clave para cifrar y descifrar y, como ya se ha mencionado, se construyen a partir de operaciones de transposición y sustitución de bloques de bits. Los algoritmos más significativos son DES, 3DES, AES y RC4, los cuales se describen brevemente a continuación.

4.2.1 DES – Data Encryption Standard Data Encryption Standard (DES) es un algoritmo de cifrado simétrico, desarrollado por IBM en 1976, y cuyo uso se ha propagado ampliamente por todo el mundo. DES rápidamente fue adoptado en la industria, especialmente en el sector bancario, aunque por longitud de su clave, relativamente corta, no es considerado seguro [7]. El texto claro se cifra en bloques de 64 bits, produciendo 64 bits de texto cifrado. El algoritmo es parametrizado mediante una clave de 56 bits y tiene 19 etapas. La primera etapa es una transposición del texto claro, independiente de la clave. La última etapa es el inverso exacto de la primera transposición. La etapa previa a la última intercambia los 32 bits de la izquierda y los 32 bits de la derecha. Las 16 etapas restantes son funcionalmente idénticas, pero utilizan una clave derivada de la clave original. Para descifrar el texto, se ejecutan los mismos pasos en orden inverso [7].

4.2.2 Triple DES (3DES) Una vez identificadas las debilidades del algoritmo DES, se buscó un mecanismo para incrementar la longitud de la clave. La solución encontrada fue


Introducción a las técnicas modernas de criptografía con ejemplos en Java

29

utilizar el cifrado DES tres veces con dos claves distintas de 56 bits, formando una clave de 112 bits. En 3DES se usan tres etapas y un solo algoritmo. En la primera etapa, se cifra el texto claro usando DES en su forma original y la clave K1. Luego, se usa DES en modo descifrado con la clave K2 y finalmente se vuelve a cifrar usando K1. Para descifrar el texto cifrado, se procede de manera inversa. La parte a) de la figura corresponde al proceso de cifrado y la parte b) es el proceso de descifrado [7]. La razón por la cual se cifra, descifra y luego volver a cifrar, es que si se hace K1 = K2, se tiene compatibilidad entre DES y DES.

4.2.3 AES - Advanced Encryption Standard El estándar de cifrado avanzado (AES), también conocido como Rijndael, es un esquema de cifrado por bloques adoptado como un estándar de cifrado por el gobierno de los Estados Unidos. Es un algoritmo criptográfico de clave simétrica que fue propuesto por Daemen y Rijmen a finales de los años 90, con el fin de permitir claves de 128 y 256 bits. Al igual que DES, AES utiliza sustituciones y permutaciones, así como múltiples rondas. Este algoritmo permite cifrar bloques de 128 bits [7].

4.2.4 RC4 RC4 es un algoritmo de cifrado por flujo mucho más eficiente que DES. Es usado en algunos de los protocolos más populares como Transport Layer Security (TLS/SSL) (para proteger el tráfico de Internet) y Wired Equivalent Privacy (WEP) (para añadir seguridad en las redes inalámbricas). No está recomendado su uso en los nuevos sistemas, sin embargo, algunos sistemas basados en RC4 son lo suficientemente seguros para un uso común [1].


30

4.3 Taller de programación El objetivo de este taller de programación es familiarizar al estudiante con la APIs de seguridad y criptografía que proporciona el JDK6 y con los sistemas criptográficos de llave simétrica, especialmente el algoritmo DES.

4.3.1 Actividad 1. API Java de seguridad y criptografía Antes de iniciar con el desarrollo de aplicaciones criptográficas, es importante entender la forma como se manejan los conceptos de seguridad y criptografía en Java, así como las implementaciones. La arquitectura criptográfica de Java (JCA – Java Cryptography Architecture) especifica los patrones de diseño y una arquitectura para definir conceptos y algoritmos criptográficos, de manera que los conceptos son separados de las implementaciones. Los conceptos están encapsulados en sus respectivas clases en los paquetes java.security y javax.crypto, mientras que las implementaciones son proporcionadas por proveedores criptográficos. A partir de la versión 1.2, el JDK incluye un proveedor por defecto que incluye las implementaciones de algunos algoritmos criptográficos [4] y [5].

Por su parte, la extensión de criptografía de Java (JCE – Java Cryptography Extension), es una extensión de la arquitectura criptográfica de Java. Su nombre obedece a una división de los algoritmos criptográficos que hace Sun Microsystems en dos grupos debido a ciertas limitaciones que el gobierno de los Estados Unidos impone a la exportación de tecnología criptográfica. Entonces, las clases incluidas en el paquete java.security hacen parte del JDK y


Introducción a las técnicas modernas de criptografía con ejemplos en Java

31

pueden ser exportadas sin limitación alguna. Para la JCE existe otro proveedor llamado SunJCE, el cual proporciona otros algoritmos, definido en el paquete javax.crypto [4] y [5]. Sistemas criptográficos simétricos La clase javax.crypto.Cipher encapsula un sistema critpográfico que puede cifrar o descifrar datos y puede ser aplicado tanto a altoritmos simétricos como asimétricos. Cipher es una clase abstracta que no puede ser instanciada directamente, por lo que se necesita utilizar algún método de fábrica para obtener una instancia. Para crear un objeto Cipher es necesario ejecutar tres pasos: • Obtener una instancia usando el método de fábrica getInstance ( ). • Inicializar el sistema criptográfico para cifrar o descifrar datos usando el método init ( ), especificando el modo de cifrado y la llave secreta. • Cifrar o descifrar los datos usando los métodos update ( ) y doFinal ( ). Administración de llaves La administración de llaves es el principal desafío para los desarrolladores. La interface java.security.Key encapsula una llave criptográfica. Esta interface define solo tres métodos: public String getAlgorithm( ). Este método retorna el nombre del algoritmo criptográfico para el cual será usada esta llave.


32 public byte[ ] getEncoded( ). Una llave se expresa como un byte [ ] utilizando un formato específico. El método getEncoded ( ) permite obtener el valor codificado de la llave. public String getFormat( ). Este método retorna el nombre del formato utilizado para codificar la llave. La interface SecretKey del paquete javax.crypto extiende de la interface Key. Esta interface representa una llave secreta que puede ser usada por un sistema criptográfico simétrico y no define ningún método adicional. Generación de llaves La clase KeyGenerator es usada para la generación de llaves aleatorias. Para generar una llave simétrica se realizan tres acciones: • Obtener el objeto generador de llaves para el algoritmo que se desea utilizar; • Inicializar el generador de llaves; y • Solicitar la llave al generador. Un objeto generador de llaves se debe crear a través del método de fábrica getInstance( ). Por ejemplo para crear un generador de llaves para el algoritmo DES, se debe pasar el nombre del algoritmo como parámetro: KeyGenerator keyGenerator = KeyGenerator.getInstance("DES"); Algunos algoritmos usados en los sistemas criptográficos simétricos manejan llaves de longitud fija mientras que otros usan llaves de longitud variable. La


Introducción a las técnicas modernas de criptografía con ejemplos en Java

33

longitud de la llave, si aplica, se especifica en el proceso de inicialización del generador de llaves. Después de inicializar el generador de llaves, es posible obtener una llave mediante el llamado al método generateKey ( ). Por ejemplo, para generar una llave que se pueda usar con el algoritmo DES, es necesario hacer lo siguiente: SecretKey secretKey = keyGenerator.generateKey ( ); Las actividades propuestas a continuación, permiten crear un sistema criptográfico simétrico. Además, es posible cifrar y descifrar mensajes de texto y objetos. Al final se deja planteado un proyecto pequeño para el desarrollo de una aplicación más interesante. Aunque se sugiere el uso del algoritmo DES, el modo ECB y el esquema de padding PKCS#5, es posible realizar modificaciones al código, utilizar otros algoritmos simétricos y otros modos de encadenamiento de bloques.

4.3.2 Actividad 2. Cifrado de mensajes de texto Considere el siguiente esquema de paquetes para el desarrollo de los ejercicios a continuación, tal como se puede apreciar en la figura 8.

Figura 8. Esquema de paquetes para el taller de programación


34 El paquete cipher es usado para alojar las clases que definen los diferentes sistemas criptográficos que se van a desarrollar durante las diversas actividades de programación de este trabajo. El paquete test va a contener todas las clases ejecutables que permiten hacer demostraciones y pruebas de los sistemas criptográficos creados y el paquete util va a ser usado para definir algunos métodos complementarios. La actividad consiste en construir un sistema criptográfico simétrico a partir de las clases SymmetricCipher.java y Util.java suministrados a continuación. Porsteriormente escriba el código fuente del programa de prueba SymmetricCipherTest01.java. SymmetricCipher.java package cipher; import javax.crypto.Cipher; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; public class SymmetricCipher { private SecretKey secretKey; private Cipher cipher; public SymmetricCipher ( SecretKey secretKey, String transformation ) { this.secretKey = secretKey; try { cipher = Cipher.getInstance ( transformation ); } catch ( Exception e ) { System.err.println ( e.getMessage ( ) ); }


Introducción a las técnicas modernas de criptografía con ejemplos en Java

35

} }

El método constructor de la clase SymmetricCipher recibe por parámetro la llave secreta y una transformación. La llave secreta será suministrada desde la aplicación que cree el sistema criptográfico simétrico y la transformación es una cadena de caracteres que especifica el algoritmo utilizado para cifrar los datos, el modo de cifrado y el esquema de padding. Por ejemplo: "DES/ECB/PKCS5Padding". Esta transformación debe ser suministrada también desde la aplicación que crea el sistema criptográfico. public byte [ ] encryptMessage ( String input ) { byte [ ] cipherText = null; byte [ ] clearText = input.getBytes ( ); try { cipher.init ( Cipher.ENCRYPT_MODE, secretKey ); cipherText = cipher.doFinal ( clearText ); } catch ( Exception e ) { System.out.println ( e.getMessage ( ) ); } return cipherText; }

El método encryptMessage ( ) recibe por parámetro un String con el mensaje que se desea cifrar; lo convierte a byte [ ] usando el método getBytes ( ) de la clase String; inicializa el sistema criptográfico con el método init ( ) en modo cifrar (ENCRYPT_MODE) y cifra los datos con el método doFinal ( ). Retorna un byte [ ] con el mensaje cifrado. public String decryptMessage ( byte [ ] input ) { String output = "";


36 try { cipher.init ( Cipher.DECRYPT_MODE, secretKey ); byte [ ] clearText = cipher.doFinal ( input ); output = new String ( clearText ); } catch ( Exception e ) { System.err.println ( e.getMessage ( ) ); } return output; }

De forma similar, el método decryptMessage ( ) recibe un byte [ ] con un String cifrado; inicializa el sistema criptográfico con el método init ( ) en modo descifrar (DECRYPT_MODE); descifra los datos con el método doFinal ( ) y crea un String con el texto claro a partir del byte [ ] descrifrado y lo retorna. Note que la llave secreta con la que fue creado el sistema criptográfico será usada tanto para cifrar como para descifrar los datos. public SecretKey getKey ( ) { return secretKey; }

El método getKey ( ) permite obtener la llave secreta con la cual se ha creado el sistema criptográfico de llave simétrica. Util.java La clase Util.java contiene algunos métodos que se utilizarán en diferentes ejemplos y ejercicios durante los talleres de programación.


Introducción a las técnicas modernas de criptografía con ejemplos en Java

37

package util; public class Util { public static void printByteArrayInt ( byte [ ] byteArray ) { System.out.println ( "{" + byteArrayIntToString ( byteArray ) + "}" ); } }

El método printByteArrayInt ( ) permite imprimir en la consola el contenido de un byte [ ] que ingresa por parámetro. La salida se especifica como un conjunto de números enteros. Observe que la salida de este método está delimitada por símbolos { y }. Este método hace uso del método byteArrayIntToString ( ) que permite extresar el contenido de un byte [ ] como un String, el cual se define a continuación. public static String byteArrayIntToString ( byte [ ] byteArray ) { String out = ""; for ( int i = 0; i < byteArray.length; i++ ) { out += byteArray [ i ] + 128 + " "; } return out; }

Como ya se mencionó, el método byteArrayIntToString ( ) es usado para expresar el contenido de un byte [ ] como un conjunto de números enteros en un String. A cada valor se le suma 128 y se separa con un espacio en blanco, para efectos de presentación. Esto simplemente es para evitar valores negativos que puedan pertenecer al byte [ ].


38 A continuaci贸n, escriba el programa SymmetricCipherTest01.java. Este programa permite crear una instancia del sistema criptogr谩fico sim茅trico utilizando el algoritmo DES y hacer una demostraci贸n. SymmetricCipherTest01.java package test; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; import util.Util; import cipher.SymmetricCipher; public class SymmetricCipherTest01 { public static void main ( String [ ] args ) { SecretKey secretKey = null; try { secretKey = KeyGenerator.getInstance ( "DES" ).generateKey ( ); } catch ( Exception e ) { System.err.println ( e.getMessage ( ) ); } SymmetricCipher s = new SymmetricCipher ( secretKey, "DES/ECB/PKCS5Padding" ); String clearText = "Bienvenidos a la criptografia!"; byte [ ] cipherText = null; System.out.println ( clearText ); cipherText = s.encryptMessage ( clearText ); Util.printByteArrayInt ( cipherText ); clearText = s.decryptMessage ( cipherText ); System.out.println ( clearText );


Introducción a las técnicas modernas de criptografía con ejemplos en Java

39

} }

Ejecute la aplicación. Por favor responda las siguientes preguntas: 1. ¿Cuál es el mensaje cifrado? Escriba los primeros 5 elementos del byte [ ] de texto cifrado. ________________________________________________________________ 2. ¿Cuál es la longitud del mensaje en texto claro y del byte [ ] de texto cifrado? ________________________________________________________________ 3. Ejecute nuevamente el programa. Escriba los primeros cinco elementos del byte [ ] que representa el mensaje cifrado que obtuvo en la segunda ejecución. ________________________________________________________________ 4. ¿Pudo obtener alguna conclusión con respecto a las llaves utilizadas después de ejecutar dos veces la aplicación? ________________________________________________________________ ________________________________________________________________ Mida el tiempo transcurrido durante el proceso de cifrado. Utilice los ejemplos a continuación de acuerdo con la unidad de tiempo que desee. Para medir en milisegundos: long timebefore = System.currentTimeMillis(); long timeafter = System.currentTimeMillis(); long elapsedTime = timeafter – timebefore ; System.out.println ( "Tiempo transcurrido: " + elapsedTime );

Para medir en nanosegundos: long timebefore = System.nanoTime (); long timeafter = System.nanoTime (); long elapsedTime = timeafter – timebefore ;


40 System.out.println ( "Tiempo transcurrido: " + elapsedTime );

5. ¿Cuánto tiempo tardó el proceso de cifrado? ________________________________________________________________

Cifre un mensaje de un 300 caracteres de longitud. Utilice el siguiente código: char [ ] text = new char [ 300 ]; for ( int i = 0; i < text.length; i++ ) { text [ i ] = 'a'; } String clearText = new String ( text );

6. ¿Cuál es la longitud del byte [ ] de texto cifrado para el nuevo mensaje? ________________________________________________________________ 7. ¿Cuánto tardó el proceso de cifrado en esta ocasión? ________________________________________________________________ 8. Utilice un ciclo anidado para crear mensajes de texto claro de longitud variable, iniciando con una letra, luego dos letras, luego tres, y así sucesivamente hasta 30 letras. ¿Qué conclusión puede obtener de la longitud del texto claro con respecto a la longitud del texto cifrado? ________________________________________________________________ ________________________________________________________________


Introducción a las técnicas modernas de criptografía con ejemplos en Java

41

4.3.3 Actividad 3. Cifrado de objetos Adicione a la clase SymmetricCipher.java los siguientes métodos: public byte [ ] encryptObject ( Object input ) { byte [ ] cipherObject = null; byte [ ] clearObject = null; try { clearObject = Util.objectToByteArray ( input ); cipher.init ( Cipher.ENCRYPT_MODE, secretKey ); cipherObject = cipher.doFinal ( clearObject ); } catch ( Exception e ) { System.err.println ( e.getMessage ( }

) );

return cipherObject; }

El método encryptObject ( ) es muy similar al método encryptMessage ( ) de la actividad 2. Este método recibe por parámetro un Object con el mensaje que se desea cifrar. Al igual que en el método encryptMessage ( ), el objeto debe ser serializado (convertido a byte [ ]). El método objectToByteArray ( ) en la clase Util permite serializar el objeto. El resto de instrucciones son iguales al método encryptMessage ( ). Retorna un byte [ ] con el mensaje cifrado. public Object decryptObject ( byte [ ] input ) { Object output = null; try { cipher.init ( Cipher.DECRYPT_MODE, secretKey ); byte [ ] clearObject = cipher.doFinal ( input ); output = Util.byteArrayToObject ( clearObject );


42 } catch ( Exception e ) { System.err.println ( e.getMessage ( }

) );

return output; }

El método decryptObject ( ) recibe un byte [ ] con un Objeto cifrado. El proceso es muy parecido al del método decryptMessage ( ) de la actividad 2. En este caso, es necesario deserializar el objeto y para esto se utiliza el método Util.byteArrayToObject ( ). El byte [ ] que se recibe por parámetro se descrifra y se obtiene otro byte [ ] con el objeto serializado pero en texto claro. Luego, el objeto se deserializa y se retorna. Para utilizar este método, debe recibirse el objeto usando un cast al tipo de objeto esperado. Ahora, es necesario adicionar los métodos Util.objectToByteArray ( ) y Util.byteArrayToObject ( ) a la clase Util.java. No olvide importar la clase Util dentro de la clase SymmetricCipher.java, usando import util.Util; en la parte superior del código de la clase. public static byte [ ] objectToByteArray ( Object o ) throws IOException { ByteArrayOutputStream bos = new ByteArrayOutputStream ( ); ObjectOutputStream out = new ObjectOutputStream ( bos ) ; out.writeObject ( o ); out.close ( ); byte [ ] buffer = bos.toByteArray ( ); return buffer; }

Para serializar un objeto se crea un flujo de salida de objetos conectado con un flujo de salida hacia un byte [ ]. Luego, se escribe el objeto que se recibe por


Introducción a las técnicas modernas de criptografía con ejemplos en Java

43

parámetro en el flujo de salida de objetos. La salida es el llamado al método toByteArray ( ) del flujo de salida hacia el byte [ ]. public static Object byteArrayToObject ( byte [ ] byteArray ) throws Exception { ObjectInputStream in = new ObjectInputStream ( new ByteArrayInputStream ( byteArray ) ); Object o = in.readObject ( ); in.close ( ); return o; }

Para deserializar un objeto, es decir, obtener el objeto a partir de un byte [ ], se crea un flujo de entrada de objetos conectado con un flujo de entrada desde un byte [ ], se lee el objeto desde el flujo de entrada de objetos y se retorna. Como ya se mencionó, debe ser recibido por el tipo de dato adecuado y utilizar un cast. No olvide importar las clases: import import import import import

java.io.ByteArrayOutputStream; java.io.IOException; java.io.ObjectOutputStream; java.io.ByteArrayInputStream; java.io.ObjectInputStream;

A continuación escriba el código del programa SymmetricCipherTest02.java. SymmetricCipherTest02.java package test; import java.util.Vector; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey;


44 import util.Util; import cipher.SymmetricCipher; public class SymmetricCipherTest02 { public static void main ( String [ ] args ) { SecretKey secretKey = null; try { secretKey = KeyGenerator.getInstance ( "DES" ).generateKey ( ); } catch ( Exception e ) { System.err.println ( e.getMessage ( ) ); } SymmetricCipher s = new SymmetricCipher ( secretKey, "DES/ECB/PKCS5Padding" ); Vector < String > clearObject = new Vector < String > ( ); clearObject.add clearObject.add clearObject.add clearObject.add clearObject.add

( ( ( ( (

"Ana" ); "Bety" ); "Carolina" ); "Daniela" ); "Elena" );

byte [ ] cipherObject = null; System.out.println ( clearObject ); cipherObject = s.encryptObject ( clearObject ); Util.printByteArrayHexadecimal ( cipherObject ); clearObject = ( Vector < String > ) s.decryptObject ( cipherObject ); System.out.println ( clearObject ); } }

Observe que en la aplicación de prueba se está utilizando el método printByteArrayHexadecimal ( ) de la clase Util. Este método permite imprimir en la consola el contenido del byte [ ] en formato hexadecimal. Observe que


Introducción a las técnicas modernas de criptografía con ejemplos en Java

45

utiliza el método byteArrayHexadecimalToString ( ), el cual también debe ser adicionado a la clase Util. public static void printByteArrayHexadecimal ( byte [ ] byteArray ) { System.out.println ( "{" + byteArrayHexadecimalToString ( byteArray ) + "}"); }

El método printByteArrayHexadecimal ( ) permite imprimir en la consola el contenido de un byte [ ] que ingresa por parámetro. La salida se especifica como un conjunto de números enteros en formato hexadecimal. Observe que la salida de este método está delimitada por símbolos { y }. Este método hace uso del método byteArrayHexadecimalToString ( ) que permite extresar el contenido de un byte [ ] como un String, el cual se define a continuación. public static String byteArrayHexadecimalToString ( byte [ ] byteArray ) { String out = ""; for ( int i = 0; i < byteArray.length; i++ ) { if ( ( byteArray [ i ] & 0xff ) <= 0xf ) { out += "0"; } out += Integer.toHexString ( byteArray [ i ] & 0xff ) + " "; } return out; }

Como ya se mencionó, el método byteArrayHexadecimalToString ( ) es usado para expresar el contenido de un byte [ ] como un conjunto de números enteros en formato hexadecimal en un String. A cada valor se le aplica una máscara 0xff (ff en hexadecimal) para obtener solamente los dos dígitos menos


46 significativos y se separa con un espacio en blanco, para efectos de presentación. Ahora, ejecute la aplicación SymmetricCipherTest02.java y responda las siguientes preguntas: 1. En este caso, se está cifrando un objeto y no un mensaje Describa el objeto que va a ser cifrado. ________________________________________________________________ 2. ¿Cuál es la longitud del byte [ ] de objeto cifrado? ________________________________________________________________ 3. ¿Cuánto tardó el proceso de cifrado del objeto? ________________________________________________________________ 4. Modifique el objeto para que ahora sea de un tamaño equivalente al doble del anterior. ¿Cuál es la longitud del byte [ ] de objeto cifrado? ________________________________________________________________ 5. ¿Cuánto tardó ahora este proceso? ________________________________________________________________


Introducción a las técnicas modernas de criptografía con ejemplos en Java

47

6. ¿Pudo obtener alguna conclusión después del segundo ejemplo? ________________________________________________________________

4.3.4 Actividad 4. Miniproyecto Desarrolle una aplicación interactiva que permita generar llaves simétricas, guardarlas en archivo, cargarlas desde un archivo, cifrar y descifrar texto. Puede crear una interfaz gráfica de usuario similar a que se muestra a continuación.

Figura 9. GUI Miniproyecto de Criptografía de Llave simétrica Los requerimientos funcionales del miniproyecto son los siguientes: • Generar una llave simétrica con el algoritmo DES, retroalimentación EBC y con PKCS5 como esquema de padding. • Guardar la llave en un archivo.


48 • Cargar una llave desde un archivo. • Cifrar un mensaje. • Descifrar un mensaje. Utilice el siguiente código para almacenar y cargar una llave secreta hacia y desde un archivo usando el concepto de serialización de objetos de Java. Guardar: FileOutputStream fileOut; ObjectOutputStream out; try { fileOut = new FileOutputStream ( fileName ); out = new ObjectOutputStream ( fileOut ); out.writeObject ( secretKey ); out.flush ( ); out.close ( ); } catch ( Exception e ) { }

Cargar: FileInputStream fileIn; ObjectInputStream in; try { fileIn = new FileInputStream ( fileName ); in = new ObjectInputStream ( fileIn ); secretKey = ( SecretKey ) in.readObject ( ); } catch ( Exception e ) { }


Introducción a las técnicas modernas de criptografía con ejemplos en Java

49

4.3.5 Actividad 5. Requisito adicional miniproyecto Adicione al miniproyecto una opción que permita cargar un archivo de texto para cifrar su contenido, el cual deberá ser enviado como flujo de bytes a un archivo binario. Debe existir la posibilidad de descifrar un archivo cifrado y enviar el texto claro a un archivo de texto. Al terminar, debe aparecer en la ventana la cantidad de tiempo que ha tardado el proceso, en minutos, segundos, milisegundos o nanosegundos, de acuerdo con el tamaño del archivo, en unidades que sean entendidas por usuarios.


50


Introducción a las técnicas modernas de criptografía con ejemplos en Java

51

5 Codificación Base64 Base64 es un esquema que permite transformar una secuencia de bytes (un byte [ ]) en un formato que use únicamente caracteres imprimibles. Es usado en aplicaciones donde se desea evitar confusión entre los datos que se están enviando de un lugar a otro y los delimitadores de acuerdo al protocolo. Por ejemplo, al cifrar o comprimir un archivo para enviarlo por correo electrónico es posible que en el archivo aparezca la secuencia de bits que puedan confundir con <cr><lf>.<cr><lf> que indica el final de un de mensaje de correo electrónico.

5.1 Alfabeto Base 64 La tabla a continuación permite apreciar el esquema de codificación Base64. Se pueden identificar dos columnas donde Valor corresponde a los números entre 0 y 63 mientras que Código se construye con las 26 letras mayúsculas seguidas por las 26 letras minúsculas, los dígitos del 0 al 9 y los caracters ‘+’ y ‘/’.


52

Figura 10. Alfabeto Base64

5.2 Transformación Para transformar datos, se toma un espacio de memoria (un buffer) de 24 bits. Cada grupo de tres bytes se ubica en el buffer de tal manera que el primer byte se pone en los 8 bits más significativos (a la izquierda), el segundo byte en los 8 del medio (en el centro) y el tercer byte en los bits menos significativos (a la derecha). Luego, se sacan cuatro grupos de seis bits de izquierda a derecha. Si hay menos de 3 bytes por codificar, se rellena con ceros el buffer hasta completar un número de bits múltiplo de 6.


Introducción a las técnicas modernas de criptografía con ejemplos en Java

53

Cada grupo de seis bits se convierte a su equivalente decimal para encontrar un número entre 0 y 63. Se busca en la tabla y se reemplaza por el carácter codificado y se añade a la cadena de salida. El proceso se repite con todos los bytes que queden hasta el final, si la longitud de los datos es múltiplo de 3 bytes. Si en el paso anterior quedaban dos bytes por codificar, entonces se añade el carácter '=' al final de la cadena de salida. Si solo quedaba un byte, se añaden al final de la cadena de salida dos caracteres '=‘, es decir, “==". El padding (el o los caracteres ‘=‘) permite que al decodificar se puedan eliminar los bits de relleno para recuperar la secuencia de bytes original sin ambigüedad.

5.3 Ejemplo 1 Codifique en Base64 el mensaje “Libros”. Primero, busque en la tabla de códigos ASCII los valores correspondientes a los caracteres que conforman la palabra “Libros”.


54

Figura 11. Table de códigos ASCII Los códigos hexadecimales de los símbolos o caracteres del mensaje a codificar, son: ‘L’: 4C; ‘i’: 69; ‘b’:62; ‘r’: 72; ‘o’:6F y ‘s’: 73.

Texto de entrada Código ASCII

L 4

Texto de entrada Código ASCII

i C

6

r 7

b 9

6

o 2

6

2

S F

7

3

A continuación, se debe convertir a binario cada uno de los números hexadecimales encontrados en la tabla ASCII correspondientes al mensaje que se desea codificar.


55

Introducción a las técnicas modernas de criptografía con ejemplos en Java

Código ASCII Binario (8 bits)

4

6

9

6

2

0 1 0 0 1 1 0 0 0 1 1 0 1 0 0 1 0 1 1 0 0 0 1 0

Código ASCII Binario (8 bits)

C

7

2

6

F

7

3

0 1 1 1 0 0 1 0 0 1 1 0 1 1 1 1 0 1 1 1 0 0 1 1

El siguiente paso es formar grupos de seis bits y convertir cada grupo en su equivalente decimal, donde cada número es mínimo 0 y máximo 63. Binario (8 bits)

0 1 0 0 1 1 0 0 0 1 1 0 1 0 0 1 0 1 1 0 0 0 1 0

Decimal (6 bits)

Binario (8 bits)

19

6

37

34

0 1 1 1 0 0 1 0 0 1 1 0 1 1 1 1 0 1 1 1 0 0 1 1

Decimal (6 bits)

28

38

61

51

Luego, busque en la tabla del alfabeto Base64 para encontrar el símbolo correspondiente a cada grupo. Decimal (6 bits)

19

6

37

34

Base64

T

G

l

I

Decimal (6 bits)

28

38

61

51

Base64

c

m

9

z

En resumen, si el texto de entrada es: “Libros”, se convierte en Base64 a la cadena “TGlicm9z”.


56

Entrada

Libros

Texto de entrada

L

Código ASCII Binario (8 bits)

4

i C

6

b 9

6

2

0 1 0 0 1 1 0 0 0 1 1 0 1 0 0 1 0 1 1 0 0 0 1 0

Decimal (6 bits)

19

6

37

34

Base64

T

G

l

I

Texto de entrada

r

Código ASCII Binario (8 bits)

7

o 2

6

S F

7

3

0 1 1 1 0 0 1 0 0 1 1 0 1 1 1 1 0 1 1 1 0 0 1 1

Decimal (6 bits)

28

38

61

51

Base64

C

m

9

z

Salida

TGlicm9z

5.4 Ejemplo 2 Codifique en Base64 el mensaje “Hola”. En este ejemplo no hay una explicación detallada, simplemente se ha desarrollado el procedimiento.


57

Introducción a las técnicas modernas de criptografía con ejemplos en Java

Entrada

Hola

Texto de entrada

H

Código ASCII Binario (8 bits)

4

o 8

6

l F

6

C

0 1 0 0 1 0 0 0 0 1 1 0 1 1 1 1 0 1 1 0 1 1 0 0

Decimal (6 bits)

18

6

61

44

Base64

S

G

9

s

=

=

Texto de entrada

A

Código ASCII Binario (8 bits)

6

1

0 1 1 0 0 0 0 1 0 0 0 0

Decimal (6 bits)

24

16

Base64

Y

Q

Salida

SG9sYQ==

Observe que al terminar de codificar se presenta sobrante, razón por la cual se debe usar el símbolo “=” dos veces como padding.

5.5 Ejemplo 3 Codifique en Base64 el mensaje “Amigo”.


58

Entrada

Amigo

Texto de entrada Código ASCII Binario (8 bits)

A 4

m 1

6

i 0

6

9

0 1 0 0 0 0 0 1 0 1 1 0 0 0 0 0 0 1 1 0 1 0 0 1

Decimal (6 bits)

16

22

53

41

Base64

Q

W

1

p

Texto de entrada Código ASCII Binario (8 bits)

g 6

o 7

6

F

0 1 1 0 0 1 1 1 0 1 1 0 1 1 1 1 0 0

Decimal (6 bits)

25

54

60

Base64

Z

2

8

Salida

=

QWqpZ28=

Al igual que en el ejemplo anterior, en este caso también se presenta sobrante y se hace necesario usar padding, aunque solo es de un símbolo “=”.

5.6 Aplicaciones de la codificación Base 64 La codificación Base64 es usada para convertir los archivos adjuntos que se envían en un correo electrónico para evitar la terminación inoportuna del mensaje. También para el esquema de autenticación básica en el protocolo HTTP y en la manipulación de certificados digitales.


Introducción a las técnicas modernas de criptografía con ejemplos en Java

5.7 Taller de codificación 1. Codifique los siguientes textos: • Crypto Entrada

Texto de entrada Código ASCII Binario (8 bits) Decimal (6 bits) Base64

Texto de entrada Código ASCII Binario (8 bits) Decimal (6 bits) Base64

Salida

Crypto

59


60 โ ข Security Entrada

Texto de entrada Cรณdigo ASCII Binario (8 bits) Decimal (6 bits) Base64

Texto de entrada Cรณdigo ASCII Binario (8 bits) Decimal (6 bits) Base64

Texto de entrada Cรณdigo ASCII Binario (8 bits) Decimal (6 bits) Base64

Salida

Security


Introducción a las técnicas modernas de criptografía con ejemplos en Java

2. Decodifique la secuencia de caracteres que corresponde a un texto: • QWxhZGlubzpBYnJldGUgU2VzYW1v Entrada

Texto de entrada Código ASCII Binario (8 bits) Decimal (6 bits) Base64

Texto de entrada Código ASCII Binario (8 bits) Decimal (6 bits) Base64

Texto de entrada Código ASCII Binario (8 bits) Decimal (6 bits) Base64

Texto de entrada Código ASCII Binario (8 bits) Decimal (6 bits)

Security

61


62 Base64

Texto de entrada C贸digo ASCII Binario (8 bits) Decimal (6 bits) Base64

Texto de entrada C贸digo ASCII Binario (8 bits) Decimal (6 bits) Base64

Texto de entrada C贸digo ASCII Binario (8 bits) Decimal (6 bits) Base64

Salida


Introducción a las técnicas modernas de criptografía con ejemplos en Java

63

3. Codifique la siguiente secuencia de bits: •

0000 0011 1100 0011 1111 1100 1101 1001 1101 1110 1010 0001 1001 1110

Entrada

0000 0011 1100 0011 1111 1100 1101 1001 1101 1110 1010 0001 1001 1110

Binario (8 bits) Decimal (6 bits) Base64

Binario (8 bits) Decimal (6 bits) Base64

Binario (8 bits) Decimal (6 bits) Base64

Binario (8 bits) Decimal (6 bits) Base64

Binario (8 bits) Decimal (6 bits) Base64


64

Binario (8 bits) Decimal (6 bits) Base64

Binario (8 bits) Decimal (6 bits) Base64

Nota: Puede usar el siguiente URL para convertir texto ASCII en su equivalente Base64: http://www.securitystats.com/tools/base64.php.

5.8 Taller de programaci贸n 5.8.1 Clase Base64.java Escriba la clase Base64.java suministrada a continuaci贸n, tomada de [5]: http://examples.oreilly.com/javacrypt/files/oreilly/jonathan/util/Base64.java. Incluya esta clase en el paquete util. Base64.java package util; public class Base64 { public static String encode ( byte [ ] raw ) { StringBuffer encoded = new StringBuffer ( ); for ( int i = 0; i < raw.length; i += 3 ) { encoded.append ( encodeBlock ( raw, i ) );


Introducción a las técnicas modernas de criptografía con ejemplos en Java } return encoded.toString ( ); } protected static char [ ] encodeBlock ( byte [ ] raw, int offset ) { int block = 0; int slack = raw.length - offset - 1; int end = ( slack >= 2 ) ? 2 : slack; for ( int i = 0; i <= end; i++ ) { byte b = raw [ offset + i ]; int neuter = ( b < 0 ) ? b + 256 : b; block += neuter << ( 8 * ( 2 - i ) ); } char [ ] base64 = new char [ 4 ]; for ( int i = 0; i < 4; i++ ) { int sixbit = ( block >>> ( 6 * ( 3 - i ) ) ) & 0x3f; base64 [ i ] = getChar ( sixbit ); } if ( slack < 1 ) base64 [ 2 ] = '='; if ( slack < 2 ) base64 [ 3 ] = '='; return base64; } protected static char getChar ( int sixBit ) { if ( sixBit >= 0 && sixBit <= 25 ) return ( char ) ( 'A' + sixBit ); if ( sixBit >= 26 && sixBit <= 51 ) return ( char ) ( 'a' + ( sixBit - 26 ) ); if ( sixBit >= 52 && sixBit <= 61 ) return ( char ) ( '0' + ( sixBit - 52 ) ); if ( sixBit == 62 ) return '+'; if ( sixBit == 63 ) return '/'; return '?'; }

65


66 public static byte [ ] decode ( String base64 ) { int pad = 0; for ( int i = base64.length ( ) - 1; base64.charAt ( i ) == '='; i-- ) { pad++; } int length = base64.length ( ) * 6 / 8 - pad; byte [ ] raw = new byte [ length ]; int rawIndex = 0; for ( int i = 0; i < base64.length ( ); i { int block = ( getValue ( base64.charAt + ( getValue ( base64.charAt ( i + 1 ) + ( getValue ( base64.charAt ( i + 2 ) + ( getValue ( base64.charAt ( i + 3 )

+= 4 ) ( ) ) )

i ) ) << 18 ) << 12 ) << 6 ) );

for ( int j = 0; j < 3 && rawIndex + j < raw.length; j++ ) raw [ rawIndex + j ] = ( byte )( ( block >> ( 8 * ( 2 - j ) ) ) & 0xff ); rawIndex += 3; } return raw; } protected { if ( c if ( c if ( c if ( c if ( c if ( c

static int getValue ( char c ) >= >= >= == == ==

return -1; } }

'A' 'a' '0' '+' '/' '='

&& c <= 'Z' ) return c - 'A'; && c <= 'z' ) return c - 'a' + 26; && c <= '9' ) return c - '0' + 52; ) return 62; ) return 63; ) return 0;


Introducción a las técnicas modernas de criptografía con ejemplos en Java

67

5.8.2 Pruebas de la codificación Base64 Escriba una clase llamada Base64Test01.java en la cual cree un vector de cinco elementos como el utilizado en la clase SymmetricCipherTest02.java. A continuación seralice el objeto y utilice el método encode ( ) de la clase Base64 para producir el String equivalente. Luego, haga el proceso inverso, utilizando el método decode ( ) de la clase Base64, deserialice y obtenga de nuevo el vector. Base64Test01.java package test; import java.util.Vector; import util.Base64; import util.Util; public class Base64Test01 { public static void main ( String [ ] args ) throws Exception { Vector < String > names = new Vector < String > ( ); names.add names.add names.add names.add names.add

( ( ( ( (

"Ana" ); "Bety" ); "Carolina" ); "Daniela" ); "Elena" );

System.out.println ( names ); byte[] namesByteArray = Util.objectToByteArray ( names ); String namesBase64 = Base64.encode ( namesByteArray ); System.out.println( namesBase64 ); byte [ ] namesByteArray2 = Base64.decode ( namesBase64 ); Vector < String > names2 = ( Vector < String > ) Util.byteArrayToObject ( namesByteArray2 ); System.out.println ( names2 );


68 } }


Introducción a las técnicas modernas de criptografía con ejemplos en Java

69

6 Criptografía de llave pública El principal problema de la criptografía simétrica es la distribución de llaves, porque si la llave cae en manos de un intruso, se echa a perder todo el esfuerzo, sin importar qué tan robusto es el algoritmo criptográfico utilizado [7]. En 1976, Diffie y Hellman, dos investigadores de la Universidad de Stanford, propusieron un nuevo sistema de llaves, en el cual se usa una para cifrar y otra para descifrar, además, donde no es posible deducir una a partir de la otra. La criptografía asimétrica, también es conocida como criptografía de llave pública, porque la llave para cifrar se hace pública mientras que la clave para descifrar se mantiene en secreto. Si la llave privada se usa para cifrar, cualquier persona que tenga en su poder la llave pública correspondiente podrá descifrar el mensaje. En este caso, no se puede garantizar la confidencialidad del mensaje pero sí el emisor ya que solamente la llave pública de quien emitió el mensaje podrá ser usada para descifrarlo. De este modo, se resuelve el problema de la distribución de claves a través de un medio inseguro. El algoritmo RSA es el principal algoritmo conocido y aplicado en criptografía asimétrica. Otros algoritmos criptográficos de clave


70 pública son basados en curvas elípticas, en los cuales los matemáticos han estado trabajando en los últimos años.

6.1 Algoritmos de llave pública 6.1.1 RSA Rivest, Shamir y Adleman, investigadores del MIT propusieron este algoritmo en 1978 y a la fecha es considerado el más robusto. Sus principales desventajas son la longitud de la clave (generalmente 1.024 bits) y es lento, comparado con los algoritmos de clave simétrica [6] y [7]. Se fundamenta en principios de la teoría de números, especialmente en la dificultad de factorizar números grandes (alrededor de 500 dígitos), lo que se considera un problema excesivamente difícil. Una característica de RSA es que: • C K − PUB [DK − PRIV (m )] = m • C K − PRIV [DK − PUB (m )] = m • DK − PUB [C K − PRIV (m )] = m • DK − PRIV [C K − PUB (m )] = m Procedimiento para encontrar el par de llaves asimétricas El primer paso es generar dos números primos grandes llamados p y q, a partir de los cuales se calcula: n= p × q

z = ( p − 1) × (q − 1)


Introducción a las técnicas modernas de criptografía con ejemplos en Java

71

Escoja un número d cualquiera que sea primo relativo de z (que no tenga factores comunes con z), es decir, que el máximo común divisor entre d y z sea 1. Ejemplo: Se eligen p = 3, y q = 11. A partir de p y q se calculan n y z. n= 3× 11

n= 33

z= (3 − 1) × (11 − 1)

z= (2) × (10)

z= 20

A continuación, se elige d, el cual debe ser un número que no tenga factores comunes con z, entre ellos están: 3, 7, 11, 13, 17, 19. Para este caso, se ha elegido d = 3. Ahora, es necesario encontrar un número e, el cual debe satisfacer la ecuación e × d = 1 mod z . Esta ecuación puede resolverse haciendo una búsqueda manual2 Para e = 1: 3 x 1 mod 20 = 3 mod 20 = 3 Para e = 2: 3 x 2 mod 20 = 6 mod 20 = 6 Para e = 3: 3 x 3 mod 20 = 9 mod 20 = 9 Para e = 4: 3 x 4 mod 20 = 12 mod 20 = 12 Para e = 5: 3 x 5 mod 20 = 15 mod 20 = 15 Para e = 6: 3 x 6 mod 20 = 18 mod 20 = 18

2

Profesor Leonardo Hernández Rodríguez, MSc., Universidad del Quindío.


72 Para e = 7: 3 x 7 mod 20 = 21 mod 20 = 1 Otra forma de calcular el valor de e, es encontrar el número más pequeño divisible por d en la serie z + 1 , 2 × z + 1 , 3 × z + 1 , … Considerando z + 1 , el resultado es un número divisible por d = 3, luego

e = (20 + 1) , es decir e = (20 + 1) / 3 = 7 . Es decir, e = 7. Entonces, la llave pública es el conjunto de valores e = 7 y n = 33; mientras que la llave privada es el conjunto de valores d = 3 y n = 33. Para cifrar un mensaje, se usa la siguiente fórmula: C = m e mod n . Por ejemplo, si se desea cifrar un mensaje m = 5, entonces: C = 57 mod 33 = 78125 mod 33 = 14 Para descifrar, se usa la siguiente fórmula: m = C d mod n . Por ejemplo, para descifrar el mensaje cifrado c = 14, entonces: m = 14 3 mod 33 = 2744 mod 33 = 5 .

6.2 Taller de cálculo de clave pública y privada 1. Calcule el par de llaves pública y privada para los números primos

p = 13 y q = 7, con el valor escogido para d = 23. 2. Calcule los valores de p, q y d dados n = 85 y e = 55.


Introducción a las técnicas modernas de criptografía con ejemplos en Java

73

6.3 Taller de programación 6.3.1 Actividad 1. API Java de seguridad y criptografía Sistemas criptográficos de llave pública Como se mencionó en el taller de programación del capítulo 4, la clase

javax.crypto.Cipher encapsula un sistema critpográfico que puede cifrar o descifrar datos y puede ser aplicado tanto a altoritmos simétricos como asimétricos [4].

Administración de llaves Igualmente, la interface java.security.Key encapsula una llave criptográfica que también puede ser usada en la criptografía de llave pública. Las interfaces PrivateKey y PublicKey del paquete java.security extienden de la interface Key. Estas interfaces representan una llave privada y una llave pública respectivamente las cuales pueden ser usadas en un sistema criptográfico de llave pública o en un sistema de firmas digitales. Estas interfaces no definen ningún método adicional.


74

Generación de llaves El JDK suministra la clase java.security.KeyPair la cual encapsula el par de llaves, debido a que las llaves pública y privada siempre se crean juntas. La clase java.security.KeyPairGenerator crea un par de llaves asimétricas y las retorna como un KeyPair. Se puede crear un KeyPairGenerator usando uno de los

métodos

de

fábrica

getInstance ( ). Por ejemplo, KeyPairGenerator.getInstance ( “RSA” ) permite obtener un generador de llaves para aplicárselo al algoritmo RSA. A continuación, el generador de llaves necesita ser inicializado con el llamado al método initialize ( ), el cual recibe por parámetro un número entero que por lo regular hace referencia a la longitud de la llave. La interpretación de este parámetro es dependiente del algoritmo. Después de la inicialización del generador, se puede obtener un par de llaves asimétricas con el llamado al método generateKeyPair ( ). Por ejemplo, para generar un par de llaves asimétricas que se puedan usar con el algoritmo RSA (con una longitud de las llaves de 1024 bits), es necesario hacer lo siguiente: KeyPairGenerator kpg = KeyPairGenerator.getInstance ("RSA"); kpg.initialize ( 1024 ); keyPair = kpg.generateKeyPair ( ); publicKey = keyPair.getPublic ( ); privateKey = keyPair.getPrivate ( );

Observe que los métodos getPublic ( ) y getPrivate ( ) sobre el objeto de tipo

KeyPair retornan las llaves pública y privada.


Introducción a las técnicas modernas de criptografía con ejemplos en Java

75

Las actividades propuestas a continuación, permiten crear un sistema criptográfico de llave pública. Además, es posible cifrar y descifrar mensajes de texto y objetos. Debido a las particularidades y restricciones del algoritmo RSA, es necesario manipular los datos de manera que puedan ser cifrados y descifrados. Al final se deja planteado un proyecto pequeño para el desarrollo de una aplicación más interesante.

6.3.2 Actividad 2. Cifrado de mensajes de texto Para el desarrollo de este taller de programación puede continuar con el proyecto iniciado en el taller de programación del capítulo 4. La actividad consiste en construir un sistema criptográfico simétrico de llave pública a partir de las clases AsymmetricCipher.java y Util.java suministrados a continuación. Porsteriormente escriba el código fuente del programa de prueba AsymmetricCipherTest01.java.

AsymmetricCipher.java La clase AsymmetricCipher.java construye un sistema criptográfico de llave pública con el cual podrá cifrar mensajes de texto y objetos. package cipher; import java.security.Key; import java.security.KeyPair; import javax.crypto.Cipher; import util.Util; public class AsymmetricCipher


76 { private String private Cipher

algorithm; cipher;

public AsymmetricCipher ( String algorithm ) { this.algorithm = algorithm; try { cipher = Cipher.getInstance ( algorithm ); } catch ( Exception e ) { System.err.println ( e.getMessage ( ) ); } } }

El método constructor de la clase AsymmetricCipher recibe por parámetro el nombre del algoritmo, por ejemplo "RSA", para crear la instancia del sistema criptográfico de llave pública con base en el algoritmo especificado. public byte [ ] encryptMessage ( String input, Key key ) { byte [ ] cipherText = null; byte [ ] clearText = input.getBytes ( ); try { cipher.init ( Cipher.ENCRYPT_MODE, key ); cipherText = cipher.doFinal ( clearText ); } catch ( Exception e ) { System.err.println ( e.getMessage ( ) ); } return cipherText; }

El método encryptMessage ( ) recibe por parámetro un String con el mensaje que se desea cifrar y la llave que será utilizada para cifrar el mensaje; lo


77

Introducción a las técnicas modernas de criptografía con ejemplos en Java

convierte a byte [ ] usando el método getBytes ( ) de la clase String; inicializa el sistema

criptográfico

con

el

método

init ( ) en modo cifrar (ENCRYPT_MODE) y cifra los datos con el método doFinal ( ). Retorna un byte [ ] con el mensaje cifrado. public String decryptMessage ( byte [ ] input, Key key ) { String output = ""; try { cipher.init ( Cipher.DECRYPT_MODE, key ); byte [ ] clearText = cipher.doFinal ( input ); output = new String ( clearText ); } catch ( Exception e ) { System.err.println ( e.getMessage ( ) ); } return output; }

De forma similar, el método decryptMessage ( ) recibe un byte [ ] con un String cifrado y una llave que será usada para descifrar el mensaje; inicializa el sistema

criptográfico

con

el

método

init

( ) en

modo

descifrar

(DECRYPT_MODE); descifra los datos con el método doFinal ( ) y crea un

String con el texto claro a partir del byte [ ] descrifrado y lo retorna. AsymmetricCipherTest01.java El programa AsymmetricCipherTest01.java permite crear una instancia del sistema criptográfico de llave pública utilizando el algoritmo RSA y hacer una demostración cifrando y descifrando cadenas de caracteres.


78 package test; import import import import

java.security.KeyPair; java.security.KeyPairGenerator; java.security.PrivateKey; java.security.PublicKey;

import util.Util; import cipher.AsymmetricCipher; public class AsymmetricCipherTest01 { public static void main ( String [ ] args ) { KeyPair keyPair = null; KeyPairGenerator keyPairGenerator = null; String algorithm = "RSA"; PublicKey publicKey = null; PrivateKey privateKey = null; try { keyPairGenerator = KeyPairGenerator.getInstance ( algorithm ); keyPairGenerator.initialize ( 1024 ); keyPair = keyPairGenerator.generateKeyPair ( ); publicKey = keyPair.getPublic ( ); privateKey = keyPair.getPrivate ( ); } catch ( Exception e ) { System.out.println ( e.getMessage ( ) ); } AsymmetricCipher a = new AsymmetricCipher ( algorithm ); String clearText = "Bienvenidos a la criptografia!"; byte [ ] cipherText = null; System.out.println ( clearText ); cipherText = a.encryptMessage ( clearText, publicKey ); Util.printByteArrayInt ( cipherText ); clearText = a.decryptMessage ( cipherText, privateKey ); System.out.println ( clearText ); cipherText = a.encryptMessage ( clearText, privateKey );


Introducción a las técnicas modernas de criptografía con ejemplos en Java

79

Util.printByteArrayInt ( cipherText ); clearText = a.decryptMessage ( cipherText, publicKey ); System.out.println ( clearText ); } }

Este programa crea un sistema criptográfico de llave pública y a continuación cifra un mensaje con la llave pública e imprime el texto cifrado. Luego lo descifra usando la llave privada e imprime de nuevo el mensaje en texto claro. Luego, realiza el mismo procedimiento invirtiendo el uso de las llaves asimétricas. Observe que las dos llaves son complementarias y si utiliza una para cifrar, debe usar la otra para descifrar. Ejecute la aplicación. Por favor responda las siguientes preguntas: 1. ¿Qué diferencias encuentra entre las clases SymmetricCipher.java y

AsymmetricCipher.java? Puede realizar la comparación indicando las diferencias entre los métodos que cumplen funcionalidades similares. ________________________________________________________________ 2. ¿Cuál es la longitud del byte [ ] de texto cifrado con la llave pública? ________________________________________________________________ 3. ¿Cuál es la longitud del byte [ ] de texto cifrado con la llave privada? ________________________________________________________________ 4. ¿La longitud del byte [ ] de texto cifrado con la llave secreta usada en el taller de criptografía de llave simétrica es igual a la longitud del byte [ ] de texto cifrado con la llave pública? ________________________________________________________________


80 5. ¿Cuánto tarda en cifrar el mensaje con la llave pública? ________________________________________________________________ 6. ¿Cuánto tarda en cifrar con la llave privada? ________________________________________________________________ 7. ¿Cuánto tarda en descifrar el mensaje con la llave privada? ________________________________________________________________ 8. ¿Cuánto tarda en descifrar con la llave pública? ________________________________________________________________ 9. ¿Cuánto se demoró cifrar con la llave secreta usada en el taller de criptografía de llave simétrica? ________________________________________________________________

6.3.3 Actividad 3. Cifrado de Objetos Adicione a la clase AsymmetricCipher.java los siguientes métodos: public byte [ ] encryptObject ( Object input, Key key ) { byte [ ] cipherObject = null; byte [ ] clearObject = null; try { cipher.init ( Cipher.ENCRYPT_MODE, key ); clearObject = Util.objectToByteArray ( input ); cipherObject = cipher.doFinal ( clearObject ); } catch ( Exception e ) { System.out.println ( e.getMessage ( ) ); }


Introducción a las técnicas modernas de criptografía con ejemplos en Java

81

return cipherObject; }

El método encryptObject ( ) es muy similar al método encryptObject ( ) usado en el sistema criptográfico simétrico. La diferencia es que en este caso se recibe por parámetro la llave a utilizar. El método objectToByteArray ( ) en la clase

Util fue explicado en la actividad 3 del sistema criptográfico simétrico del capítulo 4. public Object decryptObject ( byte [ ] input, Key key ) { Object output = null; try { cipher.init ( Cipher.DECRYPT_MODE, key ); byte [ ] clearText = cipher.doFinal ( input ); output = Util.byteArrayToObject ( clearText ); } catch ( Exception e ) { System.err.println ( e.getMessage ( ) ); } return output; }

El método decryptObject ( ) recibe un byte [ ] con un Objeto cifrado. El proceso es muy parecido al del método decryptObject ( ) de la actividad 3 del sistema criptográfico de llave simétrica. En este caso, recibe por parámetro la llave a utilizar para descifrar el objeto. De manera similar al método anterior, se utiliza el método Util.byteArrayToObject ( ) ya explicado en el capítulo 4. Para utilizar este método, debe recibirse el objeto usando un cast al tipo de objeto esperado.


82 Escriba a continuaci贸n el c贸digo de la clase AsymmetricCipherTest02.java. package test; import import import import

java.security.KeyPair; java.security.KeyPairGenerator; java.security.PrivateKey; java.security.PublicKey;

import util.Util; import cipher.AsymmetricCipher; public class AsymmetricCipherTest02 { public static void main ( String [ ] args ) { KeyPair keyPair = null; KeyPairGenerator keyPairGenerator = null; String algorithm = "RSA"; PublicKey publicKey = null; PrivateKey privateKey = null; try { keyPairGenerator = KeyPairGenerator.getInstance ( algorithm ); keyPairGenerator.initialize ( 1024 ); keyPair = keyPairGenerator.generateKeyPair ( ); publicKey = keyPair.getPublic ( ); privateKey = keyPair.getPrivate ( ); } catch ( Exception e ) { System.out.println ( e.getMessage ( ) ); } AsymmetricCipher a = new AsymmetricCipher ( algorithm ); Integer clearObject = new Integer ( 4 ); System.out.println ( clearObject ); byte [ ] cipherObject = null; cipherObject = a.encryptObject ( clearObject, publicKey ); Util.printByteArrayHexadecimal ( cipherObject ); clearObject =


Introducción a las técnicas modernas de criptografía con ejemplos en Java

83

( Integer ) a.decryptObject ( cipherObject, privateKey ); System.out.println ( clearObject ); } }

Ahora, ejecute la aplicación AsymmetricCipherTest02.java y responda las siguientes preguntas: 1. ¿En qué consiste el objeto que se utiliza para probar el funcionamiento del sistema criptográfico de llave pública? ________________________________________________________________ 2. ¿Cuál es la longitud del byte [ ] de objeto cifrado? ________________________________________________________________

6.3.4 Actividad 4. Miniproyecto Continúe con el miniproyecto que viene trabajando, y tenga en cuenta los requerimientos a continuación. • Generar un par de llaves para criptografía de llave asimétrica. • Elegir la llave utilizada para cifrar un mensaje entre las dos llaves generadas, bien sea la pública o privada. • Cifrar un mensaje que ingresa en el campo de texto. • Elegir la llave utilizada para descifrar un mensaje cifrado entre las dos llaves generadas. • Descifrar un mensaje que acaba de ser cifrado y mostrarlo en el campo de texto correspondiente. • Almacenar el par de llaves.


84 • Cargar el par de llaves.

Figura 12. GUI Miniproyecto de Criptografía de Llave Pública Puede incorporar un esquema de fichas o con menús para incorporar los nuevos requisitos de la aplicación. Debe tener en cuenta que una vez que las llaves han sido generadas o recuperadas de un archivo, se debe elegir la llave con la que desea cifrar o descifrar el texto. Incluya el requisito adicional del taller de criptografía de llave simétrica de manera que pueda utilizarlo en el sistema criptográfico de llave pública.


Introducción a las técnicas modernas de criptografía con ejemplos en Java

85

6.3.5 Actividad 5. Restricciones de RSA Cree la clase AsymmetricCipherTest03.java la cual es una modificación de la clase AsymmetricCipherTest01.java. Justo después de la creación del objeto de tipo AsymmetricCipher, escriba el siguiente código en lugar del texto

"Bienvenidos a la criptografia!". char [ ] nombre = new char [ 117 ]; for ( int i = 0; i < nombre.length; i++ ) { nombre [ i ] = 'a'; } String clearText = new String ( nombre );

1. ¿Cuál es el objetivo del código anterior? ________________________________________________________________ Continúe con el siguiente código. byte [ ] cipherText = null; System.out.println ( clearText ); cipherText = a.encryptMessage ( clearText, publicKey ); Util.printByteArrayInt ( cipherText ); clearText = a.decryptMessage ( cipherText, privateKey ); System.out.println ( clearText );

2. ¿Qué pasa si el tamaño del char [ ] se aumenta a 118 elementos? ¿Cuál es la salida en la pantalla? ________________________________________________________________


86 Cree la clase AsymmetricCipherTest04.java la cual es una modificaci贸n de la clase AsymmetricCipherTest02.java. package test; import import import import import

java.security.KeyPair; java.security.KeyPairGenerator; java.security.PrivateKey; java.security.PublicKey; java.util.Vector;

import util.Util; import cipher.AsymmetricCipher; public class AsymmetricCipherTest04 { public static void main ( String [ ] args ) { KeyPair keyPair = null; KeyPairGenerator keyPairGenerator = null; String algorithm = "RSA"; PublicKey publicKey = null; PrivateKey privateKey = null; try { keyPairGenerator = KeyPairGenerator.getInstance ( algorithm ); keyPairGenerator.initialize ( 1024 ); keyPair = keyPairGenerator.generateKeyPair ( ); publicKey = keyPair.getPublic ( ); privateKey = keyPair.getPrivate ( ); } catch ( Exception e ) { System.out.println ( e.getMessage ( ) ); } AsymmetricCipher a = new AsymmetricCipher ( algorithm ); Vector < String > clearObject = new Vector < String > ( ); clearObject.add clearObject.add clearObject.add clearObject.add clearObject.add

( ( ( ( (

"Ana" ); "Bety" ); "Carolina" ); "Daniela" ); "Elena" );


Introducción a las técnicas modernas de criptografía con ejemplos en Java

87

System.out.println ( clearObject ); try { System.out.println ( Util.objectToByteArray ( clearObject ).length ); } catch ( Exception e ) { e.printStackTrace ( ); } } }

3. ¿Cuál es la salida en la pantalla? ________________________________________________________________

6.3.6 Actividad 6. Método splitByteArray ( ) La cantidad de bytes que pueden ser cifrados usando el algoritmo RSA depende de la longitud de la llave. Cuando la llave es de 1024 bits de longitud, la cantidad máxima de bytes a cifrar es de 117 bytes. Por lo tanto, para cifrar objetos de mayor tamaño se hace necesario dividirlos en fragmentos y posteriormente tener la posibilidad de unirlos de nuevo. Adicione a la clase

Util.java el método splitByteArray ( ) especificado a continuación. El objetivo del método splitByteArray ( ) es dividir un byte [ ] en un byte[ ][ ] (un array de byte [ ]).


88

Figura 13.MĂŠtodos splitByteArray y joinByteArray public static byte [ ][ ] splitByteArray ( byte [ ] input, int chunkWidth ) { int lastChunkWidth = -1; // calcular la cantidad de fragmentos int numberOfChunks = input.length / chunkWidth; if ( input.length % chunkWidth != 0 ) { numberOfChunks++; } // calcular el tamaĂąo del ultimo fragmento lastChunkWidth = input.length % chunkWidth; byte [ ][ ] output = new byte [ numberOfChunks ][ ]; int i = 0; for ( ; i < numberOfChunks - 1; i++ ) { output [ i ] = new byte [ chunkWidth ]; } output [ i ] = new byte [ lastChunkWidth ]; i = 0; int j = 0; int k = 0; // recorrido para poblar los distintos fragmentos, excepto el ultimo for ( ; j < numberOfChunks - 1; j++ ) { // recorrido por todos los elementos del array de entrada for ( k = 0; k < chunkWidth; k++ ) {


Introducción a las técnicas modernas de criptografía con ejemplos en Java

89

output [ j ] [ k ] = input [ i ]; i++; } } // fragmento final for ( k = 0; k < lastChunkWidth; k++ ) { output [ j ] [ k ] = input [ i ]; i++; } return output; }

Este método inicia calculando el número de fragmentos (chunks) a partir de la longitud del byte [ ] que se desea partir y del ancho de cada fragmento. Si la longitud del byte [ ] es múltiplo del ancho de cada fragmento el número de fragmentos es exactamente el resultado de la división entera entre los dos. En caso contrario, quedará un bloque adicional con un ancho que resulta de calcular el residuo de la división entera entre la longitud del byte [ ] y el ancho de cada fragmento. // calcular la cantidad de fragmentos int numberOfChunks = input.length / chunkWidth; if ( input.length % chunkWidth != 0 ) { numberOfChunks++; } // calcular el tamaño del ultimo fragmento lastChunkWidth = input.length % chunkWidth;

A continuación se crea el byte [ ][ ] (una matriz de elementos tipo byte) donde cada fila es un fragmento y la última fila corresponde al último fragmento el cual puede ser de un tamaño menor al del resto de fragmentos. Observe que para crear el byte [ ][ ] es necesario haber calculado el número de fragmentos en que se va a dividir el byte [ ].


90 byte [ ][ ] output = new byte [ numberOfChunks ][ ];

Posteriormente se crea cada una de las filas del byte [ ][ ] (la matriz), es decir, cada uno de los byte [ ]. En el ciclo for, se crean todas las filas, excepto la Ăşltima que tiene un ancho especificado por la variable lastChunkWidth. int i = 0; for ( ; i < numberOfChunks - 1; i++ ) { output [ i ] = new byte [ chunkWidth ]; } output [ i ] = new byte [ lastChunkWidth ];

Luego, se hace una copia elemento a elemento de los distintos elementos del

byte [ ] en cada fila del byte [ ][ ] de acuerdo al lugar que le corresponde, sin considerar la Ăşltima fila. for ( ; j < numberOfChunks - 1; j++ ) { // recorrido por todos los elementos del array de entrada for ( k = 0; k < chunkWidth; k++ ) { output [ j ] [ k ] = input [ i ]; i++; } }

Finalmente, se realiza el mismo procedimiento anterior con los elementos de la Ăşltima fila. for ( k = 0; k < lastChunkWidth; k++ ) { output [ j ] [ k ] = input [ i ]; i++; }


Introducción a las técnicas modernas de criptografía con ejemplos en Java

91

6.3.7 Actividad 7. Método joinByteArray ( ) De la misma forma como es necesario dividir un byte [ ] antes de cifrar los datos para evitar inconvenientes con las restricciones del algoritmo RSA, es necesario contar con un método que permita unir las partes. Adicione a la clase

Util.java el método joinByteArray ( ) especificado a continuación. public static byte [ ] joinByteArray ( byte [ ][ ] input ) { int size = 0; // calcula el tamaño del array de destino for ( int i = 0; i < input.length; i++ ) { size += input [ i ].length; } // se crea el array de destino byte [ ] output = new byte [ size ]; int k = 0; // se llena el array de destino for ( int i = 0; i < input.length; i++ ) { for ( int j = 0; j < input[ i ].length; j++ ) { output [ k ] = input [ i ][ j ]; k++; } } return output; }

Este método consiste en crear un byte [ ] del tamaño apropiado, para alojar los elementos que están en el byte [ ][ ]. Por lo tanto, lo primero que se necesita es obtener el número de elementos del arreglo destino. Luego, con el tamaño ya calculado, entonces se procede a crear el arreglo y luego se hace la copia elemento a elemento manejando cuidadosamente las posiciones de las filas de


92 la matriz y de las posiciones del arreglo destino. Al finalizar, el arreglo destino es retornado.

6.3.8 Actividad 8. Prueba de los métodos de dividir y unir Revise si los métodos para fragmentar un byte [ ] y volverlo a unir funcionan correctamente. Escriba el código de la clase SerializationAndSplitTest.java. package test; import java.util.Vector; import util.Util; public class SerializationAndSplitTest { public static void main ( String [ ] args ) { Vector < String > object = new Vector < String > ( ); int size = 50; object.add object.add object.add object.add object.add

( ( ( ( (

"Ana" ); "Bety" ); "Carolina" ); "Daniela" ); "Elena" );

System.out.println ( "Objeto que se desea dividir: " ); System.out.println ( object ); Object output = null; byte [ ] objectByteArray = null; byte [ ] secondObject = null; try { // convertir el objeto a byte [ ] objectByteArray = Util.objectToByteArray ( object ); // partir el byte [ ] en chunks byte [ ][ ] chunksOfObject = Util.splitByteArray ( objectByteArray, size ); // unir los chunks en texto claro en un byte [ ]


93

Introducción a las técnicas modernas de criptografía con ejemplos en Java // unico y completo secondObject = Util.joinByteArray ( chunksOfObject ); //convertir el byte [ ] en Object output = Util.byteArrayToObject ( secondObject ); } catch ( Exception e ) { System.err.println ( e.getMessage ( }

) );

System.out.println ( "Objeto formado a partir de fragmentos: " ); System.out.println ( output ); } }

El objetivo de este método es crear un Vector de tipo String con cinco nombres de personas. Luego, se serializa el vector y se divide el vector en fragmentos de 50 elementos. Luego se unen las partes para formar un segundo objeto que debe ser exactamente igual al primero. Este método permite verificar que los métodos splitByteArray ( ) y joinByteArray ( ) realizan su trabajo sin presentar ningún inconveniente.

6.3.9 Actividad 9. Cifrar un objeto mayor de 117 bytes Con

base

en

AsymmetricCipherTest04.java

cree

la

clase

AsymmetricCipherTest05.java y haga las modificaciones en la línea ubicada a continuación de la creación del objeto de tipo AsymmetricCipher. Vector < String > clearObject = new Vector < String > ( ); clearObject.add clearObject.add clearObject.add clearObject.add clearObject.add

( ( ( ( (

"Ana" ); "Bety" ); "Carolina" ); "Daniela" ); "Elena" );

System.out.println ( clearObject );


94 byte [ ][ ] chunksOfCipherObject = null; byte [ ][ ] chunksOfDescipherObject = null; try { byte [ ] clearObjectByteArray = Util.objectToByteArray ( clearObject ); System.out.println ( "longitud total: " + clearObjectByteArray.length ); byte [ ][ ] chunksOfClearObject = Util.splitByteArray ( clearObjectByteArray, 90 ); chunksOfCipherObject = new byte [ chunksOfClearObject.length ][ ]; for ( int i = 0; i < chunksOfCipherObject.length; i++ ) { chunksOfCipherObject [ i ] = a.encryptObject ( chunksOfClearObject [ i ], publicKey ); } chunksOfDescipherObject = new byte [ chunksOfCipherObject.length ][ ]; for ( int i = 0; i < chunksOfDescipherObject.length; i++ ) { chunksOfDescipherObject [ i ] = ( byte [ ] ) a.decryptObject ( chunksOfCipherObject [ i ], privateKey ); } byte [ ] DescipherObjectByteArray = Util.joinByteArray ( chunksOfDescipherObject ); Vector < String > descipherObject = ( Vector < String > ) Util.byteArrayToObject ( DescipherObjectByteArray ); System.out.println ( descipherObject ); } catch ( Exception e ) { e.printStackTrace ( ); }

En este programa se crea el vector con los nombres de las cinco personas y se serializa el vector en un byte [ ].


95

Introducción a las técnicas modernas de criptografía con ejemplos en Java byte [ ] clearObjectByteArray = Util.objectToByteArray ( clearObject ); System.out.println ( "longitud total: " + clearObjectByteArray.length );

A continuación se divide el vector serializado en fragmentos de 90 bytes de ancho

cada

uno,

formando

una

matriz

en

texto

claro

llamada

chunksOfClearObject. Luego se crea otra matriz equivalente llamada chunksOfCipherObject la cual va a contener los fragmentos cifrados. byte [ ][ ] chunksOfClearObject = Util.splitByteArray ( clearObjectByteArray, 90 ); chunksOfCipherObject = new byte [ chunksOfClearObject.length ][ ];

El siguiente paso es cifrar cada uno de los fragmentos del objeto en texto claro, usando la llave pública asociada al sistema criptográfico. for ( int i = 0; i < chunksOfCipherObject.length; i++ ) { chunksOfCipherObject [ i ] = a.encryptObject ( chunksOfClearObject [ i ], publicKey ); }

Para descifrar el objeto se crea una nueva matriz del mismo tamaño de la matriz que almacena los fragmentos del objeto cifrado. chunksOfDescipherObject = new byte [ chunksOfCipherObject.length ][ ];

La matriz que va a almacenar el objeto descifrado se va llenando con los datos, nuevamente en texto claro, al descifrar cada una de las filas, utilizando la llave privada.


96 for ( int i = 0; i < chunksOfDescipherObject.length; i++ ) { chunksOfDescipherObject [ i ] = ( byte [ ] ) a.decryptObject ( chunksOfCipherObject [ i ], privateKey ); }

Finalmente los fragmentos almacenados en la matriz con el texto claro se unen para formar el objeto descifrado. byte [ ] DescipherObjectByteArray = Util.joinByteArray ( chunksOfDescipherObject ); Vector < String > descipherObject = ( Vector < String > ) Util.byteArrayToObject ( DescipherObjectByteArray );

Ejecute la aplicación y responda: 1. ¿Cuál es el tamaño de cada uno de los fragmentos cifrados? ________________________________________________________________ 2. ¿Cuáles son los pasos para cifrar un objeto de cualquier tamaño? ________________________________________________________________ 3. ¿Cuáles son los pasos para descifrarlo? ________________________________________________________________ 4. ¿Pudo obtener alguna conclusión? ________________________________________________________________


Introducción a las técnicas modernas de criptografía con ejemplos en Java

97

7 Sistema criptográfico híbrido Los sistemas criptográficos híbridos usan una combinación de sistemas criptográficos simétricos y los sistemas criptográficos de llave pública, debido a los problemas de rendimiento asociados a la criptografía de llave pública, especialmente cuando se trata de cifrar grandes volúmenes de datos. El sistema criptográfico híbrido es usado fundamentalmente en la comunicación de datos a través de redes de computadores. Consiste en establecer una llave de sesión simétrica secreta, la cual es cifrada usando la llave pública del receptor. De esta forma es posible distribuirla en forma segura sobre un medio considerado no seguro como es el caso de Internet. Luego, el receptor la descifra con su llave privada (es el único que la conoce) y a partir de ese momento, pueden usar la llave simétrica secreta para el intercambio de información. Dado que la aplicación de los sistemas criptográficos híbridos está orientada a las aplicaciones de redes de computadores, a continuación se realiza el taller de programación usando aplicaciones cliente – servidor.


98

7.1 Taller de criptografía en aplicaciones ClienteServidor 7.1.1 Actividad 1. Envío de objetos por la red En esta actividad haremos uso de la criptografía simétrica y asimétrica para el envío de objetos encriptados por la red. Para esta actividad, se debe crear un paquete llamado netTest. 1. Escriba el código de las clases ClientTest01.java y ServerTest01.java suministradas por el profesor para verificar el funcionamiento de una aplicación

Cliente-Servidor. ClientTest01.java El cliente envía un mensaje inicial "HOLA" seguido por dos String, un nombre de usuario (login) y un password. El servidor recibe los tres datos y envía

"OK". El cliente recibe el mensaje del servidor y lo imprime la consola. package nettest; import import import import import

java.io.IOException; java.io.ObjectInputStream; java.io.ObjectOutputStream; java.net.Socket; java.net.UnknownHostException;

public class ClientTest01 { public static final int PORT = 20000; private Socket clientSocket; private ObjectOutputStream outToServer; private ObjectInputStream inFromServer; public ClientTest01 ( )


Introducción a las técnicas modernas de criptografía con ejemplos en Java { System.out.println ( "Client" ); try { clientSocket = new Socket ( "localhost", PORT ); outToServer = new ObjectOutputStream ( clientSocket.getOutputStream ( ) ); inFromServer = new ObjectInputStream ( clientSocket.getInputStream ( ) ); //El cliente envía un mensaje "HOLA" al servidor. String message = "HOLA"; send ( message ); //El cliente crea los datos login y password String login = "carlos"; String password = "123456"; // El cliente envía los datos login y password send ( login ); send ( password ); // El cliente recibe la respuesta del servidor String answer = ( String ) receive ( ); // El cliente imprime la respuesta en la consola System.out.println ( answer ); } catch ( UnknownHostException e ) { e.printStackTrace ( ); } catch ( IOException e ) { e.printStackTrace ( ); } } private void send ( Object o ) { try { outToServer.writeObject ( o ); outToServer.reset ( ); } catch ( IOException e ) { e.printStackTrace ( ); }

99


100 } private Object receive ( ) { Object o = null; try { o = inFromServer.readObject ( ); } catch ( IOException e ) { e.printStackTrace ( ); } catch ( ClassNotFoundException e ) { e.printStackTrace ( ); } return o; } public static void main ( String args [ ] ) { new ClientTest01 ( ); } }

ServerTest01.java El servidor recibe un mensaje inicial "HOLA" seguido por un nombre de usuario (login) y un password. Luego, el servidor envĂ­a al cliente un mensaje

"OK". package nettest; import import import import import

java.io.IOException; java.io.ObjectInputStream; java.io.ObjectOutputStream; java.net.ServerSocket; java.net.Socket;

public class ServerTest01 { public static final int PORT = 20000;


Introducción a las técnicas modernas de criptografía con ejemplos en Java private private private private

ServerSocket Socket ObjectOutputStream ObjectInputStream

welcomeSocket; connectionSocket; outToClient; inFromClient;

public ServerTest01 ( ) { try { welcomeSocket = new ServerSocket ( PORT ); } catch ( IOException e ) { e.printStackTrace ( ); } System.out.println ( "Server" ); while ( true ) { try { connectionSocket = welcomeSocket.accept ( ); outToClient = new ObjectOutputStream ( connectionSocket.getOutputStream ( ) ); inFromClient = new ObjectInputStream ( connectionSocket.getInputStream ( ) ); } catch ( IOException e ) { e.printStackTrace ( ); } //El servidor recibe el mensaje "HOLA" String mensajeInicial = ( String ) receive( ); System.out.println ( mensajeInicial ); // El servidor recibe los datos login y password String login = ( String ) receive ( ); String password = ( String ) receive ( ); // El servidor muestra en la consola los datos recibidos System.out.println( login + " " + password ); //El servidor envía un mensaje "OK" confirmando que // ha recibido los datos. String answer = "OK"; send ( answer ); }

101


102 } private void send ( Object o ) { try { outToClient.writeObject ( o ); outToClient.reset ( ); } catch ( IOException e ) { e.printStackTrace ( ); } } private Object receive ( ) { Object o = null; try { o = inFromClient.readObject ( ); } catch ( IOException e ) { e.printStackTrace ( ); } catch ( ClassNotFoundException e ) { e.printStackTrace ( ); } return o; } public static void main ( String args [ ] ) { new ServerTest01 ( ); } }

Ejecute el servidor y luego el cliente. Observe la forma como se ha enviado un objeto entre cliente y servidor y viceversa. Si le es posible, ejecute el cliente en una máquina y el servidor en otra. En el programa cliente escriba el nombre de host o la dirección IP del servidor en lugar de “localhost”.


Introducción a las técnicas modernas de criptografía con ejemplos en Java

103

7.1.2 Actividad 2. Envío de objetos cifrados por la red usando criptografía de llave simétrica Escriba el código de las clases ClientTest02.java y ServerTest02.java suministradas por el profesor para verificar el funcionamiento la criptografía simétrica en una aplicación Cliente – Servidor. Tome como referencia las clases

ClientTest01.java y ServerTest01.java ya realizadas. ClientTest02.java El cliente genera una llave secreta simétrica y la envía al servidor. El cliente cifra un mensaje usando la llave secreta y lo envía al servidor. El servidor recibe el mensaje lo descifra con la misma llave y lo muestra en la consola. Luego, envía un mensaje de confirmación y lo envía al cliente. El cliente recibe el mensaje y lo muestra en la consola. package nettest; import import import import import import

java.io.IOException; java.io.ObjectInputStream; java.io.ObjectOutputStream; java.net.Socket; java.net.UnknownHostException; java.security.NoSuchAlgorithmException;

import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; import cipher.SymmetricCipher; public class ClientTest02 { public static final int PORT = 20000; private Socket clientSocket; private ObjectOutputStream outToServer; private ObjectInputStream inFromServer;


104 public ClientTest02 ( ) { System.out.println ( "Client" ); try { clientSocket = new Socket ( "localhost", PORT ); outToServer = new ObjectOutputStream ( clientSocket.getOutputStream ( ) ); inFromServer = new ObjectInputStream ( clientSocket.getInputStream ( ) ); SecretKey secretKey = KeyGenerator.getInstance ( "DES" ).generateKey ( ); SymmetricCipher s = new SymmetricCipher ( secretKey, "DES/ECB/PKCS5Padding" ); //El cliente envia la llave secreta al servidor. send ( secretKey ); // El cliente cifra el mensaje. String clearText = "Bienvenidos a la criptografia!"; System.out.println ( clearText ); byte [ ] cipherText = s.encryptMessage ( clearText ); //El cliente envĂ­a el mensaje cifrado al servidor. send ( cipherText ); // El cliente recibe del servidor la respuesta cifrada byte [ ] cipherAnswer = ( byte [ ] ) receive ( ); // El cliente descifra la respuesta y la imprime en la consola String clearAnswer = s.decryptMessage ( cipherAnswer ); System.out.println ( clearAnswer ); } catch ( UnknownHostException e ) { e.printStackTrace ( ); } catch ( IOException e ) { e.printStackTrace ( ); } catch ( NoSuchAlgorithmException e ) { e.printStackTrace ( ); } }


Introducción a las técnicas modernas de criptografía con ejemplos en Java private void send ( Object o ) { try { outToServer.writeObject ( o ); outToServer.reset ( ); } catch ( IOException e ) { e.printStackTrace ( ); } } private Object receive ( ) { Object o = null; try { o = inFromServer.readObject ( ); } catch ( IOException e ) { e.printStackTrace ( ); } catch ( ClassNotFoundException e ) { e.printStackTrace ( ); } return o; } public static void main ( String args [ ] ) { new ClientTest02 ( ); } }

105


106

ServerTest02.java El cliente genera una llave secreta simétrica y la envía al servidor. El cliente cifra un mensaje usando la llave secreta y lo envía al servidor. El servidor recibe el mensaje lo descifra con la misma llave y lo muestra en la consola. Luego, envía un mensaje de confirmación y lo envía al cliente. El cliente recibe el mensaje y lo muestra en la consola. package nettest; import import import import import

java.io.IOException; java.io.ObjectInputStream; java.io.ObjectOutputStream; java.net.ServerSocket; java.net.Socket;

import javax.crypto.SecretKey; import cipher.SymmetricCipher; public class ServerTest02 { public static final int PORT = 20000; private private private private

ServerSocket Socket ObjectOutputStream ObjectInputStream

welcomeSocket; connectionSocket; outToClient; inFromClient;

public ServerTest02 ( ) { try { welcomeSocket = new ServerSocket ( PORT ); } catch ( IOException e ) { e.printStackTrace ( ); } System.out.println ( "Server" ); while ( true ) {


Introducción a las técnicas modernas de criptografía con ejemplos en Java try { connectionSocket = welcomeSocket.accept ( ); outToClient = new ObjectOutputStream ( connectionSocket.getOutputStream ( ) ); inFromClient = new ObjectInputStream ( connectionSocket.getInputStream ( ) ); } catch ( IOException e ) { e.printStackTrace ( ); } //El servidor recibe la llave secreta SecretKey secretKey = ( SecretKey ) receive ( ); SymmetricCipher s = new SymmetricCipher ( secretKey, "DES/ECB/PKCS5Padding" ); // El servidor recibe el mensaje cifrado byte [ ] cipherText = ( byte [ ] ) receive ( ); // El servidor descifra el mensaje con la llave secreta String clearText = s.decryptMessage ( cipherText ); System.out.println ( clearText ); // El servidor cifra la respuesta con la llave secreta. String clearAnswer = "OK"; byte [ ] cipherAnswer = s.encryptMessage ( clearAnswer ); // El servidor envía el mensaje cifrado al cliente. send ( cipherAnswer ); } } private void send ( Object o ) { try { outToClient.writeObject ( o ); outToClient.reset ( ); } catch ( IOException e ) { e.printStackTrace ( ); } } private Object receive ( ) {

107


108 Object o = null; try { o = inFromClient.readObject ( ); } catch ( IOException e ) { e.printStackTrace ( ); } catch ( ClassNotFoundException e ) { e.printStackTrace ( ); } return o; } public static void main ( String args [ ] ) { new ServerTest02 ( ); } }

Ejecute el servidor y luego el cliente. Observe cómo el cliente crea la llave y luego el servidor la recibe. En ambos casos, se crea el sistema criptográfico con la misma llave.

7.1.3 Actividad 3. Envío de objetos cifrados por la red usando criptografía de llave pública Escriba el código de las clases ClientTest03.java y ServerTest03.java suministradas por el profesor para verificar el funcionamiento la criptografía de llave pública en una aplicación Cliente – Servidor. El cliente genera un par de llaves y envía la llave pública al servidor. El cliente cifra un mensaje usando su llave privada y lo envía al servidor. El servidor descifra el mensaje usando la llave publica del cliente y lo muestra en la


Introducción a las técnicas modernas de criptografía con ejemplos en Java

109

consola. El servidor cifra con la llave pública del cliente, un mensaje de confirmación y lo envía al cliente. El cliente recibe el mensaje, lo descifra con su llave privada y lo muestra en la consola.

ClientTest03.java. package nettest; import import import import import import import import import import

java.io.IOException; java.io.ObjectInputStream; java.io.ObjectOutputStream; java.net.Socket; java.net.UnknownHostException; java.security.KeyPair; java.security.KeyPairGenerator; java.security.NoSuchAlgorithmException; java.security.PrivateKey; java.security.PublicKey;

import cipher.AsymmetricCipher; public class ClientTest03 { public static final int PORT = 20000; private Socket clientSocket; private ObjectOutputStream outToServer; private ObjectInputStream inFromServer; public ClientTest03 ( ) { System.out.println ( "Client" ); try { clientSocket = new Socket ( "localhost", PORT ); outToServer = new ObjectOutputStream ( clientSocket.getOutputStream ( ) ); inFromServer = new ObjectInputStream ( clientSocket.getInputStream ( ) ); String algorithm = "RSA"; KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance ( algorithm ); keyPairGenerator.initialize ( 1024 );


110 KeyPair keyPair = keyPairGenerator.generateKeyPair ( ); PublicKey publicKey = keyPair.getPublic ( ); PrivateKey privateKey = keyPair.getPrivate ( ); AsymmetricCipher a = new AsymmetricCipher ( algorithm ); //El cliente envia la llave publica al servidor. send ( publicKey ); // El cliente cifra el mensaje usando la llave privada. String clearText = "Bienvenidos a la criptografia!"; System.out.println ( clearText ); byte [ ] cipherText = a.encryptMessage ( clearText, privateKey ); //El cliente envĂ­a el mensaje cifrado al servidor. send ( cipherText ); // El cliente recibe del servidor la respuesta cifrada byte [ ] cipherAnswer = ( byte [ ] ) receive ( ); // El cliente descifra la respuesta y la imprime en la consola String clearAnswer = a.decryptMessage ( cipherAnswer, privateKey ); System.out.println ( clearAnswer ); } catch ( UnknownHostException e ) { e.printStackTrace ( ); } catch ( IOException e ) { e.printStackTrace ( ); } catch ( NoSuchAlgorithmException e ) { e.printStackTrace ( ); } } private void send ( Object o ) { try { outToServer.writeObject ( o ); outToServer.reset ( ); } catch ( IOException e ) { e.printStackTrace ( );


Introducción a las técnicas modernas de criptografía con ejemplos en Java

111

} } private Object receive ( ) { Object o = null; try { o = inFromServer.readObject ( ); } catch ( IOException e ) { e.printStackTrace ( ); } catch ( ClassNotFoundException e ) { e.printStackTrace ( ); } return o; } public static void main ( String args [ ] ) { new ClientTest03 ( ); } }

ServerTest03.java El cliente genera un par de llaves y envía la llave pública al servidor. El cliente cifra un mensaje usando su llave privada y lo envía al servidor. El servidor descifra el mensaje usando la llave publica del cliente y lo muestra en la consola. El servidor cifra con la llave pública del cliente, un mensaje de confirmación y lo envía al cliente. El cliente recibe el mensaje, lo descifra con su llave privada y lo muestra en la consola. package nettest; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream;


112 import java.net.ServerSocket; import java.net.Socket; import java.security.PublicKey; import cipher.AsymmetricCipher; public class ServerTest03 { public static final int PORT = 20000; private private private private

ServerSocket Socket ObjectOutputStream ObjectInputStream

welcomeSocket; connectionSocket; outToClient; inFromClient;

public ServerTest03 ( ) { try { welcomeSocket = new ServerSocket ( PORT ); } catch ( IOException e ) { e.printStackTrace ( ); } System.out.println ( "Server" ); while ( true ) { try { connectionSocket = welcomeSocket.accept ( ); outToClient = new ObjectOutputStream ( connectionSocket.getOutputStream ( ) ); inFromClient = new ObjectInputStream ( connectionSocket.getInputStream ( ) ); } catch ( IOException e ) { e.printStackTrace ( ); } String algorithm = "RSA"; //El servidor recibe la llave privada PublicKey publicKey = ( PublicKey ) receive ( ); AsymmetricCipher a = new AsymmetricCipher ( algorithm );


Introducción a las técnicas modernas de criptografía con ejemplos en Java

113

// El servidor recibe el mensaje cifrado byte [ ] cipherText = ( byte [ ] ) receive ( ); // El servidor descifra el mensaje con la llave publica String clearText = a.decryptMessage ( cipherText, publicKey ); System.out.println ( clearText ); // El servidor cifra la respuesta con la llave publica. String clearAnswer = "OK"; byte [ ] cipherAnswer = a.encryptMessage ( clearAnswer, publicKey ); // El servidor envía el mensaje cifrado al cliente. send ( cipherAnswer ); } } private void send ( Object o ) { try { outToClient.writeObject ( o ); outToClient.reset ( ); } catch ( IOException e ) { e.printStackTrace ( ); } } private Object receive ( ) { Object o = null; try { o = inFromClient.readObject ( ); } catch ( IOException e ) { e.printStackTrace ( ); } catch ( ClassNotFoundException e ) { e.printStackTrace ( ); } return o; }


114 public static void main ( String args [ ] ) { new ServerTest03 ( ); } }

7.1.4 Actividad 4. Miniproyecto Con base en los ejemplos anteriores, y considerando los ejemplos vistos en el taller de programación del capítulo 6, desarrolle el sistema criptográfico híbrido para el escenario a continuación, el cual consiste en: 1. El servidor cuando entra en ejecución genera un par de llaves asimétricas. 2. El cliente envía un mensaje de saludo al servidor y como respuesta, el servidor le envía su llave pública. 3. El cliente genera una llave secreta y la envía al servidor (cifrada con la llave pública del servidor). En este caso, la llave secreta debe ser serializada y dividida en fragmentos para hacer posible el cifrado usando la llave pública del servidor. 4. El cliente construye un objeto con el nombre de usuario y el password. Cifra el objeto usando la llave secreta y envía el objeto cifrado al servidor. 5. El servidor recibe el objeto cifrado y lo descifra. 6. El servidor verifica si el usuario está registrado o no. 7. Si el usuario no está registrado, envía al cliente el mensaje:

"BIENVENIDO xxxxx USTED ES EL USUARIO # n", donde n corresponde al número del usuario en el sistema, contando a partir de 1, cifrado con la llave secreta.


Introducción a las técnicas modernas de criptografía con ejemplos en Java

115

8. Si el usuario ya está registrado, el servidor compara el password que acaba de obtener con el que está en la tabla. Si son iguales, envía al cliente el mensaje: "BIENVENIDO xxxxx" cifrado con la llave secreta. Si el usuario no está registrado, envía al cliente el mensaje: "ERROR

DE ACCESO!!!" cifrado con la llave secreta.


116


Introducción a las técnicas modernas de criptografía con ejemplos en Java

117

8 Control de integridad Un message digest (MD) es un método que proporciona control de integridad. Este método calcula un resumen de un mensaje (también llamado huella digital) a través de una función de hashing, la cual toma un mensaje y a partir de él calcula un número de longitud fija. Una función de hashing tiene las siguientes propiedades [6]: • Dado el mensaje m, es fácil calcular MD ( m ). • Dado MD ( m ), no es posible encontrar el mensaje m que fue utilizado para calcularlo. • Dado un mensaje m, no es posible encontrar otro mensaje m’ tal que produzcan el mismo MD. • Cualquier cambio en el mensaje entrada, aunque sea de un solo bit, produce un MD distinto. Un digest (MD) es usado con el fin de determinar si un mensaje ha sido alterado o no después de haberse producido. Es decir, si un documento ha sido modificado, el digest va a ser diferente. Los dos métodos más conocidos para calcular un digest son MD5 y SHA que producen MDs de 128 y 160 bits, respectivamente.


118 Calcular el digest

de un mensaje es utilizado para realizar el control de

integridad y si no es necesaria la confidencialidad, ahorra tiempo, tanto de cifrado como de transmisión, porque los que usualmente se cifra (con llave privada), es el digest del mensaje (y no el mensaje), debido a que un digest son de longitud limitada, mientras que el mensaje no. Por otra parte existe el HMAC (Keyed-Hash Message Authentication Code) el cual realiza el cálculo de un digest mediante una función de hashing como MD5 o SHA-1, junto con una clave secreta. Este cálculo proporciona tanto integridad como autenticación.

8.1 MD5 MD5, especificado en el RFC 1321, es la quinta versión de una serie de métodos para el cálculo de digest, diseñados por Ronald Rivest en 1992. Opera truncando los bits de una forma muy sofisticada en la cual cada bit de salida es afectado por cada bit de entrada [6]. Comienza por aplicar padding (relleno) inicial al mensaje y luego se le agrega un entero de 64 bits, la longitud total del mensaje es múltiplo de 512 bits. Se calcula en cuatro iteraciones luego de un proceso de inicialización predeterminada. En cada iteración, se toma un bloque de 512 bits de entrada y lo mezcla por completo con el buffer inicial de 128 bits además de usar una tabla basada en la función seno, para evitar especulaciones sobre el uso de puertas traseras en este algoritmo [6].


Introducción a las técnicas modernas de criptografía con ejemplos en Java

119

8.2 SHA-1 SHA-1 (Secure Hash Algorithm 1), al igual MD5, procesa datos de entrada en bloques de 512 bits, para producir digests de 160 bits. La diferencia en la longitud entre SHA-1 y MD5, hace que SHA-1 sea más seguro. SHA-1 es especificado en el RFC 3174 [6] y [7]. SHA-1 inicia con un padding que consiste en un bit 1 seguido por tantos ceros como se necesiten de manera que la longitud del mensaje sea múltiplo de 512 bits. Luego, se realizan ciertas manipulaciones basadas en la función XOR y corrimientos de bits [6] y [7].

8.3 Taller de programación 8.3.1 Actividad 1. Manejo de una herramienta para el cálculo de una cifra de control de integridad en Windows Utilice el programa md5sum.exe suministrado por el profesor. Puede obtenerlo de http://etree.org/md5com.html. Este programa funciona desde la consola y no tiene interfaz gráfica de usuario. A continuación se presentan unos ejemplos de la utilización de md5sum. Tenga presente que el texto que aparece destacado en negrita es lo que debe digitar. Lo que encuentra a la izquierda del símbolo > representa la carpeta de trabajo actual, por lo que puede ser diferente a la que esté utilizando en un momento dado. Suponga que la carpeta de trabajo es: md5Tools.


120

Obtener el md5 de un archivo y mostrar el resultado en la consola Suponga que desea obtener el md5 del archivo prueba.txt.

C:\md5Tools>md5sum prueba.txt cc238a2314051d593f511ff6bac1fc4e *prueba.txt En este ejemplo, el hash md5 correspondiente al archivo prueba.txt es una cadena de 128 bits (16 bytes): cc238a2314051d593f511ff6bac1fc4e.

Obtener el md5 de un archivo y guardar el resultado en un archivo Suponga que desea obtener el md5 del archivo prueba.txt y el resultado se almacenarรก en md5.prueba.txt.

C:\md5Tools>md5sum prueba.txt > md5.prueba.txt En este caso, el resultado es el mismo que en el ejemplo anterior, porque se estรก calculando el md5 al mismo archivo. Sin embargo, en lugar de mostrar la salida en la consola, la salida es enviada al archivo con nombre md5.prueba.txt.

Observar el contenido del archivo md5 C:\md5Tools>type md5.prueba.txt cc238a2314051d593f511ff6bac1fc4e *prueba.txt


Introducción a las técnicas modernas de criptografía con ejemplos en Java

121

Este es un comando que se utiliza para mostrar el contenido de un archivo de texto a través de la consola.

Verificar si un md5 almacenado en un archivo coincide con el md5 calculado Si desea verificar si el md5 calculado previamente y almacenado en un archivo de texto corresponde con el md5 actual de un archivo cualquiera, utilice el argumento –c del programa md5sum.

C:\md5Tools>md5sum -c md5.prueba.txt prueba.txt: OK Si la verificación es satisfactoria, es decir, el archivo prueba.txt (en este ejemplo) no ha sufrido modificaciones, el resultado es OK.

Obtener el md5 de varios archivos almacenados en una misma carpeta y almacenar los resultados en un archivo Suponga que desea calcular el md5 a todos los archivos que inicien con el prefijo prueba. Es decir, puede utilizar los comodines * y ? para especificar los nombres de los archivos a los que desea calcular el md5.

C:\md5Tools\n>md5sum prueba*.* > md5.txt En este caso, la salida está redirigida al archivo md5.txt y no hay salida por la consola.


122

Verificar si los md5 almacenados en un archivo coinciden con los md5 calculados C:\md5Tools\n>md5sum -c md5.txt prueba2.txt: OK prueba.txt: OK De la misma forma como se realiza una verificaci贸n del md5 para un archivo individual, se puede realizar la prueba para varios archivos.

Obtener ayuda acerca del programa md5sum C:\md5Tools\n>md5sum --help

8.3.2 Actividad 2. Control de integridad de objetos en memoria La actividad consiste en construir una clase que permita generar y comprobar c贸digos de integridad basados en los algoritmos MD5 y SHA-1, para luego, realizar una prueba de integridad. Debe crear un paquete llamado integrity.

Digest.java package integrity; import java.io.FileInputStream; import java.security.MessageDigest; public class Digest { public static byte [ ] getDigest


Introducción a las técnicas modernas de criptografía con ejemplos en Java

123

( byte [ ] input, String algorithm ) { try { MessageDigest messageDigest = MessageDigest.getInstance ( algorithm ); messageDigest.update ( input ); return messageDigest.digest ( ); } catch ( Exception e ) { e.printStackTrace ( ); } return null; } public static boolean verifyDigest ( byte [ ] digestA, byte [ ] digestB ) { if ( digestA.length != digestB.length ) { return false; } for ( int i = 0; i < digestA.length; i++ ) { if ( digestA [ i ] != digestB [ i ] ) { return false; } } return true; } }

La clase MessageDigest del paquete java.security proporciona a las aplicaciones la funcionalidad de un algoritmo para calcular un digest usando MD5 ó SHA-1. Después de instanciar un objeto MessageDigest se puede hacer uso del método update ( ) para calcular el digest.


124

Md5Test01.java Escriba el cรณdigo del programa Md5Test01.java. Esta clase realiza un control de integridad de un mensaje de texto usando el algoritmo MD5. Debe crear un paquete llamado integritytest. package integritytest; import integrity.Digest; import util.Util; public class Md5Test01 { public static void main ( String args [ ] ) throws Exception { String textoA = "Curso de seguridad informรกtica"; byte [ ] textoAba = textoA.getBytes ( ); System.out.println ( "Texto original: " + textoA ); // Calcular el digest y escribirlo byte [ ] digestA = Digest.getDigest ( textoAba, "MD5" ); System.out.print ( "Digest original: " ); Util.printByteArrayHexadecimal ( digestA ); System.out.println ( ); String textoB = "Curso de seguridad informรกtica"; byte [ ] textoBba = textoB.getBytes ( ); System.out.println ( "Texto copia: " + textoB ); // Calcular el digest y escribirlo byte [ ] digestB = Digest.getDigest ( textoBba, "MD5" ); System.out.print ( "Digest Copia: " ); Util.printByteArrayHexadecimal ( digestB ); System.out.println ( ); // Verificar con el digest boolean prueba1 = Digest.verifyDigest ( digestA, digestB ); System.out.println ( "Verificacion del digest: " + prueba1 + "\n" ); String textoC = "Curso de seguridad informatica";


Introducción a las técnicas modernas de criptografía con ejemplos en Java byte [ ] textoCba = textoC.getBytes ( ); System.out.println ( "otro dato: " + textoC ); // Calcular el digest y escribirlo byte [ ] digestC = Digest.getDigest ( textoCba, "MD5" ); System.out.print ( "Digest Otro texto: " ); Util.printByteArrayHexadecimal ( digestC ); System.out.println ( ); boolean prueba2 = Digest.verifyDigest ( digestA, digestC ); System.out.println ( "Verificacion del digest: " + prueba2 ); } }

Ejecute la aplicación y responda las siguientes preguntas: 1. Escriba el digest obtenido por el programa.

Texto Curso

de

Digest seguridad

informática Curso

de

seguridad

informática 2. Cuál es la longitud del digest MD5 en bytes? _________

125


126 3. Ejecute de nuevo el programa. Escriba el digest obtenido por el programa.

Texto

Digest

Curso de seguridad informática Curso de seguridad informática Curso de seguridad informatica 4. ¿Qué conclusión ha obtenido con respecto al cálculo del digest MD5 después de ejecutar dos veces el mismo programa? ________________________________________________________________ ________________________________________________________________ ________________________________________________________________ A continuación escriba el código del programa Sha1Test01.java. El código fuente es exactamente igual al del programa Md5Test01.java, cambiando el algoritmo “MD5” por “SHA-1”. 5. Escriba el digest obtenido por el programa.

Texto

Digest

Curso de seguridad informática Curso de seguridad informática Curso de seguridad informatica 6. Cuál es la longitud del digest SHA-1 en bytes? _________


Introducción a las técnicas modernas de criptografía con ejemplos en Java

127

8.3.3 Actividad 3. Control de integridad de archivos binarios Adicione el método getDigestFile ( ) a la clase Digest. public static byte [ ] getDigestFile ( String fileName, String algorithm ) throws Exception { MessageDigest md = MessageDigest.getInstance ( algorithm ); FileInputStream in = new FileInputStream ( fileName ); byte [ ] buffer = new byte [ 1024 ]; int length; while ( ( length = in.read ( buffer ) ) != -1 ) { md.update ( buffer, 0, length ); } return md.digest ( ); }

A continuación escriba el código del programa Md5FileTest01.java, para hacer una prueba de integridad sobre un archivo binario.

Md5FileTest01.java Esta clase le permitirá obtener un digest MD5 con base en un archivo binario. package integritytest; import integrity.Digest; import util.Util; public class Md5FileTest01 { public static void main ( String [ ] args ) throws Exception { byte [ ] digest = Digest.getDigestFile ( "prueba.doc", "MD5" ); System.out.println ( "Digest:" ); Util.printByteArrayHexadecimal ( digest ); } }


128 Para poder realizar esta prueba, debe tener un archivo llamado “prueba.doc” en el directorio raíz del proyecto. 1. Escriba el digest obtenido por el programa.

Texto

Digest

Curso de seguridad informática 2. ¿Qué conclusión ha obtenido con respecto al cálculo del digest MD5 cuando es obtenido con base en el contenido de un objeto o con base en el contenido de un archivo binario? ________________________________________________________________ ________________________________________________________________ ________________________________________________________________ 3. Puede realizar una prueba de integridad usando el algoritmo SHA-1, simplemente cambiando el algoritmo. ¿De qué longitud debe ser el resultado? ________________________________________________________________

8.3.4 Actividad 4. Miniproyecto 1. Escriba un programa con una GUI que permita al usuario realizar lo siguiente:


Introducción a las técnicas modernas de criptografía con ejemplos en Java

129

• Calcular el valor de integridad de un mensaje de texto que ingresa en un área de texto bien sea utilizando el algoritmo MD5 ó SHA-1. Para elegir el algoritmo puede utilizar botones de radio. • Calcular el valor de integridad de un archivo cualquiera. El archivo debe ser obtenido mediante una ventana emergente de selección de archivos (JFileChooser). • Guardar el valor de integridad calculado en un archivo, serializando el digest obtenido. El archivo con el digest debe ser exactamente del tamaño correspondiente de acuerdo al algoritmo utilizado. 2. Escriba un programa cliente – servidor con los siguientes requisitos: • El programa cliente debe tener una GUI en la cual pueda recibir un mensaje de texto del usuario, la dirección del servidor y el número de puerto. La GUI debe tener un botón Enviar Mensaje y cuando el usuario haga clic sobre este botón, debe enviar el mensaje de texto junto con el digest del mensaje. El servidor debe recibir el mensaje y el digest que le envió el cliente. A continuación el servidor calcula en digest del mensaje recibido y verifica la integridad. Si el resultado de la verificación es correcto, envía como respuesta “OK”. En caso contrario envía como respuesta “ERROR”. • En la GUI debe incorporar un botón Enviar Archivo. Cuando el usuario haga clic en este botón, el programa Cliente debe solicitar el nombre del archivo a enviar mediante una ventana emergente de selección de archivos (JFileChooser). El cliente debe enviar el archivo y el digest correspondiente. Cuando el servidor reciba el archivo y el digest, debe guardar el archivo en disco y calcular el digest sobre el archivo recibido. A continuación debe realizar la comprobación entre el digest recibido y


130 el digest calculado. Si el resultado de la verificación es correcto, envía como respuesta “Archivo recibido correctamente”. En caso contrario envía como respuesta “ERROR en archivo recibido”. A continuación, un ejemplo de una aplicación cliente-servidor para la transferencia de archivos usando el protocolo TCP.

FileTCPClient.java /* Clase FileTCPClient Programa cliente que solicita al usuario el nombre de un archivo (por medio de una ventana emergente). El cliente envía el nombre del archivo al servidor y el servidor le envía el archivo al cliente. El cliente lo recibe y lo crea en el disco. No hay ninguna clase de validación acerca de la existencia o no del archivo solicitado. El archivo recibido se guarda en la carpeta Download */ import import import import import import

java.io.File; java.io.FileOutputStream; java.io.ObjectInputStream; java.io.ObjectOutputStream; java.net.Socket; javax.swing.JOptionPane;

public class FileTCPClient { public static final int PORT = 20000; private private private private private

Socket ObjectOutputStream ObjectInputStream FileOutputStream File

clientSocket; outToServer; inFromServer; out; file;

public FileTCPClient ( ) throws Exception { System.out.println ( "File Transfer Client" ); clientSocket = new Socket ( "localhost", PORT ); inFromServer = new ObjectInputStream ( clientSocket.getInputStream ( ) ); outToServer = new ObjectOutputStream ( clientSocket.getOutputStream ( ) );


Introducción a las técnicas modernas de criptografía con ejemplos en Java

131

// Solicita al usuario el nombre del archivo. String fileName = JOptionPane.showInputDialog ( null, "Nombre del archivo:" ); // Envía el nombre del archivo al servidor. send ( fileName ); // Recibe el archivo. receiveFile ( fileName ); // Cierra la conexión. inFromServer.close ( ); System.out.println ( "OK" ); } private void receiveFile ( String fileName ) throws Exception { // Se crea el archivo con el nombre especificado en la carpeta // Download. file = new File ( "Download\\" + fileName ); out = new FileOutputStream ( file ); // El cliente recibe el número de bloques que compone el archivo. int numberOfBlocks = ( ( Integer ) receive ( ) ).intValue ( ); // Se reciben uno a uno los bloques que conforman el archivo y se // almacenan en el archivo. for ( int i = 0; i < numberOfBlocks; i++ ) { byte [ ] buffer = ( byte [ ] ) receive ( ); out.write ( buffer, 0, buffer.length ); } // Se cierra el archivo que acaba de ser recibido. out.close ( ); } private void send ( Object o ) throws Exception { outToServer.writeObject ( o ); outToServer.reset ( ); } private Object receive ( ) throws Exception { return inFromServer.readObject ( ); } public static void main ( String args [ ] ) throws Exception


132 { new FileTCPClient ( ); } }

FileTCPServer.java /* Clase FileTCPServer Programa servidor que recibe un nombre de un archivo y lo env铆a al cliente. El cliente lo recibe y lo crea en el disco. No hay ninguna clase de validaci贸n acerca de la existencia o no del archivo solicitado. El archivo buscado debe estar en la carpeta Shared. */ import import import import import import

java.io.File; java.io.FileInputStream; java.io.ObjectInputStream; java.io.ObjectOutputStream; java.net.ServerSocket; java.net.Socket;

public class FileTCPServer { public final static int PORT = 20000; public final static int BUFFER_SIZE = 1024; private private private private private private

ServerSocket welcomeSocket; Socket connectionSocket; ObjectInputStream inFromClient; ObjectOutputStream outToClient; FileInputStream in; File file;

public FileTCPServer ( ) throws Exception { welcomeSocket = new ServerSocket ( PORT ); System.out.println ( "File Transfer Server ..." ); while ( true ) { connectionSocket = welcomeSocket.accept ( ); outToClient = new ObjectOutputStream ( connectionSocket.getOutputStream ( ) ); inFromClient = new ObjectInputStream ( connectionSocket.getInputStream ( ) );


Introducción a las técnicas modernas de criptografía con ejemplos en Java

133

// Se recibe el nombre del archivo que el cliente desea que el // servidor le envíe y se imprime en la consola. String fileName = ( String ) receive ( ); System.out.println ( fileName ); // Se envía el archivo. sendFile ( fileName ); } } private void send ( Object o ) throws Exception { outToClient.writeObject ( o ); outToClient.reset ( ); } private void sendFile ( String fileName ) throws Exception { // Los archivos compartidos se almacenan en la carpeta Shared. file = new File ( "Shared\\" + fileName ); // Abre el archivo solicitado. FileInputStream fileIn = new FileInputStream ( file ); // Se obtiene el tamaño del archivo y se imprime en la consola. long size = file.length ( ); System.out.println ( "Size: " + size ); // Se calcula el número de bloques y el tamaño del ultimo bloque. int numberOfBlocks = ( int ) ( size / BUFFER_SIZE ); int sizeOfLastBlock = ( int ) ( size % BUFFER_SIZE ); // Si el archivo no se puede partir en bloques de igual tamaño // queda un bloque adicional, más pequeño. if ( sizeOfLastBlock > 0 ) { numberOfBlocks++; }

// Se imprimen en la consola el número de bloques y el tamaño del // último bloque. System.out.println ( "Number of blocks: " + numberOfBlocks ); System.out.println ( "Size of last block: " + sizeOfLastBlock ); // Se envía el número de bloques al cliente. send ( new Integer ( numberOfBlocks ) ); // Si todos los bloques son de igual tamaño, no hay un bloque al // final más pequeño.


134 if ( sizeOfLastBlock == 0 ) { // Se envían todos los bloques. for ( int i = 0; i < numberOfBlocks; i++ ) { byte [ ] buffer = new byte [ BUFFER_SIZE ]; fileIn.read ( buffer ); send ( buffer ); } } else { // Si queda un bloque más pequeño al final, se envían todos los // bloques menos 1. for ( int i = 0; i < numberOfBlocks - 1; i++ ) { byte [ ] buffer = new byte [ BUFFER_SIZE ]; fileIn.read ( buffer ); send ( buffer ); } // El bloque restante se envía a continuación. byte [ ] lastBuffer = new byte [ sizeOfLastBlock ]; fileIn.read ( lastBuffer); send ( lastBuffer ); } // Al terminar, se cierra el archivo que se acaba de enviar. fileIn.close ( ); } private Object receive ( ) throws Exception { return inFromClient.readObject ( ); } public static void main ( String args [ ] ) throws Exception { new FileTCPServer ( ); } }


Introducción a las técnicas modernas de criptografía con ejemplos en Java

135

9 Firmas digitales Se define firma como un nombre o título, que una persona escribe de su propia mano en un documento, para darle autenticidad o para expresar que aprueba su contenido [3]. La necesidad de firmas personales viene de tiempo atrás y es usada en diferentes contextos [2]. La persona mediante la firma manuscrita confirma que tiene conocimiento y está de acuerdo con el contenido del documento que está firmando [7] y proporciona una prueba que el emisor, y nadie más, firmó el documento deliberadamente. La firma manuscrita debe ser única y aunque puede ser falsificada, existe la grafología, una técnica de análisis para determinar su autenticidad, la cual es usada para darle validez a un documento. Cada persona posee una forma de escribir propia y diferente a la de las demás personas. Aunque la forma de escribir de las personas cambia con el tiempo, se conservan elementos básicos durante toda la vida. Verificar una firma requiere el análisis de ciertos parámetros porque la persona que intenta falsificarla trata de hacerla igual. Se analiza la espontaneidad, la


136 presión que se ejerce sobre el papel, la forma, el tamaño, la continuidad, la regularidad, la inclinación, la dirección y la proporcionalidad. Una firma digital intenta prestar el mismo servicio que una firma convencional, permitiéndole a un tercero verificar que un mensaje o documento es una copia no alterada de uno producido por el emisor [2]. La firma digital debe ser verificable y no falsificable. Es decir, debe ser posible probar que un documento firmado por una persona fue efectivamente firmado por esa persona y que solamente esa persona pudo haber firmado el documento [7]. Las firmas digitales son un requerimiento esencial en los sistemas distribuidos [2]. Proporcionan autenticación, integridad y no repudio a la información digital. No intenta mantener la confidencialidad (no siempre es necesaria). Son necesarias para vincular los identificadores de los usuarios a sus llaves públicas o vincular algunas reglas de acceso a las identidades de los usuarios [2]. La firma digital vincula un documento o mensaje con una llave secreta que solo es conocida por el emisor [2]. Usualmente la criptografía de llave pública es usada para la generación de firmas digitales, donde la llave privada es usada para cifrar el digest del mensaje. La firma puede ser descifrada por cualquiera usando la correspondiente llave pública del emisor. Quien verifica debe estar seguro que la llave pública pertenece a quien el transmisor está afirmando que le pertenece [2]. Un documento con firma digital puede ser considerablemente más resistente a la falsificación que uno manuscrito [2].


Introducción a las técnicas modernas de criptografía con ejemplos en Java

137

9.1 Generación de la firma digital El emisor genera un par de llaves y publica su llave pública en algún lugar bien conocido. El emisor calcula el digest del mensaje M, usando una función de hash seguro H y lo cifra usando la llave privada para producir la firma S y la envía. S = [H (M )]K PRIVATE

9.2 Verificación de la firma digital La verificación consiste en comprobar la identidad del emisor al usar la llave pública del emisor para descifrar el digest. Debido a que el mensaje viaja en texto claro se puede calcular el digest del mensaje. Entonces, también se comprueba la integridad comparando el digest obtenido de la firma con el digest obtenido del mensaje recibido. La firma digital entonces proporciona autenticación e integridad, pero no confidencialidad, debido a que el mensaje en enviado en texto claro. No se cifra todo el mensaje para evitar el costo computacional de cifrar todo el mensaje. A pesar de ser la firma de alguien, la firma digital está asociada al mensaje, por lo que siempre será diferente.

9.3 Firmas digitales en Java La API de seguridad de Java proporciona una clase llamada Signature en el paquete java.security, la cual es usada para proporcionar a las aplicaciones la funcionalidad de un algoritmo de firma digital.


138 Un objeto Signature puede ser usado para generar y verificar firmas digitales. El objeto de fábrica usado para obtener un objeto de la clase Signature es: public static Signature getInstance ( String algorithm ) throws NoSuchAlgorithmException

Este método returna un objeto Signature para el algoritmo dado. El algoritmo de firma digital puede ser, entre otros, DSA con SHA-1 (SHA1withDSA) o RSA

con

MD2,

MD5

o

SHA-1

(MD2withRSA,

MD5withRSA,

SHA1withRSA). El nombre del algoritmo debe ser especificado, debido a que no hay uno por defecto. Hay tres fases para usar un objeto Signature para firmar datos o verificar una firma. • Inicialización, bien sea con una llave privada (para firmar) con una llave pública (para verificación) • Actualización, dependiendo del tipo de inicialización, esto actualizará los bytes que serán firmados o verificados. • Firmar o verificar un firma sobre todos los bytes actualizados. Inicializar el objeto Signature con una llave privada para generar una firma. public final void initSign ( PrivateKey privateKey ) throws InvalidKeyException

Inicializar el objeto Signature con una llave pública para verificar una firma. public final void initVerify ( PublicKey publicKey ) throws InvalidKeyException


Introducción a las técnicas modernas de criptografía con ejemplos en Java

139

Para adicionar datos al objeto Signature de la misma forma como lo hace el objeto Digest. public final void update ( byte [] input, int offset, int length ) throws SignatureException

Adiciona length bytes del arreglo dado por parámetro, comenzando en offset, a los datos de entrada del objeto Signature.

9.3.1 Generar una firma El método sign ( ) del objeto Signature retorna la firma. public final byte[] sign() throws SignatureException

Este método calcula un firma, basada en los datos de entrada que fueron suministrados cuando se llamó el método update ( ). Una SignatureException es lanzada si el objeto Signature no fue inicializado apropiadamente. Para generar una firma, se necesitan la llave privada del emisor y el mensaje que desea firmar. El procedimiento es el siguiente:


140 Obtenga una objeto Signature usando el método de fábrica getInstance ( ). Es necesario especificar un algoritmo que puede ser SHA1withDSA,

MD5withDSA, SHA1withRSA o MD5withRSA. Inicialice el objeto Signature con la llave privada del emisor usando el método

initSign ( ). Use el método update ( ) para adicionar los datos del mensaje a la firma. Calcule la firma usando el método sign ( ). Este método retorna un byte [ ] que es la firma, la cual se debe almacenar para su posterior transmisión.

9.3.2 Verificar una firma El método verify ( ) del objeto Signature permite verificar una firma. public final boolean verify(byte[] signature) throws SignatureException

Este método verifica que la firma (el byte [ ] suministrado), coincide con la entrada de datos que ha sido suministrada usando el método update ( ). Si la firma coincide, retorna true, en caso contrario, retorna false. Si el objeto

Signature no es inicializado apropiadamente, se lanza una SignatureException. El procedimiento es el siguiente: Obtenga un objeto Signature usando el método de fábrica getInstance ( ). Inicialice el objeto Signature con la llave pública del emisor usando el método

initVerify ( ).


Introducción a las técnicas modernas de criptografía con ejemplos en Java

141

Use el método update ( ) para adicionar los datos del mensaje a la firma. Verifique si la firma coincide usando el método verify ( ). Este método acepta por parámetro un byte [ ] que es la firma que se va a verificar. Retorna un valor

boolean que es true si la firma coincide o false en otro caso.

9.4 Certificados digitales La criptografía de clave pública hace posible que las personas que no comparten una clave común se puedan comunicarse con seguridad. También permiten firmar mensajes sin la presencia de un tercero que pueda certificar su autenticidad. Sin embargo, hay un inconveniente, cuando A y B no se conocen, ¿cómo puede cada uno obtener la clave pública del otro para poder comunicarse? Es necesario un mecanismo para asegurar que las claves públicas puedan intercambiarse de manera segura [7]. Un certificado digital es un documento digital mediante el cual un tercero confiable o autoridad certificadora, garantiza la vinculación entre la identidad de un sujeto o entidad y su clave pública. Si bien existen variados formatos para certificados digitales, los más comúnmente empleados se rigen por el estándar X.509. El certificado contiene usualmente el nombre de la entidad certificada, un número de serie, la fecha de expiración, una copia de la clave pública del titular del certificado (utilizada para la verificación de su firma digital), y la firma digital de la autoridad emisora del certificado de forma que el receptor pueda verificar que esta última ha establecido realmente la asociación. La entidad certificadora debe proteger muy bien su clave privada.


142 Un certificado digital es un método usado para difundir la clave pública. Un certificado digital se obtiene mediante una solicitud a una entidad certificadora. Un certificado digital puede entregarse en un medio físico (tarjeta) para mayor seguridad, aunque es un procedimiento no escalable. Cada vez que un transmisor desea verificar la identidad del receptor, primero solicita su certificado digital. A partir del certificado digital puede obtener su clave pública, enviarle sobres digitales y verificar su identidad (para evitar suplantaciones). Algunas de las entidades certificadoras más conocidas son Verisign, MCI, AT&T, y en Colombia Certicámara. Los certificados digitales tienen validez legal en Colombia, siempre y cuando sean expedidos por una entidad certificadora reconocida legalmente en Colombia.

9.5 Taller de programación 9.5.1 Actividad 1. Firmar un archivo y verificar la firma Escriba el código de las clases DigitalSignature y DigitalSignatureTest01, suministradas a continuación. Utilice el paquete signature.

DigitalSignature.java package signature; import import import import import

java.io.BufferedInputStream; java.io.FileInputStream; java.security.PrivateKey; java.security.PublicKey; java.security.Signature;


Introducción a las técnicas modernas de criptografía con ejemplos en Java public class DigitalSignature { public static byte [ ] sign ( String fileName, String algorithm, PrivateKey privateKey ) { Signature signature; try { signature = Signature.getInstance ( algorithm ); signature.initSign ( privateKey ); BufferedInputStream bin = new BufferedInputStream ( new FileInputStream ( fileName ) ); byte [ ] buffer = new byte [ 1024 ]; int length; while ( bin.available ( ) != 0 ) { length = bin.read ( buffer ); signature.update ( buffer, 0, length ); } bin.close ( ); return signature.sign ( ); } catch ( Exception e ) { e.printStackTrace ( ); } return null; } public static boolean verify ( String fileName, String algorithm, PublicKey publicKey, byte [] signatureba ) { boolean result = false; Signature signature; try { signature = Signature.getInstance ( algorithm ); signature.initVerify ( publicKey ); BufferedInputStream bin = new BufferedInputStream ( new FileInputStream ( fileName ) ); byte [ ] buffer = new byte [ 1024 ];

143


144 int length; while ( bin.available ( ) != 0 ) { length = bin.read ( buffer ); signature.update ( buffer, 0, length ); } bin.close ( ); result = signature.verify ( signatureba ); } catch ( Exception e ) { e.printStackTrace ( ); } return result; } }

La clase DigitalSignature se apoya en la clase Signature del paquete java.security y permite la creaci贸n de firmas digitales as铆 como su verificaci贸n. A continuaci贸n construya la clase DigitalSignatureTest01.java para verificar el funcionamiento de la clase DigitalSignature.java. Utilice el paquete signaturetest.

DigitalSignatureTest01.java package signaturetest; import import import import

java.security.KeyPair; java.security.KeyPairGenerator; java.security.PrivateKey; java.security.PublicKey;

import signature.DigitalSignature; import util.Util; public class DigitalSignatureTest01 { public static void main ( String [ ] args ) { KeyPair keyPair = null; KeyPairGenerator keyPairGenerator = null; String algorithm = "RSA";


Introducción a las técnicas modernas de criptografía con ejemplos en Java

145

PublicKey publicKey = null; PrivateKey privateKey = null; try { keyPairGenerator = KeyPairGenerator.getInstance ( algorithm ); keyPairGenerator.initialize ( 1024 ); keyPair = keyPairGenerator.generateKeyPair ( ); publicKey = keyPair.getPublic ( ); privateKey = keyPair.getPrivate ( ); } catch ( Exception e ) { System.out.println ( e.getMessage ( ) ); } byte [ ] f = DigitalSignature.sign ( "Vocabulario1.doc", "MD5withRSA", privateKey ); Util.printByteArrayInt ( f ); System.out.print ( "Verificacion de la firma: " ); boolean res = DigitalSignature.verify ( "Vocabulario2.doc", "MD5withRSA", publicKey, f ); boolean res2 = DigitalSignature.verify ( "Vocabulario3.doc", "MD5withRSA", publicKey, f ); System.out.println ( res ); System.out.println ( res2 ); } }

Al ejecutar esta aplicación, se hace necesario contar con tres archivos llamados

Vocabulario1.doc, Vocabulario2.doc y Vocabulario3.doc. Si los tres archivos son exactamente iguales, la verificación va a ser exitosa. Si por el contrario

Vocabulario2.doc es diferente a Vocabulario1.doc, la primera verificación será errónea, lo mismo que si el archivo Vocabulario3.doc es diferente a Vocabulario1.doc.


146

9.5.2 Actividad 2. Firma digital en aplicaciones cliente - servidor Desarrolle una aplicación cliente-servidor en la que el cliente genere el par de llaves y envíe al servidor la llave pública. Luego, el cliente envía al servidor el nombre del archivo que va a firmar, el archivo en texto claro y la firma del archivo. El servidor verifica la firma y envía al cliente un mensaje que indique el resultado de la verificación, cifrado con la llave pública del cliente.

9.5.3 Actividad 3. Miniproyecto 1. Desarrolle una aplicación cliente-servidor en la que el servidor sea un servidor de archivos. Un cliente al iniciar, el cliente genera un par de llaves asimétricas y las almacena en dos archivos. El usuario entonces se conecta al servidor y envía el nombre de usuario (login), un password y la llave pública, la cual será guardada en el servidor. Asegúrese que el nombre del archivo con la llave pública sea el nombre del usuario con la extensión pubk. 2. Desarrolle otra aplicación cliente-servidor en la que el cliente envía al servidor el nombre de usuario (login), el nombre de un archivo, el archivo en texto claro y la firma digital del archivo. El servidor usará la identificación del cliente para conectarse con el servidor de llaves públicas para descargar la llave pública del cliente y verificará la firma del archivo que acaba de recibir. Si la verificación resulta satisfactoria, el servidor almacenará el archivo. En todo caso, enviará al cliente un mensaje con el resultado de la verificación de la firma, cifrado con la llave pública del cliente. Si la llave pública no está disponible en el servidor de llaves públicas, el servidor enviará un mensaje que así lo indique al cliente, en texto claro.


Introducción a las técnicas modernas de criptografía con ejemplos en Java

147

10 Referencias [1] COLE, E. KRUTZ, R. y CONLEY, J. Network Security Bible. Wiley Publishing, Inc. [2] COULOURIS, G., DOLLIMORE, J. y KINDBERG, T. Distributed Systems: Concepts and designs. Fourth Edition. Addison Wesley. 2005. [3] Diccionario de la lengua española. Real Academia Española. Disponible en: www.rae.es. [4] Java Platform, Standard Edition 6 API Specification. Disponible en: http://java.sun.com/javase/6/docs/api/index.html. [5] KNUDSEN, J. Java cryptography. O’Reilly. 1998. [6] KUROSE, J. y ROSS, K. Computer networking: A top-down approach. Fourth Edition. Addison Wesley. 2008. [7] TANENBAUM, A. “Computer Networks”. Prentice Hall. 4th edition, 2004.


Issuu converts static files into: digital portfolios, online yearbooks, online catalogs, digital photo albums and more. Sign up and create your flipbook.