En la criptografía
tradicional, (también llamada criptografía de "clave secreta")
tanto el emisor como el receptor poseen una misma clave o password.
El emisor utiliza esta clave para cifrar el mensaje obteniéndose el
"mensaje cifrado", que es ilegible. El receptor utiliza la misma clave utilizada
para el cifrado para descifrar el mensaje y obtener así el mensaje
original. Si esta clave es conocida únicamente por emisor y receptor,
se asegura que el mensaje recibido es el original y que es enviado por la
única persona (además del receptor), que tiene la clave.
Este tipo de
criptografía acarrea un problema. Imagínese que existe otro
emisor que también envía mensajes cifrados al receptor anterior.
Si el segundo emisor tiene la misma clave que el primer emisor, en caso de
poder interceptar mensajes de éste, podría descifrarlos ya
que utilizan la misma clave. Si, por el contrario, utiliza una clave distinta,
el receptor deberá tener tantas claves como potenciales emisores,
lo que conduce a una gran complejidad en el mantenimiento de claves.
Este tipo de
criptografía puede utilizar distintos algoritmos para cifrar los
datos. Los más utilizados son los siguientes:
Este algoritmo puede utilizar un estilo de relleno como PKCS#5 "Public Key Cryptography Standard".
La idea del uso de
criptografía con "clave pública" es bastante sencilla, aunque
los algoritmos y los fundamentos matemáticos en los que se basa no
lo sean tanto.
En este tipo de cifrado,
el emisor y el receptor poseen claves distintas. El emisor utiliza una clave
para cifrar el mensaje y el receptor utiliza otra clave para descifrarlo.
El receptor comunica la clave de cifrado a todo el mundo (De ahí el
término "clave pública") que quiera enviarle mensajes
cifrados, pero se reserva para sí la clave capaz de descifrar los
mensajes. A esta clave que únicamente conoce el receptor se le denomina
"clave privada" y es la única capaz de descifrar los mensajes
generados por la clave pública.
En el ejemplo de la figura, los mensajes enviados por Emisor 1 y 2 sólo pueden ser descifrados por Receptor.
Este tipo de
criptografía puede utilizar distintos algoritmos para cifrar los
datos. El más utilizado es el RSA "Rivest, Shamir and Adlerman
asymmetricCipher algorithm".
Con este cifrado
conseguimos privacidad aunque no conseguimos autentificar al
emisor.
Una firma digital sirve
básicamente para lo mismo que una firma realizada con una pluma o
bolígrafo en un escrito, pero la firma digital tiene una ventaja
añadida: además de probar que un documento esta escrito por
una determinada persona o institución, asegura que el documento no
fue alterado después de ser firmado.
El concepto de "firma
digital" se basa en la criptografía de clave pública, pero
el proceso es el contrario. Quien "firma" el mensaje lo cifra mediante su
"clave privada", de forma que puede ser descifrado por todo el mundo,
siempre que posean la "clave pública" correspondiente a la
clave privada utilizada para firmar el mensaje. Si la clave pública
es capaz de descifrar un mensaje sólo puede ser por un motivo, que
éste mensaje fuera cifrado por el poseedor de la clave privada, lo
que autentifica su identidad (además de asegurar que el mensaje original
no ha sido alterado, de lo contrario no podría ser descifrado por
la clave pública).
Lo que se consigue con
esto NO es cifrar un mensaje para que sólo pueda ser leído
por otra persona (puede ser descifrado por todo aquel que posea la clave
pública), sino asegurar a quien lo lee, que realmente ha sido enviado
por quien posee la clave privada y que el contenido no ha sido alterado.
El proceso de cifrar
un mensaje con la clave privada puede consumir un tiempo de proceso valioso.
Si es indiferente que el mensaje pueda ser leído por cualquier persona,
y no sólo aquellos que posean la clave pública, puede obtenerse
mediante una función hash un texto mucho más corto que
el mensaje original llamado "huella digital" o fingerprint.
Si se altera el mensaje original, también varía la huella digital.
Lo que se cifra mediante la clave privada no es el mensaje original completo,
sino esta "huella digital"; el resultado se añade al documento a
transmitir.
Quien lee el mensaje
firmado, en primer lugar aplica la función hash al documento,
después descifra la "huella digital" cifrada y compara ésta
con la obtenida al procesar el documento. si son iguales, el documento no
ha sido alterado y, efectivamente, ha sido enviado por quien firma.
De todos los algoritmos
de hashing, los más utilizados son los siguientes:
Existe un algoritmo
de Clave pública, diseñado solo para firmar documentos, el
DSA.
¿Cómo puede
una persona que recibe una clave pública saber que esa clave corresponde
realmente a quien dice ser y no a otra persona con pretensiones desconocidas?
Dicho de otra forma:
¿Cómo puede una persona suministrar su clave pública a
quien la desee? Sólo con enviarla no es suficiente. Alguien con
pretensiones desconocidas podría suministrar una clave pública
haciéndose pasar por otra persona y descifrar con la clave privada
los mensajes codificados con esa clave pública.
Precisamente para eso,
sirve un certificado, para obtener la clave pública de otra persona
o entidad.
Un certificado es una
declaración firmada digitalmente por una entidad en la que se confía
(de la que se tiene su clave pública), indicando que la clave
pública de otra persona o entidad tiene un determinado valor.
Para conseguir privacidad
y autentificación deberemos utilizar el cifrado de los datos
con la clave pública del receptor y firmarlos con nuestra clave privada..
El soporte que da el
JDK a la criptografía se divide en dos grandes bloques, el JCA "Java
Cryptography Architecture" y el JCE "Java Cryptography Extension".
La primera parte nos define las bases del soporte criptográfico de
Java, y la segunda nos provee de los algoritmos necesarios para poder encriptar
y desencriptar datos.
Debido a las leyes de
los Estados Unidos, que prohiben exportar software de encriptación
de datos, el JCE no viene incluido en el JDK, y existen restricciones para
bajarlo de la site de SUN, pero existen paquetes de terceros, desarrollados
fuera de los Estados Unidos, que implementan todas las especificaciones del
JCE y que no están sujetos a sus restricciones legales, como por ejemplo
la librería CRYPTICS.
El JCA, se refiere al
marco para acceder y desarrollar la funcionalidad criptográfica de
Java. Incluye todo el API relativo a la criptografía ( el paquete
java.security y sus subpaquetes), así
como una serie de especificaciones a tener en cuenta por los programadores
que quieran crear extensiones criptográficas (como la librería
CRYPTICS, etc).
La arquitectura
criptografica de Java ha sido diseñada teniendo en cuenta los principios
de independencia de la implementación e interoperabilidad. La
independencia de la implementación se consigue introduciendo el concepto
de "Proveedor" (Cryptography Package Provider).
El termino "Proveedor"
se refiere a un paquete o grupo de paquetes que que implementan algoritmos
criptográficos específicos.
El JDK 1.1 viene con
un proveedor por defecto llamado "SUN", el cual incluye una implementación
del algoritmo DSA y una implementación de los algoritmos de Hashing
MD5 y SHA-1.
En el JDK se pueden
instalar uno o mas paquetes proveedores.
La clase
SecureRandom es un generador de números
pseudo-aleatorio.
Para crear objetos
SecureRandom usaremos uno de los siguientes
constructores:
Una vez tenemos
un objeto SecureRandom, podemos utilizar los
siguientes métodos:
setSeed(byte[]) |
Reinicializa el SecureRandom utilizando la semilla indicada en el array de bytes. |
setSeed(long) |
Reinicializa el SecureRandom utilizando los ocho bytes del long indicado. |
next(int) |
Devuelve un entero conteniendo el número indicado de bits pseudo-aleatorios. |
El interface
Key es el interface de mas alto nivel de todas
las claves y define la funcionalidad compartida por todas las claves.
Tiene tres métodos:
String getAlgorithm() |
Devuelve el nombre del algoritmo de esta clave o nulo si es desconocido. |
byte[] getEncoded() |
Devuelve la clave codificada o nulo si la clave no soporta codificación. |
String getFormat() |
Devuelve el formato utilizado para codificar la clave o nulo si la clave no puede ser codificada. |
Los interfaces
PublicKey y
PrivateKey son interfaces sin métodos,
que derivan de la interface Key, utilizados solamente para comprobación
de tipos.
La clase
KeyPair es simplemente un contenedor de un par
de claves (una pública y una privada).
Tiene dos métodos:
PrivateKey getPrivate() |
Devuelve la clave privada. |
PublicKey getPublicKey() |
Devuelve la clave pública. |
La clase
KeyPairGenerator se utiliza para crear un par
de claves.
Para crear objetos
KeyParGenerator usaremos uno de los siguientes
métodos:
Una vez tenemos creado
un generador de claves podemos utilizar los siguientes métodos:
initialize(int potencia, SecureRandom
random) |
Inicializa el generador de claves para la potencia indicada utilizando el generador de números pseudo-aleatorio indicado. |
initialize(int potencia) |
Inicializa el generador de claves para la potencia indicada utilizando un generador de números pseudo-aleatorio subministrado por el sistema. |
KeyPair generateKeyPair() |
Genera un par de claves. |
La clase
Identity se utiliza para manejar identidades.
Para crear identidades
usaremos el constructor siguiente:
Una vez tenemos creada
una identidad podemos utilizar los siguientes métodos:
setInfo(String info) |
Asigna una cadena de información a la identidad. |
String getInfo() |
Devuelve la cadena de información de la identidad. |
setPublicKey(PublicKey key) |
Asigna una clave pública a la identidad. |
PublicKey getPublicKey() |
Devuelve la clave pública de la identidad. |
String getName() |
Devuelve el nombre de la identidad. |
La clase
Signer deriva de la clase
Identity, y se utiliza para manejar identidades
capaces de firmar datos.
Para crear "signers"
usaremos el método siguiente:
Una vez tenemos creado
un signer podemos utilizar los métodos de la clase Identity además
de los siguientes métodos:
setKeyPair(KeyPair pair) |
Asigna un par de claves al "signer". |
PrivateKey getPrivateKey() |
Devuelve la clave privada del "signer". |
La clase
Signature se utiliza para crear o verificar
firmas.
Para crear objetos
Signature usaremos uno de los siguientes
métodos:
Una vez tenemos creado
un objeto firma podemos utilizar los siguientes métodos:
initSign(PrivateKey) |
Inicializa el objeto firma para firmar. |
initVerify(PublicKey) |
Inicializa el objeto firma para verificar. |
update(byte b) |
Actualiza los datos a ser firmados o verificados en un byte. |
update(byte data[]) |
Actualiza los datos a ser firmados o verificados utilizando un array de bytes. |
update(byte data[],int off,int
len) |
Actualiza los datos a ser firmados o verificados utilizando una parte de un array de bytes que empieza en la posición off y tiene una longitud len. |
byte[] sign() |
Genera una firma. |
boolean verify(byte
signature[]) |
Verifica si la firma que se le pasa como parámetro es correcta. |
El Ejemplo siguiente consta de dos aplicaciones, una cliente que crea dos claves, lee un fichero, calcula la firma, crea un objeto "SignedData", con los datos anteriores y lo manda por la red a un servidor que leera el objeto "SignedData" y verificara si la firma es correcta.
El JCE extiende el
proveedor "SUN" e incluye una implementación de los algoritmos DES,
3DES, los modos ECB y CBC, y el estilo de relleno PKCS#5. Para utilizar otros
algoritmos deberemos utilizar otro proveedor que los implemente. (Para utilizar
el algoritmo RSA nosotros utilizaremos la libreria CRYPTIX).
La interface
SecretKey es una interface sin métodos,
que deriva de la interface Key, utilizada solamente para comprobación
de tipos.
La clase
KeyGenerator se utiliza para crear una clave
secreta.
Para crear objetos
KeyGenerator usaremos uno de los siguientes
métodos:
Una vez tenemos creado
un generador de claves podemos utilizar los siguientes métodos:
initialize(SecureRandom
random) |
Inicializa el generador de claves utilizando el generador de números pseudo-aleatorio indicado. |
SecretKey generateKey() |
Genera una clave secreta. |
La clase
Cipher se utiliza para cifrar o descifrar datos.
Para crear objetos
Cipher usaremos uno de los siguientes métodos:
Una vez tenemos creado
un objeto Cipher podemos utilizar los siguientes
métodos:
initDecrypt(Key key) |
Inicializa el objeto Cipher para descifrar. |
initEncrypt(Key key) |
Inicializa el objeto Cipher para cifrar. |
int getState() |
Devuelve el estado del objeto Cipher, pudiendo ser uno de los siguientes valores UNINITIALIZED, ENCRYPT, DECRYPT . |
byte[] crypt(byte in[]) |
Encripta o desencripta (dependiendo de la inicialización) el array especificado. |
byte[] crypt(byte in[],int off,int
len) |
Encripta o desencripta una parte del array especificado que empieza en la posición off y tiene una longitud len. |
int crypt(byte in[], int inOff, int inLen,
byte out[], int outOff) |
Encripta o desencripta una parte del array especificado que empieza en la posición inOff y tiene una longitud inLen, guardando el resultado a partir de la posición outOff del array out.. |
La clase
CipherInputStream es un stream de entrada de
datos, que puede encriptar o desencriptar los datos que pasan a traves de
el.
Para crear objetos
CipherInputStream usaremos el siguiente constructor:
Una vez tenemos creado
el stream de entrada podemos utilizar los siguientes métodos:
int read() |
Lee el byte siguiente encriptado o desencriptado según el estado del objeto Cipher. |
int read(byte b[], int off, int
len) |
Lee len, bytes encriptados o desencriptados, guardandolos en b, comenzando por b[off]. Devuelve el número de bytes leídos. |
La clase
CipherOutputStream es un stream de salida de
datos, que puede encriptar o desencriptar los datos que pasan a traves de
el.
Para crear objetos
CipherOutputStream usaremos el siguiente
constructor:
Una vez tenemos creado
el stream de salida podemos utilizar los siguientes métodos:
void write() |
Escribe un byte encriptado o desencriptado según el estado del objeto Cipher. |
void write(byte b[], int off, int
len) |
Escribe len, bytes encriptados o desencriptados, de b, comenzando por b[off]. |
void flush() |
Realiza el proceso final y la salida de los datos restantes del proceso de encriptación o desencriptación. |
El Ejemplo siguiente es el mismo que el ejemplo visto en la sección anterior, pero utilizando en este caso los algoritmos RSA y MD5.
El Ejemplo siguiente consta de dos aplicaciones, una cliente que crea una clave, lee un fichero, y lo manda encriptado por la red a un servidor que creará una clave y desencriptara los datos leidos por la red.
El Ejemplo siguiente consta de tres aplicaciones:
Una aplicación para crear las claves del cliente y del servidor y
guardarlas en ficheros.
Una aplicación cliente que lee de un fichero sus dos claves y la clave
pública del servidor, lee un fichero, calcula la firma y encripta
los datos, crea un objeto "DadesSegures", con los datos anteriores y lo manda
por la red.
Una aplicación servidor que lee de un fichero sus dos claves y la
clave pública del cliente, lee el objeto "DadesSegures" de la red,
desencripta los datos, y verifica si la firma es correcta.