#blogeando<a href="https://hashblogeando.wordpress.com/wp-content/uploads/2024/07/captura-desde-2024-07-01-22-31-12.png" rel="nofollow noopener" target="_blank"></a>Datos encriptados con llave publica PGP<p>En estos días de APIs e integraciones entre empresas la seguridad de los datos ya se empieza a tomar en serio, por lo que no es extraño que le soliciten encripte los datos antes de enviarlos. </p><p>Uno de los métodos de encriptados mas comúnmente usados con esos fines es la encriptación asimétrica vía una llave publica PGP/GPG, así que veamos como se hace.</p><p><strong>Llave publica PGP / GPG</strong></p><p>La encriptación asimétrica funciona de la siguiente manera, nosotros tenemos dos llaves diferentes, una que solo sirve para encriptar, la llave publica y una que solo sirve para desencriptar, la llave privada.</p><p>La llave publica la distribuimos con toda la gente de la que planeemos recibir información y con esta pueden generar un mensaje que solo puede ser descifrado con la llave privada.</p><p>La llave privada no la compartimos fuera de la organización y con esta podemos descifrar los mensajes encriptados con la llave publica.</p><p>Las llaves no son intercambiables la llave privada no sirve para generar mensajes y la llave publica no puede desencriptarlos por lo que debe ser cuidadoso en como maneja estos dos archivos.</p><p>Las llaves son manejadas por varios programas siendo los mas comunes, y los que le dan el nombre que usamos en esta entrada, el Pretty Good Privacy o PGP y el GNU Privacity Guard o GPG ambos son compatibles entre si y esas llaves funcionaran con el ejemplo que veremos a continuación.</p><p><strong>Bouncy Castle</strong></p><p>El framework mas popular y completo para encriptación en Java es Bouncy Castle y lo usaremos para </p><p>Dado que este framework cubre muchos métodos de encriptación esta dividido en múltiples módulos, para lo que necesitamos usaremos dos, el modulo bcpkix y bcpg los cuales puede encontrar en el repositorio de Maven en los siguientes enlaces:</p><p><a href="https://mvnrepository.com/artifact/org.bouncycastle/bcpkix-jdk18on" rel="nofollow noopener" target="_blank">Bouncy Castle PKIX, CMS, EAC, TSP, PKCS, OCSP, CMP, and CRMF APIs</a></p><p><a href="https://mvnrepository.com/artifact/org.bouncycastle/bcpg-jdk18on" rel="nofollow noopener" target="_blank">Bouncy Castle OpenPGP API</a></p><p>Detalle muy importante, Bouncy Castle es MUY dependiente de la versión del Kit de Desarrollo Java (JDK) que este utilizando y por tanto de la versión de la maquina virtual donde lo desea correr, estos enlaces son para el Java 18, si va a utilizar otra versión busque en Maven las versiones con versión JDK que planea usar y utilice esos, de otro modo es posible vea errores extraños al ejecutar su programa.</p><p>Si esta utilizando NetBeans le recomiendo agregue ambos módulos desde el dialogo de agregar dependencias al proyecto, ya que se ese se encargara de buscar la versión adecuada según la plataforma Java que halla configurado en su proyecto.</p><p><strong>Crear una llave publica GPG / PGP</strong></p><p>Si apenas esta agregando el encriptado a su programa lo mas posible es que no tenga una llave publica para realizar pruebas, en ese caso le recomiendo genere una para pruebas con ayuda de este muy útil sitio:</p><p><a href="https://pgpkeygen.com/" rel="nofollow noopener" target="_blank">https://pgpkeygen.com/</a></p><a href="https://hashblogeando.wordpress.com/wp-content/uploads/2024/07/captura-desde-2024-07-01-23-27-57.png" rel="nofollow noopener" target="_blank"></a>pgpkeygen.com, utileria web para generar llaves GPG / PGP<p>Su uso es sencillo, solo llene la información, presione «Generate Keys» y después de unos momentos tendrá el par de llaves la publica y privada, las cuales podrá descargar con el botón correspondiente, para nuestro ejemplo solo requeriremos de la llave publica.</p><p>Usando ese sitio se crearon las llaves usadas en este ejemplo, las cuales se incluyen como los archivos llave.gpg que contiene la llave publica y llavePrivada.gpg que contiene la llave privada.</p><p>Notara que uno de los campos en la utilería es PassPhrase, esta es la contraseña de la llave privada, la cual distingue entre mayúsculas y minúsculas y es necesaria para descifrar el mensaje, no hay forma de recuperarla desde las llaves así que asegúrese de no perderla.</p><p>La contraseña de la llave privada del ejemplo es hashHash téngalo en mente ya que usaremos esto al final del ejemplo.</p><p><strong>Ejemplo</strong></p><p>El proceso para aplicar la encartación es el siguiente:</p><ol><li>Cargar la llave publica desde su archivo a un InputFileStream</li><li>Inicializar Bouncy Castle</li><li>Convertir el mensaje que deseamos encriptar a un arreglo de bytes</li><li>Obtener el tamaño de ese arreglo de bytes</li><li>Convertir el arreglo de bytes a un ByteArrayInputStream</li><li>Crear un OutputStream para almacenar el resultado</li><li>Crear el objeto compresor de datos</li><li>Crear la configuración del encriptador</li><li>Crear el encriptador</li><li>Convertir el Stream de la llave publica a PGPPublicKey</li><li>Creamos el método de encriptado JcePublicKeyKeyEncryptionMethodGenerator</li><li>Agregamos ese método al encriptador</li><li>Si se usara la armadura convertirmos el Stream de salida en ArmoredOutputStream</li><li>Aplicamos la encriptacion</li><li>Convertidos los datos encriptados a un outputStream con PGPLiteralDataGenerator</li><li>Convertimos el OutputStream a un arreglo de bytes</li><li>Convertimos el arreglo de bytes a String</li><li>Mostramos en pantalla</li></ol><p>Notara que los datos a encriptar, la llave publica y los datos encriptados se manejan como Streams de diversos tipos, esto es por requerimientos de Bouncy Castle</p><p>Ahora veamos el código, para este ejemplo se usan dos clases, App que es la clase principal y EncriptadorDAO que efectúa el grueso del proceso, las lineas estan comentadas para hacer fácil seguir lo que hacen.</p><p><strong>App.java</strong></p> <pre>package mx.hash.encriptado;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.File;import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.IOException;import java.util.logging.Level;import java.util.logging.Logger;import org.bouncycastle.openpgp.PGPException;public class App { static private final Logger LOGGER = Logger.getLogger("mx.hash.encriptado.App"); public static void main(String[] args) { FileInputStream streamLlavePublica = null; try { File llavePublica = new File("llave.gpg"); streamLlavePublica = new FileInputStream(llavePublica); EncriptadorDAO encriptadorDAO = new EncriptadorDAO(); encriptadorDAO.inicializarBouncyCastle(); String texto = "HOLA-MUNDO"; byte[] datos = texto.getBytes(); ByteArrayInputStream datosStream = new ByteArrayInputStream(datos); Integer longitudDatos = datos.length; ByteArrayOutputStream salidaStream = new ByteArrayOutputStream(); encriptadorDAO.encriptar(salidaStream, datosStream, longitudDatos, streamLlavePublica); byte[] datosEncriptados = salidaStream.toByteArray(); String resultado = new String(datosEncriptados); LOGGER.log(Level.INFO, resultado); streamLlavePublica.close(); } catch (FileNotFoundException ex) { LOGGER.log(Level.SEVERE, "Archivo llave no encontrado"); ex.printStackTrace(); } catch (IOException | PGPException ex) { LOGGER.log(Level.SEVERE, "Error de entrada/salida"); ex.printStackTrace(); } }}</pre> <p><strong>EncriptadorDAO.java</strong></p> <pre>/* * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template */package mx.hash.encriptado;import java.io.IOException;import java.io.InputStream;import java.io.OutputStream;import java.security.SecureRandom;import java.security.Security;import java.time.LocalDateTime;import java.time.ZoneOffset;import java.util.Arrays;import java.util.Date;import java.util.Iterator;import java.util.Optional;import java.util.logging.Level;import java.util.logging.Logger;import org.bouncycastle.bcpg.ArmoredOutputStream;import org.bouncycastle.bcpg.CompressionAlgorithmTags;import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags;import org.bouncycastle.jce.provider.BouncyCastleProvider;import org.bouncycastle.openpgp.PGPCompressedDataGenerator;import org.bouncycastle.openpgp.PGPEncryptedDataGenerator;import org.bouncycastle.openpgp.PGPException;import org.bouncycastle.openpgp.PGPLiteralData;import org.bouncycastle.openpgp.PGPLiteralDataGenerator;import org.bouncycastle.openpgp.PGPPublicKey;import org.bouncycastle.openpgp.PGPPublicKeyRing;import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;import org.bouncycastle.openpgp.PGPUtil;import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator;import org.bouncycastle.openpgp.operator.jcajce.JcePGPDataEncryptorBuilder;import org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyKeyEncryptionMethodGenerator;/** * * @author david */public class EncriptadorDAO { static private final Logger LOGGER = Logger.getLogger("mx.hash.encriptado.EncriptadorDAO"); private final int algoritmoCompresion = CompressionAlgorithmTags.ZIP; private final int algoritmoLlaveSimetrica = SymmetricKeyAlgorithmTags.AES_128; private final boolean armadura = true; private final boolean chequeoIntegridad = true; private final int bufferSize = 1 << 16; public void inicializarBouncyCastle() { LOGGER.log(Level.INFO, "Inicializando Bouncy Castle"); Security.addProvider(new BouncyCastleProvider()); } public void encriptar(OutputStream encriptado, InputStream datosStream, long longitudDatos, InputStream llavePublicaStream) throws IOException, PGPException { LOGGER.log(Level.INFO, "Encriptando datos"); // Compresor de datos PGPCompressedDataGenerator compresor = new PGPCompressedDataGenerator(algoritmoCompresion); // Objeto con las configuraciones para encriptacion JcePGPDataEncryptorBuilder constructorPGP = new JcePGPDataEncryptorBuilder(algoritmoLlaveSimetrica); constructorPGP.setWithIntegrityPacket(chequeoIntegridad); constructorPGP.setSecureRandom(new SecureRandom()); constructorPGP.setProvider(BouncyCastleProvider.PROVIDER_NAME); // Encriptador de datos PGPEncryptedDataGenerator pgpEncryptedDataGenerator = new PGPEncryptedDataGenerator(constructorPGP); // Objeto de la llave publica PGPPublicKey llavePublica = obtenerLlavePublica(llavePublicaStream); // Metodo de encriptado JcePublicKeyKeyEncryptionMethodGenerator metodo = new JcePublicKeyKeyEncryptionMethodGenerator(llavePublica); // Agregamos el metodo de encriptado deseado al encriptador pgpEncryptedDataGenerator.addMethod(metodo); // Si se esta usando la armadura generamos el Stream adecuado if (armadura) { encriptado = new ArmoredOutputStream(encriptado); } // Creamos el Stream con los datos encriptados OutputStream cipherOutStream = pgpEncryptedDataGenerator.open(encriptado, new byte[bufferSize]); // Generamos datos finales apartir del Stream de datos encriptados generarDatos(compresor.open(cipherOutStream), datosStream, longitudDatos, bufferSize); // Cerramos los streams compresor.close(); cipherOutStream.close(); encriptado.close(); } public PGPPublicKey obtenerLlavePublica(InputStream keyInputStream) throws IOException, PGPException { LOGGER.log(Level.INFO, "Obteniendo llave Publica"); PGPPublicKeyRingCollection pgpPublicKeyRings = new PGPPublicKeyRingCollection( PGPUtil.getDecoderStream(keyInputStream), new JcaKeyFingerprintCalculator()); Iterator<PGPPublicKeyRing> keyRingIterator = pgpPublicKeyRings.getKeyRings(); while (keyRingIterator.hasNext()) { PGPPublicKeyRing pgpPublicKeyRing = keyRingIterator.next(); Optional<PGPPublicKey> pgpPublicKey = extraerPGPKey(pgpPublicKeyRing); if (pgpPublicKey.isPresent()) { return pgpPublicKey.get(); } } throw new PGPException("Llave publica invalida"); } public Optional<PGPPublicKey> extraerPGPKey(PGPPublicKeyRing pgpPublicKeyRing) { LOGGER.log(Level.INFO, "Extrayendo llave PGP"); for (PGPPublicKey publicKey : pgpPublicKeyRing) { if (publicKey.isEncryptionKey()) { return Optional.of(publicKey); } } return Optional.empty(); } public void generarDatos(OutputStream copia, InputStream datosOriginales, long longitudDatos, int bufferSize) throws IOException { LOGGER.log(Level.INFO, "Generandos datos finales"); PGPLiteralDataGenerator literalDataGenerator = new PGPLiteralDataGenerator(); OutputStream pOut = literalDataGenerator.open(copia, PGPLiteralData.BINARY, PGPLiteralData.CONSOLE, Date.from(LocalDateTime.now().toInstant(ZoneOffset.UTC)), new byte[bufferSize]); byte[] buffer = new byte[bufferSize]; try { int len; long totalBytesWritten = 0L; while (totalBytesWritten <= longitudDatos && (len = datosOriginales.read(buffer)) > 0) { pOut.write(buffer, 0, len); totalBytesWritten += len; } pOut.close(); } finally { // Clearing buffer Arrays.fill(buffer, (byte) 0); // Closing inputstream datosOriginales.close(); } }}</pre> <p>Bastante mas código de lo que tal vez esperaba, ¿No? Esto es mas que nada por la necesidad de un par de funciones conversoras de formatos relacionadas con la estructura interna de una llave publica, algo que no ocupamos mover por el momento.</p><p>Si recuerda del proceso listado a principio de esta sección los pasos del 7 al 14 ocurren en la función EncriptadorDAO.encriptar.</p><p>Ya que corra el programa obtendrá una salida similar a esta, la encriptación usa la fecha y hora del sistema como uno de los parámetros así que no le extrañe si cada vez que corra el programa obtiene una salida diferente.</p><a href="https://hashblogeando.wordpress.com/wp-content/uploads/2024/07/captura-desde-2024-07-02-00-59-20.png" rel="nofollow noopener" target="_blank"></a>Salida del programa<p>Donde lo que esta entre —–BEGIN PGP MESSAGE—– y —–END PGP MESSAGE—– es nuestro mensaje encriptado</p> <pre>-----BEGIN PGP MESSAGE-----Version: BCPG v1.70hQEMAwOZTa/i2J0tAQf/YhzovUXVdSxFAeQNkWrBFnVx32wa6P2o+dqrlGSAxXs1XIsFxgbu/9ZXv2OTbHGj3X/TblRbw4+tnAqkrYyjwnzfhC1eqgcel4uT/mqsmNKh5JOstOQENDvHzd8G6yvFEUAXA+UJDOhudME5vxtj6yV8VNWgimqCXBFkU2Yd47KutI1W+2nrtjliYPbZybhBCdi0lN9w9HSCoNZY9CBN8OGSq1G7ukIT89X8r0p0pOzI2RwsLt1/7GZRGgrU7VZNumaxxxFah3ETjlOAOPNyN/O0IO+a7fWsMbTldGv0c1jD43i+uArhWehaFcDMPbDwDb9NkvbKCHSzsCZJayfzqdJHAfDbfiih0Iz2U+w0PEPJlVKymUuIJwYdc2gSr0QOeWwV8vI9LGCmQMzSkj37WtZ9ZdBK0BJxbDyBaAhfRmKmZSnW1R7SwdU==q8RV-----END PGP MESSAGE-----</pre> <p><strong>Verificando salida del programa</strong></p><p>Y seguramente se preguntara, ¿Hay alguna forma de validar que todo saliera bien?, claro que la hay, ya que tenemos la llave privada y la contraseña de la misma podemos ir a cualquier aplicación que descifre PGP / GPG y descifrar el mensaje.</p><p>Para este ejemplo suguiero <a href="https://pgptool.org/" rel="nofollow noopener" target="_blank">https://pgptool.org/</a> en especifico la pestaña Decrypt (+Verify)</p><a href="https://hashblogeando.wordpress.com/wp-content/uploads/2024/07/captura-desde-2024-07-02-00-38-28.png" rel="nofollow noopener" target="_blank"></a>Decrypt (+Verify) de PGP Tool<p>En «Receiver’s Private Key (For decryption purpose)» cargaremos la llave privada del ejemplo (llavePrivada.gpg) , pondremos la contraseña de la llave privada en «Passphrase for private key» y copiaremos TODO el mensaje encriptado (incluso los guiones) al cuadro de texto «Encrypted PGP Message»</p><a href="https://hashblogeando.wordpress.com/wp-content/uploads/2024/07/captura-desde-2024-07-02-01-00-24.png" rel="nofollow noopener" target="_blank"></a>Datos ingresados<p>Ya que tenga todos los datos en si lugar presione «Decrypt the message»</p><a href="https://hashblogeando.wordpress.com/wp-content/uploads/2024/07/captura-desde-2024-07-02-01-00-57.png" rel="nofollow noopener" target="_blank"></a>Mensaje descifrado<p>Y obtendrá el mensaje que encriptamos HOLA-MUNDO.</p><p>Con esto podemos estar seguros que la encriptación se aplico correctamente, ya que una aplicación adicional fue capaz de descifrar el mensaje.</p><p>Notara que aparece un mensaje amarillo que dice «Decrypted, but incorrect fingerprint – signature not verified. If this message encrypted without signature – ignore this message.» esto se debe a que no aplicamos una firma a la encriptación, esto es un proceso adicional que por el momento no requerimos.</p><p>Si desea el código completo de este ejemplo puede encontrarlo aquí:</p><p><a href="https://gitlab.com/ticomWebcomic/encriptadopgp" rel="nofollow noopener" target="_blank">https://gitlab.com/ticomWebcomic/encriptadopgp</a></p><p>Espero que esta entrada le fuera de utilidad, nos vemos en la próxima y si desea cooperar con la causa.</p> <a href="https://ko-fi.com/Q5Q0EMQV" rel="nofollow noopener" target="_blank"></a> <p><a href="https://hashblogeando.wordpress.com/2024/07/02/encriptar-con-una-llave-publica-pgp-gpg-en-java/" class="" rel="nofollow noopener" target="_blank">https://hashblogeando.wordpress.com/2024/07/02/encriptar-con-una-llave-publica-pgp-gpg-en-java/</a></p><p><a rel="nofollow noopener" class="hashtag u-tag u-category" href="https://hashblogeando.wordpress.com/tag/encriptacion/" target="_blank">#encriptacion</a> <a rel="nofollow noopener" class="hashtag u-tag u-category" href="https://hashblogeando.wordpress.com/tag/java/" target="_blank">#java</a></p>