Aller au contenu principal
Retour au blog

Symfony 8.2 PGP/MIME : signer et chiffrer tes emails

Symfony 8.2 signe et chiffre tes emails en PGP/MIME via le Mailer, et empêche l’envoi en clair d’un message S/MIME. Config YAML, en-têtes X-Pgp, pièges.

Flavien Métivier · 11 min de lecture
Sommaire · 8 parties

PGP/MIME (RFC 3156) est un standard qui permet de signer et de chiffrer le corps d'un email avec des clés OpenPGP, sans autorité de certification. Symfony 8.2 l'ajoute au composant Mime et au Mailer, et durcit au passage le chiffrement S/MIME existant. Présenté le 29 septembre 2026 sur le blog officiel, contribution de Florent Morselli (PR #64540 pour PGP, #64541 pour S/MIME), il arrive avec Symfony 8.2, attendu en novembre 2026. Toutes les nouvelles classes sont expérimentales : l'API peut changer dans une version mineure.

Chiffrer un email depuis Symfony était possible depuis la 4.4, mais seulement en S/MIME, qui repose sur des certificats X.509 délivrés par une autorité. Beaucoup d'équipes techniques, de juristes et de journalistes utilisent plutôt des clés OpenPGP, et restaient hors de portée du Mailer. Pire, le comportement par défaut de S/MIME cachait un piège : si un seul destinataire n'avait pas de certificat, le message partait non chiffré pour tout le monde, sans erreur. Un chiffrement qui s'évapore en silence est pire qu'une absence de chiffrement, parce que tu crois être protégé.

PGP/MIME ou S/MIME : quelle différence pour tes emails Symfony ?

Les deux standards signent et chiffrent le corps d'un email. S/MIME s'appuie sur une infrastructure de certificats ; PGP/MIME sur des clés OpenPGP que chaque correspondant gère et échange lui-même. Le bon choix dépend de ce qu'utilisent tes interlocuteurs, pas d'une supériorité technique. Symfony 8.2 met les deux au même niveau dans le Mailer, que tu utilises déjà pour les templates hébergés et le transport Microsoft Graph.

Signer et chiffrer un email avec PgpSigner et PgpEncrypter

Le composant Mime reçoit deux classes, PgpSigner et PgpEncrypter, qui suivent la logique de S/MIME : le signer ajoute une signature détachée, l'encrypter chiffre avec les clés publiques des destinataires. Exemple du billet :

php
use Symfony\Component\Mime\Crypto\PgpEncrypter;
use Symfony\Component\Mime\Crypto\PgpSigner;

$signer = new PgpSigner('/path/to/secret.asc', '/path/to/public.asc', $passphrase);
$signedEmail = $signer->sign($email);

$encrypter = new PgpEncrypter();
$encryptedEmail = $encrypter->encrypt($signedEmail, [
    'alice@example.com' => '/path/to/alice.asc',
]);

Aucune extension PHP n'est requise : Symfony appelle le binaire gpg via le composant Process. Selon la PR #64540, chaque opération tourne dans un trousseau éphémère supprimé ensuite, la passphrase passe par un fichier et jamais en ligne de commande, et la signature est calculée sur les octets réellement transmis. On signe toujours avant de chiffrer : l'inverse laisserait la signature visible hors de l'enveloppe chiffrée.

Configurer PGP une fois pour tout le Mailer

Dans une application full-stack, tout se déclare sous framework.mailer :

yaml
# config/packages/mailer.yaml
framework:
    mailer:
        pgp_signer:
            secret_key: '%kernel.project_dir%/config/keys/private.asc'
            passphrase: '%env(PGP_PASSPHRASE)%'
        pgp_encrypter:
            keys:
                'alice@example.com': '%kernel.project_dir%/config/keys/alice.asc'
            # or use a service implementing PgpPublicKeyRepositoryInterface
            # repository: App\Mailer\PublicKeyRepository
            on_missing_key: 'fail' # fail | encrypt | skip

La protection se déclenche ensuite message par message, avec deux en-têtes :

php
$email->getHeaders()->addTextHeader('X-Pgp-Sign', 'true');
$email->getHeaders()->addTextHeader('X-Pgp-Encrypt', 'true');

Symfony signe et chiffre après le rendu du contenu : tes TemplatedEmail fonctionnent sans adaptation. Une map statique d'adresses vers des fichiers de clés convient pour quelques correspondants fixes ; dès que les destinataires viennent de ta base, passe par un service qui implémente PgpPublicKeyRepositoryInterface. Quant à la passphrase et à la clé privée, ce sont des secrets : variable d'environnement alimentée par ton gestionnaire de secrets, volume monté en lecture seule, jamais dans le dépôt. La même rigueur que pour le chiffrement des données avec KeyManagement, qui couvre les données au repos quand PGP couvre le transit par email.

Que se passe-t-il si un destinataire n’a pas de clé ?

L’option on_missing_key décide du comportement :

Votre dette technique s'accumule ?

Audit complet en 10 jours. Recommandations priorisées et actionnables.

Calculer mon audit →
  • fail lève une exception, et rien ne part. C’est la valeur à garder par défaut en production.
  • encrypt chiffre pour les destinataires qui ont une clé ; les autres reçoivent un message qu’ils ne pourront pas lire.
  • skip retire les destinataires sans clé de la liste.

Dans tous les cas, un message PGP n'est jamais envoyé non chiffré. Les destinataires en copie cachée sont par ailleurs masqués à l'intérieur du message chiffré. Deux nuances de terrain, tirées de la PR : l'option framework.mailer.envelope.recipients, appliquée après coup, écrase la liste produite par skip ; et l'en-tête X-Pgp-Encrypt peut choisir pour un message un mode plus souple que celui de la configuration. Aucun mode n'envoie en clair, mais cette souplesse mérite d'être encadrée en revue de code.

Ce que PGP ne protège pas

  • Les en-têtes restent visibles : seul le corps est chiffré, Subject, From et To restent lisibles. Un objet du type « Rapport d’incident chez le client X » trahit l’essentiel ; garde des objets neutres.
  • gpg doit être installé sur toutes les machines qui envoient : image applicative, worker Messenger si les emails partent en asynchrone, conteneur de tests.
  • La gestion des clés est le vrai coût : chiffrer avec une clé publique non vérifiée ne protège de rien. Symfony ne contrôle pas les empreintes à ta place.
  • API expérimentale : elle peut bouger en version mineure.

Je ne fais ici aucune affirmation réglementaire : si ce chiffrement doit répondre à une obligation précise dans ton secteur, la question relève de ton DPO ou de ton conseil.

S/MIME plus sûr : la fin de l’envoi en clair silencieux

Le même lot durcit S/MIME. L'option on_missing_certificate reprend les trois valeurs de PGP, et tu peux déclarer les certificats directement dans la configuration :

yaml
# config/packages/mailer.yaml
framework:
    mailer:
        smime_encrypter:
            certificates:
                'alice@example.com': '%kernel.project_dir%/config/certificates/alice.crt'
            on_missing_certificate: 'fail'

Pour la rétrocompatibilité, la valeur par défaut reste send_unencrypted, le comportement dangereux décrit plus haut. Elle est désormais dépréciée, et le billet annonce que Symfony 9.0 lèvera une exception dans ce cas. Un en-tête X-SMime-Encrypt permet de surcharger le comportement par message, par exemple X-SMime-Encrypt: skip, sans jamais pouvoir rétrograder vers l'envoi en clair. Enfin, les listeners s'exécutent désormais dans un ordre fixe : signer, chiffrer, puis DKIM. Avant, signature et chiffrement partageaient la même priorité, et l'ordre dépendait de l'enregistrement des services.

Checklist avant la sortie de 8.2

  • Si tu envoies déjà du S/MIME, repère les configurations sans on_missing_certificate : elles suivent la valeur dépréciée.
  • Pose fail explicitement et traite les exceptions (alerte, file de reprise) plutôt que d’envoyer en clair.
  • Si tu combines signature, chiffrement et DKIM, reteste la chaîne complète : l’ordre est maintenant déterministe.
  • Pour PGP, installe gpg dans toutes les images concernées et stocke la passphrase en secret.
  • Collecte les clés publiques de tes correspondants depuis une source vérifiée, et teste sur un environnement de recette avec de vraies clés.

Ma lecture

Deux choses, par ordre d'urgence. D'abord, si tu envoies du S/MIME, passe on_missing_certificate à fail dès l'arrivée de la 8.2 : ton application n'enverra plus jamais en clair par accident, et tu t'épargnes la rupture de la 9.0. Ensuite, adopte PGP/MIME sur des flux à forte valeur et à faible volume, comme des échanges avec un cabinet d'avocats ou un auditeur, avec fail par défaut et un repository de clés. N'en fais pas encore le socle de tes emails transactionnels : l'API est expérimentale, la dépendance à gpg pèse sur tes images, et les en-têtes restent en clair. Côté authentification, le login OpenID Connect natif complète ce registre sécurité de la version.

Le plus inquiétant dans cette histoire, c'est un email censé être chiffré qui part en clair sans qu'aucune erreur ne s'affiche. Si tu ne sais pas si ton application est dans ce cas, relire la configuration du Mailer, des secrets et des clés fait partie de ce qu'on vérifie dans un Bear Scan : on regarde ce que le code fait réellement, pas ce qu'on croit qu'il fait, et on te rend des recommandations priorisées en dix jours.

Cet article vous a été utile ? Partagez-le.

Portrait de Flavien Métivier, fondateur de The Bearded Bear : barbe longue, lunettes, sur fond sombre

Écrit par

Flavien Métivier

Expert PHP/Symfony indépendant depuis 2014, ancien CTO de Kartable et d'Extrabat. Plus de 80 projets menés, de l'audit à la migration.

Mon parcours

À lire ensuite

Votre dette technique s'accumule ?

Audit complet en 10 jours. Recommandations priorisées et actionnables.

Newsletter Bear Bytes

Veille Claude Code, Symfony et PHP, retours d'expérience terrain et conseils techniques, directement dans votre boîte mail.