Técnicas de programación segura con Java
Sumario
Introdución
Java inclúe un conxunto de API que permite garantizar a seguridade de comunicacións, mediante o uso de criptografía e o control de acceso.
Criptografía con JCA
JCA (Java Chriptography Architecture) é unha arquitectura baseada en proveedores que proporciona un conxunto de API para todo o relacionado con criptografía de clave privada e pública:
- Firma dixital
- Hash ou digest de mensaxes
- Certificados dixitais
- Encriptación simétrica e asimétrica
- Xeneración e gestión de claves
- Xeneración de números aleatorios
JCA é unha arquitectura estándar de Java, parte do JDK, que proporciona un marco ou framework no que se integran servizos criptográficos implementados por diferentes proveedores de servizos criptográfifios (ou CSP, cryptographic service provider).
Inclúe paquetes coma:
- java. security
- javax.crypto
- javax.crypto.spec
- javax.crypto.interfaces
Os principios de deseño desta arquitectura son:
- Independencia de implementación. Os servizos de seguridade son proporcionados por proveedores enlazados coa plataforma Java mediante interfaces estándares. Unha aplicación pode utilizar servizos proporcionados por múltiples proveedores independentes.
- Interoperabilidade. Os servicios proporcionados por distintos proveedores son compatibles entre eles. Existen clases que proporcionan un tipo determinado de servizo, e a diferentes algoritmos para este tipo de servizo. Estas se denominan engine classes e son as seguintes:
| Clase | Descripción |
|---|---|
| MessageDigest | Para funcións hash ou digest |
| KeyGenerator | Para xeneración e xestión de claves para criptografía simétrica |
| KeyPairGenerator | Para xeneración e xestión de pares de claves para criptografía asimétrica ou de clave pública |
| Cipher | Para algoritmos de cifrado de datos |
| Signature | Para algoritmos de sinatura dixital |
TAREFA. Obtén os proveedores disponibles no JDK que estés utilizando co método getProviders() da clase Security. Compara cos que aparecen no ficheiro de configuración do JDK, no ficheiro de configuración do JDK conf/security/java.security. Para cada un amosa o seu nome e outra información relevante. Obtén despois todos os servizos proporcionados polo proveedor. Amosa para cada un o seu tipo e o nome do algoritmo. Presta atención os distintos tipos de servizos. Identifica os algoritmos que recoñezas, e localiza algún algoritmo proporcionado por máis dun proveedor.
Explican as capacidades de JCA nos siguientes apartados:
- funcións de hash ou digest
- criptografía de clave simétrica ou privada (comenzando pola xeneración de claves)
- criptografía de clave asimétrica ou de clave pública (comezando pola xeneración de pares de claves)
- certificados dixitais
Resúmenes de mensajes (funcións de hash ou digest)
JCA conten implementacións para os principales algoritmos de hash ou digest. Estos están disponibles mediante a engine classes MessageDigest.
Existen distintos algoritmos de hash: MD5, SHA-1, SHA2 y SHA-3.
NOTA. MD5 e SHA-1 non se consideran seguros na actualidade para criptografía.
Para xenerar un hash con JCA procedese así:
1. Créase un obxecto de clase MessageDigest co método estático getInstance() da mesma clase, especificando o nome do algoritmo. Opcionalmente pódese especificar o nome do proveedor.
2. Engádense datos co método update(). Pódese engadir un byte ou un array de tipo byte[]. Esto se pode facer repetidamente para ir engadindo novos datos.
3. Obtense o valor de hash co método digest().
4. Se quixeramos calcular un novo hash invocaríase ó método reset().
Exemplo
O seguinte programa de exemplo realiza o hash SHA-2 de 256 bits dunha cadea de caracteres introducida como parámetros de líña de comando.
1 package generacionhash; 2 import java.security.MessageDigest; 3 import java.security.NoSuchAlgorithmException; 4 public class GeneracionHash { 5 public static void main(String[] args) { 6 if (args.length < 1) { 7 System.out.println( 8 “Introducir cadea de caracteres para calcular hash”); 9 return; 10 } 11 12 String cadena = args[0]; 13 for(int i=1; i<args.length; i++) { 14 cadena += “ “+args[i]; 15 } 16 MessageDigest md; 17 try { 18 byte[] bytes = cadena.getBytes(); 19 md = MessageDigest.getInstance(“SHA-256”); 20 md.update(bytes); 21 byte[] hash = md.digest(); 22 System.out.printf(“Cadea: [%s]\nHash: [%s]\n.”, 23 cadena, valorHexadecimal(hash)); 24 } catch (NoSuchAlgorithmException e) { 25 System.out.println(“Non disponible algoritmo de hash”); 26 } 27 } 28 static String valorHexadecimal(byte[] bytes) { 29 String result = “”; 30 for (byte b : bytes) { 31 result += String.format(String.format(“%x”, b)); 32 } 33 return result; 34 } 35 }
Pódese comparar o resultado co obtido con comandos en linux.
Por exemplo o xeito de obter en Linux o hash SHA-2 de 256 bits para a cadena “Probando cadea” é
$ echo -n “Probando cadea” sha256sum da73486a45ca51ab22c9046b908a8150d21988d6b346f6c67fcd2fb4bc62e8ea
Nota. A opción -n é importante para eliminar o retorno de carro
Tarefa. Probar este programa para a mesma cadea de caracteres e cotexa a súa saída coa de un dos comandos de Linux: md5sum, sha1sum, sha224sum, sha256sum, etc.
Xeneración e xestión de claves
Desde unha perspectiva práctica das aplicacións en Java que deben utilizar as claves, caben dúas consideracións: 1. O valor en sí das claves.
Se se trata de criptografía de clave privada, hai unha soa clave e se pode elixir libremente dentro do posible conxunto de valores posibles, delimitado fundamentalmente pola lonxitude da clave, que é distinta para cada algoritmo de cifrado de clave simétrica.
Se se trata de clave pública, hai un par de claves relacionadas e a súa xeneración é máis complexa, debido á relación matemática que existe entre elas. As claves para algoritmos de cifrado adoitan xenerarse mediante métodos específicos de determinadas clases que implementan algoritmos de xeneración de claves.
2. O seu almacenamento no sistema. As claves almacenaranse en ficheiros. Unha colección de claves pódese almacenar en repositorios especiales denominados keyrings (chaveros).
Criptografía de clave privada
Criptografía de clave públia
Xeneración e uso de certificados dixitais
Xeneración e uso de firma dixital
Comunicacións seguras con JSSE
JSSE é unha implementación de TLS/SSL en Java. As clases de JSSE están no paquete javax.net.ssl.
SSLServerSocket é filla de ServerSocket, e engade unha capa de encriptación baseada en TLS/SSL, de xeito que os datos que se transmiten en datagramas de TCP van cifrados. Para ela existe unha clase de tipo factory, SSLServerSocketFactory.
JSSE implementa diversas versións de TLS/SSL, e diversas opcións destes protocolos. Para sacar partido de todas elas, basta con que cada parte teña a súa clave privada e a clave pública da outra parte. Pero se non se precisa autenticación de cliente, bastaría con xenerar o par de claves para el servidor.
Pódese xenerar o par de claves pública e privada para cada parte con ferramentas de openssl; pero tambén con keygen, unha ferramenta propia de Java.
En cada parte (cliente e servidor) utilizaranse dous almacéns de claves (keystore). Os keystores xestionanse tamén con keygen.
- nun keystore para o propio certificado almacena o propio certificado coa clave privada, xenerado coa opción -genkey, para o algoritmo RSA (–keyalg RSA), protexido por contrasinal especificada coa opción -storepass. Despois, se exporta a un ficheiro con -exportkey e -file.
- dado que os certificados anteriores son autoasinados, para que se admitan pola outra parte, debe establecerse o seu emisor coma unha CA ou autoridade de certificación (o seu certificado, que conten a súa clave pública, debe importarse con -importcert a un keystore para certificados de autoridades de certificación ou emisores de confianza). Esto faise coa opción –trustcacerts (abreviatura de trusted CA certificates ou certificados de AC de confianza).
O seguiente script fai todo isto para ambas partes. Pero, para o uso máis habitual, no que non se realiza validación de cliente, solo son necesarias as operacións en grosa. É dicir, a xeneración nun keystore para o servidor dun certificado dixital coa súa clave pública máis a clave privada correspondente, e a importación do certificado coa clave pública nun keystore para o cliente.
mkdir certificados mkdir certificados/servidor mkdir certificados/cliente # Xenerar e exportar certificados de servidor e cliente '''keytool -genkey -alias certServ -keyalg RSA -keystore keystoreServ -storepass pwdkeystoreserv''' '''keytool -exportcert -alias certServ -keystore keystoreServ -storepass pwdkeystoreserv -file certificados/servidor/servidor.cer''' keytool -genkey -alias certCli -keyalg RSA -keystore keystoreCli -storepass pwdkeystorecli keytool -exportcert -alias certCli -keystore keystoreCli -storepass pwdkeystorecli -file certificados/cliente/cliente.cer # Importar coma certificados de confianza certificado de servidor para cliente e de cliente para servidor '''keytool -importcert -trustcacerts -alias certConfCli -file certificados/servidor/servidor.cer -keystore keystoreCliCertConf -storepass pwdkeystoreconfcli''' keytool -importcert -trustcacerts -alias certConfServ -file certificados/cliente/cliente.cer -keystore keystoreServCertConf -storepass pwdkeystoreconfserv
Exemplo de eco con TLS/SSL
Amósanse as diferenzas (mínimas) entre os programas cliente e servidor de eco:
- o desenvolto anteriomente para TCP
- o programa equivalente para TLS/SSL, que fai uso das clases de JSSE.
No programa servidor para TLS/SSL con JSSE créase un socket de servidor da clase SSLServerSocket a partir dunha instancia de SSLServerSocketFactory. Antes hai que asignar valor a determinadas propiedades do sistema javax.net.ssl, en concreto:
- os nomes dos keystores ou almacenes de claves
- os contrasinais para acceder a eles.
So se usa un keystore que conten a clave privada do servidor. A SSLServerSocketFactory obtense con SSLServerSocketFactory.getDefault(), pero podense utilizar diversos constructores con diversos parámetros para utilizar as diferentes opcións de TLS/SSL.
| + Cambios en servidor baseado en TCP para utilizar TLS/SSL | Servidor para TCP | Servidor para TLS/SSL (con JSSE) |
|---|---|---|
| package socketstcp_servidoreco; | package jsse_servidoreco; | |
| import javax.net.ssl.SSLServerSocketFactory; public class SocketsTCP_ServidorEco { public class JSSE_ServidorEco { | ||
| System.setProperty(“javax.net.ssl.keyStore”,”keystoreServ”);
System.setProperty(“javax.net.ssl.keyStorePassword”,”(pwd) “); //System.setProperty(“javax.net.ssl.trustStore”,”keystoreServCertConf”); //System.setProperty(“javax.net.ssl.trustStorePassword”,”(pwd)”); | ||
| try (ServerSocket socketServidor = new ServerSocket(numPuerto)) { | SSLServerSocketFactory sfactSSL = (SSLServerSocketFactory) SSLServerSocketFactory.getDefault(); try (ServerSocket socketServidor = sfactSSL.createServerSocket(numPuerto)) { |
Entre os programas clientes as diferenzas son análogas. Con JSSE crease un socket de comunicación da SSLSocket a partir dunha instancia de SSLSocketFactory. Antes hai que asignar valor a determinadas propiedades del sistema javax.net.ssl:
- os nombres dos keystores ou almacenes de claves
- os contrasinais para acceder a eles.
Solo se usa, en reciprocidade co programa de servidor, un keystore que conten a clave pública do servidor. A SSLSocketFactory obtense con SSLSocketFactory.getDefault(), pero se poden utilizar diversos constructores con diferentes parámetros para utilizar aa opcións de TLS/SSL.
| Cliente para TCP | Cliente para TLS/SSL (con JSSE) |
|---|---|
| package socketstcp_clienteeco; | package jsse_clienteeco; |
| import javax.net.ssl.SSLSocketFactory; | |
| public class SocketsTCP_ClienteEco { | public class JSSE_ClienteEco { |
// System.setProperty(“javax.net.ssl.keyStore”,”keystoreCli”); // System.setProperty(“javax.net.ssl.keyStorePassword”,”(pwd)”); System.setProperty(“javax.net.ssl.trustStore”, “keystoreCliCertConf”); System.setProperty (“javax.net.ssl.trustStorePassword”, “(pwd)”); | |
| try (Socket socketComunicacion = new Socket(nomHost, numPuerto)) { | SSLSocketFactory sfactSSL = (SSLSocketFactory) SSLSocketFactory.getDefault(); try (Socket socketComunicacion = sfactSSL.createSocket(nomHost, numPuerto)) { |
Se se fixera autenticación do cliente debería utilizarse no cliente o keystore para a súa clave privada; e no servidor, un keystore para incluir o seu certificado coma un certificado de confianza, ou no seu defecto certificados raíces ou intermedios que permitiran validalo.
Control de acceso con JAAS
JAAS (Java Authentication and Authorization Service, servicio de autorización y autenticación de Java) es una interfaz que pennite a las aplicaciones Java acceder a servicios de control de autenticación y acceso. Pódese utllizar para dous propósitos:
- para a autenticación de usuarios: para detenninar de forma fiable e segura quén está executando o noso código Java
- para a autorización de usuarios: para asegurarse de que quen o executa ten os permisos necesarios para realizar as accións
Están involucradas as seguientes clases e interfaces:
- LoginContext, contexto de inicio de sesión: inicia e xestiona o proceso de autenticación mediante a creación dun Subject.
- A autenticación faise chamando ó método login()
- LoginModule, módulo de conexión: é a interfaz que debe implementarse para definir os mecanismos de autenticación na aplicación. Encárgase de validar os datos nun proceso de autenticación. Debense implementar os seguintes métodos:
- initialize()
- login()
- commit()
- abort()
- logout()
- Subject: clase que representa a un ente autenticable dentro da aplicación (entidade,usuario, sistema)
- Principal: clase que representa os atributos que posee cada Subject recuperado una vez que se efectua o ingreso á aplicación. Un Subject puede contener varios principales.
- CallBackHandler: interfaz que se debe implementar cando se precisa recibir do usuario a información para a autenticación; encárgase da interacción co usuario para obter os datos de autenticación. Débese desarrollar o método handle() para implementala.
As clases e interfaces principais de JAAS están disponibles nos seguintes paquetes:
- javax.security.auth.*: contén as clases de base e interfaces para os mecanismos de autenticación e autorización
- javax.security.auth.callback.*: contén as clases e interfaces para definir as credenciais de autenticación da aplicación
- javax.security.auth.login.*: contén as clases para entrar e sair dun dominio de aplicación
- javax.security.auth.spi.*: contén interfaces para un proveedor de JAAS para implementar módulos JAAS
Autenticación
Autorización
Resumo
- JCA (Java Chriptography Architecure) é unha arquitectura de Java para criptografía.
- JSSE (Java Secure Socket Extension) é unha implementación en Java de TLS/SSL.
- JAAS (Java Authentication and Authorization Service) proporciona
- autenticación de usuarios que executan código de Java
- autorización de usuarios para realizar determinadas accións
Ampliación
- Invesigar o uso de Apache Shiro, Spring Security e OACC como substitutos de JAAS.
Actividades propostas
Xenera o resumo dun ficheiro
O ficheiro débese elixir do disco duro coa clase JFileChooser. O resumo se debe amosar en hexadecimal. Utiliza os algoritmos SHA e MD5 e comproba os resúmenes xenerados
Creación de sockets seguros
Crea unha conexión sobre un socket servidor seguro e que atenderá ata catro conexións de clientes que se identificarán cun certificado válido. O servidor agarda as conexións; cada cliente que se conecta recibe unha mensaxe e a continuación envíalle un saúdo.
Autenticación e autorización
Elabora unha aplicación que nos pida o nome de usuario e contrasinal desde unha ventana de diálogo. A autenticación farase mediante Jaas, onde os usuarios Xosé, Lois e Alba terán acceso. A continuación, haberá dous botóns:
- amosar o número de ficheiros que hai no directorio onde se está executando o programa (poden facelo Xosé, Lois e Alba)
- crear un ficheiro que conteña o nome dos ficheiros que hai no directorio onde se está executando o programa (só pode facelo Xosé)
Referencias e créditos
- Encriptación con Java
- Socket SSL con Java
- JSSE y JAAS
- Java Security Libraries
- Java Cryptography Architecture (JCA) Reference Guide
- Java Secure Socket Extension (JSSE) Reference Guide
- Java Authentication and Authorization Service (JAAS) Reference Guide
- JAAS Exemplo
- Using a CallBackHandler in Java Authentication and Authorization Services (JAAS)
- Online Tools