Cómo firmar correos electrónicos con GPG y JavaMail

Escrito por picodotdev el .
blog-stack java planeta-codigo planeta-linux programacion seguridad
Comentarios

El correo electrónico es un medio muy utilizado para realizar ataques de phising, algunos son muy burdos pero seguramente algunos usuarios sin muchos conocimientos caen víctimas de ellos y aún los usuarios con conocimientos también pueden serlo si están bien realizados y muestran un correo electrónico exactamente igual que el que intentan suplantar. Los usuarios son las víctimas pero si los sitios web que envían los correos electrónicos legítimos los firmasen digitalmente sería una garantía más para proteger a sus usuarios, pudiendo detectar de otra forma el spam y phising. En este artículo muestro a modo de ejemplo como firmar un correo electrónico con GPG y JavaMail e igualmente podría utilizarse para cifrarlo, aunque usar DKIM sería lo más apropiado.

Java
GnuPG

Los sitios de comercio electrónico y muchas páginas web utilizan el protocolo seguro HTTPS para cifrar los datos intercambiados entre cliente y servidor impidiendo a una tercera persona conocer qué información se está transmitiendo, además impide que puedan ser alterados sin su conocimiento. Es habitual usar HTTPS y certificados en las páginas de compra en las que hay que introducir datos personales junto con la tarjeta de crédito también en las cuentas de usuario como forma de proporcionar seguridad y proteger la información personal. Generando y usando certificados TLS/SSL en el servidor el sitio y el usuario evitan caer en un ataque de phising en la que una tercera persona con intenciones maliciosas intenta suplantar la identidad del sitio web.

Pero los ataques de phising también son realizados a través del correo electrónico, mensajes en los que se incluyen enlaces hacia páginas que suplantan a un sitio. Algunos usuarios quizá no se den cuenta de la suplantación al hacer clic en los enlaces maliciosos. Los motores de búsqueda mantendrán a los usuarios a salvo de enlaces maliciosos en las páginas de resultados que les lleven a páginas de phising, pero no del correo electrónico que si no es detectado como spam llegará a la bandeja de entrada de los usuarios. El correo electrónico es una vía para llevar a los usuarios hacia esas páginas de phising. Para evitar este posible peligro no todos los sitios web y de comercio electrónico son los que firman sus mensajes como forma de verificar la autenticidad de los mismos así como evitar que pueda ser modificados sin conocimiento.

Con GPG y JavaMail podemos firmar los mensajes electrónicos que enviemos desde una aplicación Java. La firma de un correo electrónico consiste baśicamente en firmar el contenido del mensaje y adjuntar la firma como un documento adjunto con un mimetype de application/pgp-signature. Lo primero que deberemos hacer es generar un par de claves de cifrado asimétrico usando GPG. Si los mensajes los vamos a enviar usando un cuenta de gmail y tenemos activada la verificación en dos pasos debemos genera una contraseña de aplicación desde Mi cuenta de Google.

Además de cómo firmar un correo electrónico el siguiente ejemplo muestra cómo ejecutar un proceso del sistema en Java que nos proporciona acceso a todas las utilidades GNU, scripts de Python u otros comandos que tenga instalados, también muestra cómo enviar un correo electrónico en un programa Java que ya comenté pero ahora con un ejemplo ejecutable y enviando un archivo adjunto.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
package io.github.picodotdev.blogbitix.javamailgpg;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import javax.mail.Authenticator;
import javax.mail.Message;
import javax.mail.Multipart;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;

public class Main {

    public static void main(String[] args) throws Exception {
        String gmailPassword = args[0];
        
        Authenticator authenticator = new Authenticator() {
            protected PasswordAuthentication getPasswordAuthentication() {
                return new PasswordAuthentication("pico.dev@gmail.com", gmailPassword);
            }
        };

        Properties properties = new Properties();
        properties.put("mail.smtp.auth", "true");
        properties.put("mail.smtp.starttls.enable", "true");
        properties.put("mail.smtp.host", "smtp.googlemail.com");
        properties.put("mail.smtp.port", "587");

        // Contenido del mensaje
        String content = "Hola mundo!\n";

        // Establecer las direcciones a las que será enviado el mensaje
        MimeBodyPart contentPart = new MimeBodyPart();
        contentPart.setText(content, "UTF-8");

        // Firmar el contenido con GPG
        String signature = execute(new String[] { "gpg", "--armor", "--detach-sig", "-u", "pico.dev@gmail.com" }, content);

        MimeBodyPart signaturePart = new MimeBodyPart();
        signaturePart.setContent(signature, "application/pgp-signature");
        signaturePart.setHeader("Content-Type", "application/pgp-signature; " + "name=signature.asc");

        // Agrupar las partes
        Multipart mp = new MimeMultipart();
        mp.addBodyPart(contentPart);
        mp.addBodyPart(signaturePart);

        // Obtener la sesión para enviar correos electrónicos
        Session session = Session.getDefaultInstance(properties, authenticator);

        // Crear el mensaje a enviar
        MimeMessage message = new MimeMessage(session);
        message.setSubject("Hola mundo!", "UTF-8");
        message.setFrom(new InternetAddress("pico.dev@gmail.com"));
        message.addRecipient(Message.RecipientType.TO, new InternetAddress("pico.dev@gmail.com"));
        message.addRecipient(Message.RecipientType.BCC, new InternetAddress("pico.dev@gmail.com"));
        message.setContent(mp);

        // Enviar el correo electrónico
        Transport.send(message);
    }

    private static String execute(String[] command, String input) throws Exception {
        Process process = Runtime.getRuntime().exec(command);
        BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(process.getOutputStream(), "UTF-8"));
        writer.write(input);
        writer.close();

        process.waitFor(10, TimeUnit.SECONDS);
        int ev = process.exitValue();

        InputStream is = process.getInputStream();
        InputStreamReader isr = new InputStreamReader(is, "UTF-8");
        BufferedReader br = new BufferedReader(isr);
        String success = br.lines().collect(Collectors.joining("\n"));
        br.close();

        InputStream eis = process.getErrorStream();
        InputStreamReader eisr = new InputStreamReader(eis);
        BufferedReader ebr = new BufferedReader(eisr);
        String error = ebr.lines().collect(Collectors.joining("\n"));
        ebr.close();

        if (ev != 0) {
            throw new Exception(error);
        }

        return success;
    }
}

En este ejemplo solo se firma el contenido del mensaje quedando fuera de la firma el asunto, fecha, otros adjuntos y destinatarios del mensaje pero podría utilizarse lo mismo para firmar estos otros datos. Enviado el correo electrónico podemos verificar la firma con el siguiente comando de GPG.

1
2
$ gpg --verify signature.asc email.txt
1
2
Hola mundo!
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v2

iQIcBAABCAAGBQJW7RzkAAoJEHL+XiDgEsmJAoMQAJ4l9R8HcAw1lkg7CDWlJJrd
8bJeaAh8VgD1owz8odfF4vbv3Zc2wv8Qlk/YqQ3T+OMnMq2RHk4THfm0TqRH7Y+Y
1Q0JPd+lckLrCQfNGLjQuDqcqhKdaYybwPSjzF4O81fD1xu4Z5blzyZ8nfMEFcdB
ciJHb4NHpkp0IklsQnhDrFHYK2dbtF9P2RmGx+btdouqr21tuxVRvlda+wDVFUMG
N7NH8lKGNmWz2eABED0J6B7jmZMpIlsOzD/o0Mp7aKGL7bh1B7Mq/S1Fh00buRgR
GpXdpA9f+BHXBZlGKR6cm1QHykfARRWt1Tj2R0GBYBgb853Dv4v/dhcyspCvEf+O
NSeAUDu4UY5ExZgig27EgnAntHklqAAQpZRGUhyR4JdBToDkxJqMNNGUsMO0HOFj
7pmyJaEGbPWh5CpXWzsItdOTGprzbD4Tsp9YthsRh185iwtYK3D2pTxIFI75tggK
A+XzRvwmwqajUWYZZtHjZZj7ZDb4044OmSY+rCMrZfwtrxLVL7k3/q/NUukFiNID
ZO46vf7nLfYKCs1niXVuHdMRNeDf4ucOlZLFrmtV/rQJVyxsp2kZevJln37MUOjW
uMMot1wggYP/D+Q40vLxtXojMR5fUr3XMlU5SqqHiX9rKREk+65f14IhyDj0X8X9
hjejzRrf8xAHq545a8wf
=yNCQ
-----END PGP SIGNATURE-----
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
                                                                                                                                                                                                                                                               
Return-Path: <pico.dev@gmail.com>
Received: from archlinux (193.62-99-44.dynamic.clientes.euskaltel.es. [62.99.44.193])
        by smtp.googlemail.com with ESMTPSA id wr2sm15734914wjc.49.2016.03.19.02.33.25
        for <pico.dev@gmail.com>
        (version=TLS1 cipher=ECDHE-RSA-AES128-SHA bits=128/128);
        Sat, 19 Mar 2016 02:33:26 -0700 (PDT)
Date: Sat, 19 Mar 2016 02:33:26 -0700 (PDT)
From: pico.dev@gmail.com
To: pico.dev@gmail.com
Message-ID: <693632176.1.1458380004611.JavaMail.picodotdev@archlinux>
Subject: Hola mundo!
MIME-Version: 1.0
Content-Type: multipart/mixed; 
    boundary="----=_Part_0_824318946.1458380004550"

------=_Part_0_824318946.1458380004550
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 7bit

Hola mundo!

------=_Part_0_824318946.1458380004550
Content-Type: application/pgp-signature; name=signature.asc
Content-Transfer-Encoding: 7bit

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v2

iQIcBAABCAAGBQJW7RzkAAoJEHL+XiDgEsmJAoMQAJ4l9R8HcAw1lkg7CDWlJJrd
8bJeaAh8VgD1owz8odfF4vbv3Zc2wv8Qlk/YqQ3T+OMnMq2RHk4THfm0TqRH7Y+Y
1Q0JPd+lckLrCQfNGLjQuDqcqhKdaYybwPSjzF4O81fD1xu4Z5blzyZ8nfMEFcdB
ciJHb4NHpkp0IklsQnhDrFHYK2dbtF9P2RmGx+btdouqr21tuxVRvlda+wDVFUMG
N7NH8lKGNmWz2eABED0J6B7jmZMpIlsOzD/o0Mp7aKGL7bh1B7Mq/S1Fh00buRgR
GpXdpA9f+BHXBZlGKR6cm1QHykfARRWt1Tj2R0GBYBgb853Dv4v/dhcyspCvEf+O
NSeAUDu4UY5ExZgig27EgnAntHklqAAQpZRGUhyR4JdBToDkxJqMNNGUsMO0HOFj
7pmyJaEGbPWh5CpXWzsItdOTGprzbD4Tsp9YthsRh185iwtYK3D2pTxIFI75tggK
A+XzRvwmwqajUWYZZtHjZZj7ZDb4044OmSY+rCMrZfwtrxLVL7k3/q/NUukFiNID
ZO46vf7nLfYKCs1niXVuHdMRNeDf4ucOlZLFrmtV/rQJVyxsp2kZevJln37MUOjW
uMMot1wggYP/D+Q40vLxtXojMR5fUr3XMlU5SqqHiX9rKREk+65f14IhyDj0X8X9
hjejzRrf8xAHq545a8wf
=yNCQ
-----END PGP SIGNATURE-----
------=_Part_0_824318946.1458380004550--
Verificación de la firma GPG del correo electrónico

En el anillo de claves de GPG la clave que usemos para firmar no ha de tener passphrase de lo contrario cuando se ejecute el comando GPG la solicitará en una ventana emergente. Aunque con las opciones –passphrase y –batch no debería solicitarla no he conseguido evitarlo.

El código fuente completo del ejemplo puedes descargarlo del repositorio de ejemplos de Blog Bitix alojado en GitHub y probarlo en tu equipo ejecutando el comando ./gradlew --daemon run -Pargs="[contraseña de aplicación de cuenta gmail]".

Las suplantaciones mediante correo electrónico son y seguirán siendo habituales si no son detectadas como spam. Después de escribir este artículo usar DKIM parece ser la forma adecuada de firmar y cifrar los correos electrónicos y viendo el mensaje original de los que envían Google y Amazon es lo que utilizan ellos que algo sabrán de esto. Usar DKIM en los correos electrónicos será tema para otro posible artículo, la nube de Amazon ofrece soporte para DKIM.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
                                                                                                                                                                                                                                                               
Delivered-To: pico.dev@gmail.com
Received: by 10.202.213.87 with SMTP id m84csp657124oig;
        Sat, 5 Mar 2016 11:33:21 -0800 (PST)
X-Received: by 10.50.43.228 with SMTP id z4mr5087403igl.8.1457206401882;
        Sat, 05 Mar 2016 11:33:21 -0800 (PST)
Return-Path: <3gTTbVggTCBQ78-By95Iuww8E7DC.08805y.w8692w8.xyF06u25.w86@gaia.bounces.google.com>
Received: from mail-io0-x247.google.com (mail-io0-x247.google.com. [2607:f8b0:4001:c06::247])
        by mx.google.com with ESMTPS id qc5si4273351igb.48.2016.03.05.11.33.21
        for <pico.dev@gmail.com>
        (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128);
        Sat, 05 Mar 2016 11:33:21 -0800 (PST)
Received-SPF: pass (google.com: domain of 3gTTbVggTCBQ78-By95Iuww8E7DC.08805y.w8692w8.xyF06u25.w86@gaia.bounces.google.com designates 2607:f8b0:4001:c06::247 as permitted sender) client-ip=2607:f8b0:4001:c06::247;
Authentication-Results: mx.google.com;
       spf=pass (google.com: domain of 3gTTbVggTCBQ78-By95Iuww8E7DC.08805y.w8692w8.xyF06u25.w86@gaia.bounces.google.com designates 2607:f8b0:4001:c06::247 as permitted sender) smtp.mailfrom=3gTTbVggTCBQ78-By95Iuww8E7DC.08805y.w8692w8.xyF06u25.w86@gaia.bounces.google.com;
       dkim=pass header.i=@accounts.google.com;
       dmarc=pass (p=REJECT dis=NONE) header.from=accounts.google.com
Received: by mail-io0-x247.google.com with SMTP id l127so142107109iof.1
        for <pico.dev@gmail.com>; Sat, 05 Mar 2016 11:33:21 -0800 (PST)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
        d=accounts.google.com; s=20120806;
        h=mime-version:feedback-id:date:message-id:subject:from:to;
        bh=NITgFdhPAzqOWo2iqDP6r/WemT69aCMvQgch8i8Dg+Q=;
        b=UepeQqjXMHGmkDTuXUQkgkxNQQqaxS4XJMc8UFiCGY/qIcekT35TfY1jANbOwGhXjw
         ywTuOV6/SVuerbMEatWbArCoz0/+F5hyRcMc8sZ8R1v1KmqGEJVCEHGyAT3DToF2R1UX
         i4/rkYEExoiWzVZo3uIyIx7qSkd/Xbgk13CYACsI5hfqRS5RJqO2tHxi4AlRIYG0XsyX
         NO+nXadBo/ObEU/xDWu0Qit03pci2mVLHq4oJcJedn+SFJdtDhNWc5m/r9VxrUhOh2Vr
         RCTSORdH/4TgkaVCdRj5eZdHvcw6Bt/pocM1Dv8hHLOiqcLCvQgrjfqeYavlqbsYd29Y
         HOHA==
X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
        d=1e100.net; s=20130820;
        h=x-gm-message-state:mime-version:feedback-id:date:message-id:subject
         :from:to;
        bh=NITgFdhPAzqOWo2iqDP6r/WemT69aCMvQgch8i8Dg+Q=;
        b=lyJe3AjvLk/0mtKQeEcahpZ2kY6sUB4AM2uhbUN5e5K8J4Q8vD5Yzqj1xU7711Bl2T
         UoNEZL5ZXCaavY1lwievbYrcy7UUbP0kHUCFSrpVUr/r3u08MhNZocJ0O/M8VF0d+JXv
         2PDtjEEecriMmxbPZQzg8iPaN13TtRscvXnSO+/fMSThC5850X0aShKTTypQwzXeDniQ
         rpdOcnGcBULBsxibI1NcwKzVKtcXzfC+AZm60ieM9GsObhxqafHVByPbrrd5ZnThh6xB
         V7jOMb8+20MK8qcztNyICTy9V7lcO+//E6VTR/n6z7+Hj/1aCxNFvRf2aOLIS6UQ2M/B
         Io4w==
X-Gm-Message-State: AD7BkJLU4wn+DRfKk7mHW8ysscqGw5YVzpB6ZvgaKBe88k5et3vUXxJNHIQUAx6+T4u+dVDaFdNFfiTnJQjIL8hU
MIME-Version: 1.0
X-Received: by 10.107.6.149 with SMTP id f21mr6132703ioi.25.1457206401678;
 Sat, 05 Mar 2016 11:33:21 -0800 (PST)
X-Notifications: XEAAAAKY__YKZOH9BRZfJxfsNZpg
X-Account-Notification-Type: STRONGAUTH_ENROLL_WITH_ASP_NO_BACKUP
Feedback-ID: STRONGAUTH_ENROLL_WITH_ASP_NO_BACKUP:account-notifier
Date: Sat, 5 Mar 2016 19:33:17 +0000 (UTC)
Message-ID: <f7d0Qd6Min_SAvrKnlIuEw@notifications.google.com>
Subject: =?ISO-8859-1?Q?Se_ha_activado_la_verificaci=F3n_en_dos_pasos?=
From: Google <no-reply@accounts.google.com>
To: pico.dev@gmail.com
...