Criptografía en Java

Hola Habr! Les presento la traducción del artículo "Criptografía Java" de Jakob Jenkov.


Esta publicación es una traducción del primer artículo de criptografía de Java de una serie de artículos para principiantes que desean aprender los conceptos básicos de la criptografía en Java.


Tabla de contenido:


  1. Criptografía Java
  2. Cifrado
  3. Messagedigest
  4. Mac
  5. Firma
  6. Par de llaves
  7. Generador de claves
  8. KeyPairGenerator
  9. Keystore
  10. Keytool
  11. Certificado
  12. CertificateFactory
  13. CertPath

Criptografía Java


La API de criptografía de Java proporciona la capacidad de cifrar y descifrar datos en Java, así como administrar claves, firmas y autenticar mensajes (autenticar), calcular hashes criptográficos y mucho más.


Este artículo explica los conceptos básicos sobre cómo utilizar la API de criptografía de Java para realizar diversas tareas que requieren cifrado seguro.


Este artículo no explica los conceptos básicos de la teoría criptográfica. Tendrá que ver esta información en otro lugar.


Extensión de criptografía de Java


La API de criptografía de Java es proporcionada por la llamada Java Cryptography Extension (JCE). JCE ha sido durante mucho tiempo parte de la plataforma Java. Inicialmente, JCE se separó de Java debido a restricciones de exportación en tecnología de cifrado en los Estados Unidos. Por lo tanto, los algoritmos de cifrado más fuertes no se incluyeron en la plataforma estándar de Java. Estos algoritmos de cifrado más sólidos se pueden aplicar si su empresa se encuentra en los EE. UU., Pero en otros casos tendrá que usar algoritmos más débiles o implementar sus propios algoritmos de cifrado y conectarlos a JCE.


Desde 2017, las reglas para exportar algoritmos de cifrado en los Estados Unidos se han relajado significativamente, y en la mayoría de las partes del mundo puede usar estándares de cifrado internacionales a través de Java JCE.


Arquitectura de criptografía de Java


Java Cryptography Architecture (JCA) es el nombre del dise√Īo interno de API de criptograf√≠a en Java. JCA se estructura en torno a varias clases principales e interfaces de prop√≥sito general. Los proveedores proporcionan la funcionalidad real de estas interfaces. Por lo tanto, puede usar la clase Cipher para cifrar y descifrar algunos datos, pero la implementaci√≥n espec√≠fica del cifrado (algoritmo de cifrado) depende del proveedor particular utilizado.


También puede implementar y conectar sus propios proveedores, pero debe tener cuidado con esto. ¡Implementar correctamente el cifrado sin agujeros de seguridad es difícil! Si no sabe lo que está haciendo, probablemente sea mejor usar el proveedor de Java incorporado o un proveedor de confianza como Bouncy Castle.


Principales clases e interfaces


La API de criptografía de Java consta de los siguientes paquetes de Java:


  • java.security
  • java.security.cert
  • java.security.spec
  • java.security.interfaces
  • javax.crypto
  • javax.crypto.spec
  • javax.crypto.interfaces

Las principales clases e interfaces de estos paquetes:


  • Proveedor
  • SecureRandom
  • Cifrado
  • Messagedigest
  • Firma
  • Mac
  • Algoritmo Par√°metros
  • AlgorithmParameterGenerator
  • Keyfactory
  • SecretKeyFactory
  • KeyPairGenerator
  • Generador de claves
  • Keyagreement
  • Keystore
  • CertificateFactory
  • CertPathBuilder
  • CertPathValidator
  • CertStore

Proveedor


La clase de proveedor (java.security.Provider) es la clase central en la API de cifrado de Java. Para utilizar la API criptográfica de Java, debe instalar un proveedor de criptografía. El SDK de Java viene con su propio proveedor de criptografía. A menos que establezca explícitamente el proveedor de criptografía, se utilizará el proveedor predeterminado. Sin embargo, este proveedor criptográfico puede no admitir los algoritmos de cifrado que desea utilizar. Por lo tanto, es posible que deba instalar su propio proveedor de criptografía.


Uno de los proveedores de criptografía más populares para la API de criptografía Java se llama Bouncy Castle. Aquí hay un ejemplo en el que BouncyCastleProvider está configurado como proveedor criptográfico:


import org.bouncycastle.jce.provider.BouncyCastleProvider; import java.security.Security; public class ProviderExample { public static void main(String[] args) { Security.addProvider(new BouncyCastleProvider()); } } 

Cifrado


La clase Cipher (javax.crypto.Cipher) representa un algoritmo criptográfico. Un cifrado se puede usar tanto para el cifrado como para descifrar datos. La clase Cipher se explica con más detalle en las siguientes secciones, con una breve descripción a continuación.


Crear una instancia de la clase de cifrado que utiliza el algoritmo de cifrado AES para uso interno:


 Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); 

El método Cipher.getInstance (...) acepta una cadena que determina qué algoritmo de cifrado usar, así como algunos otros parámetros del algoritmo.
En el ejemplo anterior:


  • AES - algoritmo de cifrado
  • CBC es un modo en el que el algoritmo AES puede funcionar.
  • PKCS5Padding es c√≥mo el algoritmo AES debe procesar los √ļltimos bytes de datos para el cifrado. ¬ŅQu√© significa exactamente esto? Busque en el manual de criptograf√≠a en su conjunto y no en este art√≠culo.

Inicialización de cifrado


Antes de usar una instancia de cifrado, debe inicializarla. La instancia de cifrado se inicializa llamando al método init () . El método init () toma dos parámetros:


  • Modo: cifrado / descifrado
  • Clave

El primer parámetro indica el modo de operación de la instancia de cifrado: para cifrar o descifrar datos. El segundo parámetro indica qué clave usan para cifrar o descifrar datos.


Un ejemplo:


 byte[] keyBytes = new byte[]{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15}; String algorithm = "RawBytes"; SecretKeySpec key = new SecretKeySpec(keyBytes, algorithm); cipher.init(Cipher.ENCRYPT_MODE, key); 

Tenga en cuenta que el método de creación de claves en este ejemplo no es seguro y no debe usarse en la práctica. Este artículo en las siguientes secciones explicará cómo crear claves de manera más segura.


Para inicializar una instancia de cifrado para descifrar datos, debe usar Cipher.DECRYPT_MODE, por ejemplo:


 cipher.init(Cipher.DECRYPT_MODE, key); 

Cifrado o descifrado de datos


Despu√©s de inicializar el cifrado, puede comenzar a cifrar o descifrar los datos llamando a los m√©todos update () o doFinal () . El m√©todo update () se usa si est√° encriptando o desencriptando un dato. Se llama al m√©todo doFinal () cuando encripta la √ļltima pieza de datos o si el bloque de datos que pasa a doFinal () es un conjunto √ļnico de datos para encriptar.


Un ejemplo de encriptación de datos usando el método doFinal () :


 byte[] plainText = "abcdefghijklmnopqrstuvwxyz".getBytes("UTF-8"); byte[] cipherText = cipher.doFinal(plainText); 

Para descifrar los datos, debe pasar el texto cifrado (datos) al método doFinal () o doUpdate () .


Llaves


Para cifrar o descifrar datos, necesita una clave. Existen dos tipos de claves, seg√ļn el tipo de algoritmo de cifrado que se use:


  • Llaves sim√©tricas
  • Llaves asim√©tricas

Las claves simétricas se utilizan para algoritmos de cifrado simétricos. Los algoritmos de cifrado simétrico usan la misma clave para el cifrado y descifrado.
Las claves asim√©tricas se utilizan para algoritmos de cifrado asim√©tricos. Los algoritmos de cifrado asim√©trico usan una clave para el cifrado y otra para el descifrado. Los algoritmos de cifrado de clave p√ļblica y privada son ejemplos de algoritmos de cifrado asim√©trico.


De alguna manera, la parte que necesita descifrar los datos debe conocer la clave necesaria para descifrar los datos. Si el descifrador no es parte del cifrado de datos, las dos partes deben acordar una clave o intercambiar una clave. Esto se llama intercambio de claves.


Seguridad clave


Las claves deben ser difíciles de adivinar para que un atacante no pueda recoger fácilmente una clave de cifrado. En el ejemplo de la sección anterior sobre la clase Cipher, se utilizó una clave muy simple y codificada. En la práctica, esto no vale la pena hacerlo. Si la clave de las partes es fácil de adivinar, será fácil para un atacante descifrar los datos cifrados y posiblemente crear mensajes falsos por su cuenta. Es importante hacer una clave que sea difícil de adivinar. Por lo tanto, la clave debe consistir en bytes aleatorios. Cuantos más bytes aleatorios, más difícil es adivinar, porque hay más combinaciones posibles.


Generación clave


Para generar claves de cifrado aleatorias, puede usar la clase Java KeyGenerator. KeyGenerator se describir√° con m√°s detalle en los siguientes cap√≠tulos, aqu√≠ hay un peque√Īo ejemplo de su uso aqu√≠:


 KeyGenerator keyGenerator = KeyGenerator.getInstance("AES"); SecureRandom secureRandom = new SecureRandom(); int keyBitSize = 256; keyGenerator.init(keyBitSize, secureRandom); SecretKey secretKey = keyGenerator.generateKey(); 

La instancia de SecretKey resultante se puede pasar al método Cipher.init () , por ejemplo, así:


 cipher.init(Cipher.ENCRYPT_MODE, secretKey); 

Generación de pares de claves


Los algoritmos de cifrado asim√©trico utilizan un par de claves que consta de una clave p√ļblica y una clave privada para cifrar y descifrar datos. Para crear un par de claves asim√©tricas, puede usar KeyPairGenerator (java.security.KeyPairGenerator). KeyPairGenerator se describir√° con m√°s detalle en los siguientes cap√≠tulos, a continuaci√≥n se muestra un ejemplo simple de Java KeyPairGenerator:


 SecureRandom secureRandom = new SecureRandom(); KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("DSA"); KeyPair keyPair = keyPairGenerator.generateKeyPair(); 

Tienda de llaves


Java KeyStore es una base de datos que puede contener claves. Java KeyStore está representado por la clase KeyStore (java.security.KeyStore). Un almacén de claves puede contener claves de los siguientes tipos:


  • Claves privadas
  • Claves p√ļblicas y certificados (Claves p√ļblicas + certificados)
  • Llaves secretas

Las claves privadas y p√ļblicas se utilizan en cifrado asim√©trico. La clave p√ļblica puede tener un certificado asociado. Un certificado es un documento que prueba la identidad de una persona, organizaci√≥n o dispositivo que afirma poseer una clave p√ļblica.


El certificado suele estar firmado digitalmente por la parte que confía como prueba.
Las claves privadas se utilizan en el cifrado simétrico. La clase KeyStore es bastante compleja, por lo que se describe con más detalle más adelante en un capítulo separado sobre Java KeyStore.


Herramienta de administración de claves (Keytool)


Java Keytool es una herramienta de línea de comandos que puede funcionar con archivos Java KeyStore. Keytool puede generar pares de claves en un archivo KeyStore, exportar certificados e importar certificados en KeyStore y algunas otras funciones. Keytool viene con una instalación de Java. Keytool se describe con más detalle más adelante en un capítulo separado sobre Java Keytool.


Resumen del mensaje


Cuando recibe datos cifrados del otro lado, ¬Ņpuede estar seguro de que nadie ha cambiado los datos cifrados en el camino hacia usted?


Por lo general, la solución es calcular el resumen del mensaje a partir de los datos antes de cifrarlo, luego cifrar los datos y el resumen del mensaje, y enviarlo a través de la red. Un resumen de mensaje es un valor hash calculado en función de los datos del mensaje. Si se modifica al menos un byte en los datos cifrados, el resumen del mensaje calculado a partir de los datos también cambiará.


Cuando recibe datos cifrados, los descifra, calcula el resumen del mensaje a partir de ellos y compara el resumen calculado del mensaje con el resumen del mensaje enviado junto con los datos cifrados. Si los dos res√ļmenes de mensajes son iguales, existe una alta probabilidad (pero no del 100%) de que los datos no se hayan cambiado.


Java MessageDigest (java.security.MessageDigest) se puede utilizar para calcular res√ļmenes de mensajes. Para crear una instancia de MessageDigest, se llama al m√©todo MessageDigest.getInstance () . Hay varios algoritmos de resumen de mensajes diferentes. Debe especificar qu√© algoritmo desea usar al crear la instancia de MessageDigest. El trabajo con MessageDigest se describir√° con m√°s detalle en el cap√≠tulo Java MessageDigest.


Una breve introducción a la clase MessageDigest:


 MessageDigest messageDigest = MessageDigest.getInstance("SHA-256"); 

Este ejemplo crea una instancia de MessageDigest que utiliza el algoritmo de cifrado interno criptogr√°fico SHA-256 para calcular res√ļmenes de mensajes.


Para calcular el resumen del mensaje de algunos datos, llame al método update () o digest () . El método update () se puede llamar varias veces y el resumen del mensaje se actualiza dentro del objeto. Cuando haya pasado todos los datos que desea incluir en el resumen del mensaje, llame al resumen () y recupere el resumen del resumen del mensaje.


Un ejemplo de llamar a update () varias veces, seguido de una llamada a digest () :


 MessageDigest messageDigest = MessageDigest.getInstance("SHA-256"); byte[] data1 = "0123456789".getBytes("UTF-8"); byte[] data2 = "abcdefghijklmnopqrstuvxyz".getBytes("UTF-8"); messageDigest.update(data1); messageDigest.update(data2); byte[] digest = messageDigest.digest(); 

También puede llamar a digest () una vez, pasando todos los datos para calcular el resumen del mensaje. Un ejemplo:


 MessageDigest messageDigest = MessageDigest.getInstance("SHA-256"); byte[] data1 = "0123456789".getBytes("UTF-8"); byte[] digest = messageDigest.digest(data1); 

Código de autenticación de mensaje (MAC)


La clase Java Mac se usa para crear un MAC (Código de autenticación de mensaje) a partir de un mensaje. El MAC es similar a un resumen del mensaje, pero utiliza una clave adicional para cifrar el resumen del mensaje. Solo teniendo los datos de origen y la clave, puede verificar el MAC. Por lo tanto, un MAC es una forma más segura de proteger un bloque de datos de modificaciones que un resumen de mensaje. La clase Mac se describe con más detalle en el capítulo Java Mac, seguido de una breve introducción.


Una instancia de Java Mac se crea llamando al método Mac.getInstance () , pasando el nombre del algoritmo para usar como parámetro. Así es como se ve:


 Mac mac = Mac.getInstance("HmacSHA256"); 

Antes de crear un MAC a partir de datos, debe inicializar la instancia de Mac con la clave. Aquí hay un ejemplo de inicialización de una instancia de Mac con una clave:


 byte[] keyBytes = new byte[]{0,1,2,3,4,5,6,7,8 ,9,10,11,12,13,14,15}; String algorithm = "RawBytes"; SecretKeySpec key = new SecretKeySpec(keyBytes, algorithm); mac.init(key); 

Después de inicializar la instancia de Mac, puede calcular el MAC a partir de los datos llamando a los métodos update () y doFinal () . Si tiene todos los datos para calcular el MAC, puede llamar inmediatamente al método doFinal () . Así es como se ve:


 byte[] data = "abcdefghijklmnopqrstuvxyz".getBytes("UTF-8"); byte[] data2 = "0123456789".getBytes("UTF-8"); mac.update(data); mac.update(data2); byte[] macBytes = mac.doFinal(); 

Firma


La clase Signature (java.security.Signature) se usa para firmar datos digitalmente. Cuando se firman los datos, se crea una firma digital a partir de estos datos. Por lo tanto, la firma se separa de los datos.


Se crea una firma digital creando un resumen de mensaje (hash) a partir de los datos y encriptando este resumen de mensaje con la clave privada del dispositivo, persona u organización que debe firmar los datos. El resumen del mensaje cifrado se denomina firma digital.


Para crear una instancia de Signature, se llama al método Signature.getInstance (...) :


 Signature signature = Signature.getInstance("SHA256WithDSA"); 

Firma de datos


Para firmar datos, debe inicializar la instancia de firma en modo firma llamando al método initSign (...), pasando la clave privada para firmar los datos. Un ejemplo de inicialización de una instancia de firma en modo de firma:


 signature.initSign(keyPair.getPrivate(), secureRandom); 

Después de inicializar la instancia de firma, se puede usar para firmar los datos. Esto se hace llamando al método update (), pasando los datos de la firma como un parámetro. Puede llamar al método update () varias veces para complementar los datos para crear la firma. Después de pasar todos los datos al método update (), se llama al método sign () para obtener una firma digital. Así es como se ve:


 byte[] data = "abcdefghijklmnopqrstuvxyz".getBytes("UTF-8"); signature.update(data); byte[] digitalSignature = signature.sign(); 

Verificación de firma


Para verificar la firma, debe inicializar la instancia de firma en modo de verificaci√≥n llamando al m√©todo initVerify (...) , pasando como par√°metro la clave p√ļblica que se utiliza para verificar la firma. Un ejemplo de inicializaci√≥n de una instancia de firma en modo de verificaci√≥n se ve as√≠:


 Signature signature = Signature.getInstance("SHA256WithDSA"); signature.initVerify(keyPair.getPublic()); 

Después de la inicialización en modo de verificación, los datos firmados se transmiten al método update () . Una llamada al método verificar () devuelve verdadero o falso dependiendo de si la firma se puede verificar o no. Aquí está la verificación de firma:


 byte[] data2 = "abcdefghijklmnopqrstuvxyz".getBytes("UTF-8"); signature2.update(data2); boolean verified = signature2.verify(digitalSignature); 

Firma completa y ejemplo de verificación


 SecureRandom secureRandom = new SecureRandom(); KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("DSA"); KeyPair keyPair = keyPairGenerator.generateKeyPair(); Signature signature = Signature.getInstance("SHA256WithDSA"); signature.initSign(keyPair.getPrivate(), secureRandom); byte[] data = "abcdefghijklmnopqrstuvxyz".getBytes("UTF-8"); signature.update(data); byte[] digitalSignature = signature.sign(); Signature signature2 = Signature.getInstance("SHA256WithDSA"); signature2.initVerify(keyPair.getPublic()); signature2.update(data); boolean verified = signature2.verify(digitalSignature); System.out.println("verified = " + verified); 

Source: https://habr.com/ru/post/444764/


All Articles