¿Quiere comprender Pretty Good Privacy? Simularlo.

Como sugiere su nombre, Pretty Good Privacy (o PGP) es un programa de cifrado que en realidad proporciona una privacidad bastante buena. La parte "bastante buena" está destinada a ser un eufemismo irónico. Ha sido una de las formas dominantes de cifrado de extremo a extremo para las comunicaciones por correo electrónico después de que Phil Zimmermann lo desarrolló en 1991. Se hizo cada vez más popular después de que lo utilizara el denunciante Edward Snowden.

PGP proporciona dos cosas esenciales para una comunicación segura:

  1. Confidencialidad: proporcionada mediante el uso de cifrado de bloque simétrico, compresión mediante el algoritmo ZIP y compatibilidad de correo electrónico con el esquema de codificación radix64
  2. Autenticación: proporcionada mediante el uso de firmas digitales

Sin más preámbulos, vayamos al funcionamiento de PGP.

Cómo funciona

Estaré explicando el concepto de PGP desde el punto de vista de la implementación en el contexto de Alice (el remitente) y Bob (el receptor). Usaremos los siguientes algoritmos:

  1. RSA como algoritmo de cifrado asimétrico
  2. SHA-512 como algoritmo hash
  3. DES como el algoritmo de cifrado simétrico y
  4. ZIP para compresión

También puede utilizar otros algoritmos. (Sé que DES es demasiado antiguo para usarlo, pero el objetivo aquí es comprender el concepto de PGP).

Alice y Bob generan su par de claves (claves públicas y privadas) utilizando el algoritmo RSA. Las claves públicas de Alice y Bob deben conocerse entre sí.

Lado de Alice / remitente:

  1. Alice escribe un mensaje M, que tiene la intención de enviar a Bob.
  2. M se proporciona como entrada al algoritmo SHA-512 para obtener el hash binario de 512 bits (representado como una cadena hexadecimal de 128 bits) del mismo.
  3. Este hash está firmado digitalmente mediante el algoritmo RSA, es decir, el hash está encriptado por las claves privadas de Alice. Las entradas a RSA son las claves privadas de Alice y el hash. La salida de RSA es el hash firmado digitalmente o el hash cifrado EH.
  4. Ahora, M y EH se agregan juntos. (Se agrega en el sentido de que se colocan en una matriz de cadenas).
  5. M y EH (que están en una matriz de cadenas) actúan como entrada al algoritmo de compresión ZIP para obtener la M comprimida y la EH comprimida, nuevamente en una matriz de cadenas.
  6. La salida del paso anterior ahora se cifra mediante el algoritmo de cifrado simétrico DES. Para ello, primero generaremos la SecretKey para DES. Esta clave y la salida del paso 5 actuarán como entrada al algoritmo de encriptación DES que nos proporcionará una salida encriptada (nuevamente en una matriz de cadenas).
  7. Por último, pero no menos importante, dado que M se cifra con SecretKey, también debe enviarse a Bob. Encriptaremos el algoritmo SecretKey de DES con la clave pública de Bob. Usaremos RSA para esto y las entradas serán la clave pública de Bob y SecretKey.
  8. Los resultados de los pasos 6 y 7 ahora se adjuntan y se envían como mensaje final a Bob.

El mensaje completo se envía como una matriz de cadenas ( String finalmessage[]) que contiene lo siguiente en los índices:

0: mensaje comprimido M que está encriptado con SecretKey

1: Hash EH firmado digitalmente que luego se comprime y encripta con SecretKey

2: Salida del paso 7

Lado de Bob / receptor:

  1. Bob primero descifrará la clave secreta de DES con sus claves privadas. Las entradas al algoritmo RSA para esto serán las claves privadas de Bob y finalmessage[2]. La salida de RSA le dará a Bob la clave secreta.
  2. Esta SecretKey ahora actuará como una de las entradas al algoritmo de descifrado DES para descifrar finalmessage[0]y finalmessage[1]. Estos dos también actuarán como entradas al algoritmo de descifrado DES. La salida de este paso será decrypted versionde finalmessage[0]y finalmessage[1].
  3. Las salidas del paso anterior deben proporcionarse como entrada al algoritmo ZIP para la descompresión.
  4. Del resultado del paso anterior, obtendremos el hash firmado digitalmente y el mensaje original M. Verificaremos si el hash fue firmado por Alice. Para ello, calcularemos el hash del mensaje original M usando SHA-512 ( calculated_hash). También descifraremos el hash firmado digitalmente con las claves públicas de Alice usando RSA (Entradas a RSA: hash firmado digitalmente y claves públicas de Alice y Salida de RSA:) decrypted_hash.
  5. Compare el decrypted_hashy calculated_hash. Si resultan ser iguales, entonces se logra la autenticación, lo que significa que el mensaje fue enviado por Alice.

La siguiente es la simulación de PGP realizada de la forma más sencilla utilizando Java.

import java.util.*; import java.math.*; import javax.crypto.Cipher; import java.security.*; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; import javax.crypto.spec.*; public class PGP{ static Cipher ecipher, dcipher;//Required for DES public static void main(String args[]) throws Exception{ //Generating sender keys KeyPair senderkeyPair = buildKeyPair(); PublicKey senderpubKey = senderkeyPair.getPublic(); PrivateKey senderprivateKey = senderkeyPair.getPrivate(); //Generating receiver keys KeyPair receiverkeyPair = buildKeyPair(); PublicKey receiverpubKey = receiverkeyPair.getPublic(); PrivateKey receiverprivateKey = receiverkeyPair.getPrivate(); //Sending both public keys and private keys for choice of digital signature or normal assymetric encryption String messagetoreceiver[] = senderside(senderpubKey, senderprivateKey, receiverpubKey, receiverprivateKey); receiverside(messagetoreceiver, senderpubKey, senderprivateKey, receiverpubKey, receiverprivateKey); } public static void receiverside(String messagetoreceiver[], PublicKey senderpubKey, PrivateKey senderprivateKey, PublicKey receiverpubKey, PrivateKey receiverprivateKey) throws Exception { //Receiver receives the message messagetoreceiver[] with messagetoreceiver[2] as secret key encrypted with receiver pub key //Receiver decrypts the messagetoreceiver[2] with his/her privatekey String receiverencodedsecretkey = decrypt(receiverpubKey, receiverprivateKey, messagetoreceiver[2], 1); //Key after decryption is in base64 encoded form byte[] decodedKey = Base64.getDecoder().decode(receiverencodedsecretkey); SecretKey originalKey = new SecretKeySpec(decodedKey, 0, decodedKey.length, "DES"); System.out.println("\nReceiver Side: Receiver SecretKey DES after Decryption with his/her Private Key=\n"+originalKey.toString()); //Decrypt the rest of the message in messagetoreceiver with SecretKey originalKey String receiverdecryptedmessage[] = new String[messagetoreceiver.length-1]; System.out.println("\nReceiver Side: Message After Decryption with SecretKey="); for (int i=0;i
    
     encryptwithprivatekey 1->encryptwithpublickey public static String encrypt(PublicKey publicKey, PrivateKey privateKey, String message, int ch) throws Exception { Cipher cipher = Cipher.getInstance("RSA"); if (ch == 0) { cipher.init(Cipher.ENCRYPT_MODE, privateKey); byte[] utf8 = cipher.doFinal(message.getBytes("UTF-8")); return new sun.misc.BASE64Encoder().encode(utf8); } else { cipher.init(Cipher.ENCRYPT_MODE, publicKey); byte[] utf8 = cipher.doFinal(message.getBytes("UTF-8")); return new sun.misc.BASE64Encoder().encode(utf8); } } //n: 0->decryptwithpublickey 1->decryptwithprivatekey public static String decrypt(PublicKey publicKey,PrivateKey privateKey, String st, int ch) throws Exception { Cipher cipher = Cipher.getInstance("RSA"); byte[] encrypted = new sun.misc.BASE64Decoder().decodeBuffer(st); if (ch == 0) { cipher.init(Cipher.DECRYPT_MODE, publicKey); byte[] utf8 = cipher.doFinal(encrypted); return new String(utf8, "UTF8"); } else { cipher.init(Cipher.DECRYPT_MODE, privateKey); byte[] utf8 = cipher.doFinal(encrypted); return new String(utf8, "UTF8"); } } }
    

Hemos utilizado el esquema de codificación base64 que es similar a radix64 que se utiliza en PGP.

Nota:

  1. Codificamos en base64 las cadenas después del cifrado y la compresión para obtener un formato de texto legible.
  2. Para el descifrado y la descompresión, enviamos las entradas decodificadas en base64 como las entradas reales a los algoritmos de descifrado y descompresión.
  3. La clave ha sido codificada y decodificada en base64 desde que utilicé Java para la simulación de PGP, que requiere una forma codificada en el lado del receptor para que pueda convertirse al tipo de datos SecretKey para el proceso de descifrado.

Siga, aplauda y comparta. Comente cualquier error, mejora o sugerencia. Incluso puedes seguirme en Twitter.