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 :
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 :
# 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 | skipLa protection se déclenche ensuite message par message, avec deux en-têtes :
$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.
faillève une exception, et rien ne part. C’est la valeur à garder par défaut en production.encryptchiffre pour les destinataires qui ont une clé ; les autres reçoivent un message qu’ils ne pourront pas lire.skipretire 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,FrometTorestent lisibles. Un objet du type « Rapport d’incident chez le client X » trahit l’essentiel ; garde des objets neutres. gpgdoit ê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 :
# 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
failexplicitement 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
gpgdans 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.

É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