Charger un fichier
  1. base64hub
  2. Apprendre Base64
  3. Base64 dans les e-mails
Guide

Base64 dans les e-mails : MIME, pièces jointes et en-têtes

Chaque pièce jointe d’un e-mail voyage sous forme de texte Base64, coupé à 76 caractères. Une fois que vous savez lire la structure MIME qui l’entoure, récupérer le fichier dans un message brut prend une minute.

Pourquoi l’e-mail a besoin de Base64

Le courrier Internet a été conçu pour du texte ASCII 7 bits en lignes de longueur limitée. La RFC 5322 plafonne toujours une ligne à 998 caractères, et SMTP ne garantit la livraison sans risque que des données 7 bits, sauf si le serveur destinataire annonce l’extension 8BITMIME. Même avec un transport 8 bits, un message ne doit contenir ni octets NUL ni retours chariot ou sauts de ligne isolés, ce que tout fichier binaire finit par contenir. Envoyer des octets bruts exige l’extension BINARYMIME, et un expéditeur ne peut pas compter sur elle tout au long du trajet de serveur en serveur.

MIME (RFC 2045) résout le problème avec l’en-tête Content-Transfer-Encoding. Il indique au destinataire comment le corps d’une partie a été transformé en texte sûr. Il existe cinq valeurs :

ValeurSignification
7bitLignes ASCII simples, sans encodage
8bitTexte 8 bits en lignes courtes, exige 8BITMIME
binaryOctets bruts, exige BINARYMIME
quoted-printableTexte presque lisible, octets non ASCII échappés en =XX
base64Base64 en lignes de 76 caractères au plus

Pour les pièces jointes, les logiciels de messagerie choisissent presque toujours base64. L’algorithme lui-même est expliqué dans le fonctionnement de Base64.

Base64 ou quoted-printable

Quoted-printable garde les caractères ASCII tels quels et écrit chaque autre octet sous la forme = suivi de deux chiffres hexadécimaux. Un é en UTF-8 (octets C3 A9) devient =C3=A9. Les lignes de plus de 76 caractères sont coupées par un saut de ligne doux, un = en fin de ligne.

C’est idéal pour un texte anglais avec un accent de temps en temps, et coûteux pour tout le reste, puisque chaque octet non ASCII coûte 3 caractères. Base64 coûte un 4/3 fixe, quel que soit le contenu. Nous avons encodé 20 répétitions d’une phrase dans trois langues avec quopri.encodestring et base64.encodebytes de Python (tous deux avec des fins de ligne LF) :

Échantillon (UTF-8)OctetsQuoted-printableBase64
Pangramme anglais9009261 216
Phrase française avec accents1 0601 8301 435
Pangramme russe1 6004 6022 165

Les clients de messagerie font généralement ce choix pour vous : quoted-printable ou 7bit pour les corps de texte essentiellement ASCII, Base64 pour les pièces jointes et pour le texte en écritures non latines. Si vous devez lire ou produire du quoted-printable à la main, l’outil quoted-printable convertit dans les deux sens.

Les pièces jointes dans un message brut

Un message avec pièce jointe a un corps multipart/mixed. Chaque partie a ses propres en-têtes, une ligne vide, puis son contenu. Voici un message minimal complet, généré avec le paquet email de Python (limite raccourcie pour la lisibilité) :

From: Ana <ana@example.com>
To: bo@example.org
Subject: =?utf-8?b?Q2Fmw6kgbWVudSDigJMgR3LDtsOfZQ==?=
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="b1"

--b1
Content-Type: text/plain; charset="utf-8"
Content-Transfer-Encoding: quoted-printable

Hi Bo,

The caf=C3=A9 menu is attached.

--b1
Content-Type: application/pdf
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="menu.pdf"

JVBERi0xLjcKJeLjz9MKMSAwIG9iago8PCAvVHlwZSAvQ2F0YWxvZyA+PgplbmRvYmoKdHJhaWxl
cgo8PCAvUm9vdCAxIDAgUiA+PgolJUVPRgo=

--b1--

Les éléments à repérer :

  • Content-Type donne le type de fichier, Content-Disposition le nom du fichier.
  • Content-Transfer-Encoding: base64 confirme que le corps est en Base64.
  • Le Base64 commence après la première ligne vide de la partie et se termine avant la ligne --boundary suivante.
  • Les lignes font exactement 76 caractères, sauf la dernière. Chaque ligne complète contient 57 octets du fichier.
  • JVBERi0 au début est le Base64 de %PDF- : il s’agit donc d’un PDF. Les autres signatures sont listées dans les préfixes Base64 courants.

Les images intégrées dans un e-mail HTML utilisent le même mécanisme dans une partie multipart/related. Chaque image reçoit un en-tête Content-ID, et le HTML y fait référence sous la forme <img src="cid:...">. Pendant le transport, ces lignes se terminent par CRLF, si bien que chaque tranche de 76 caractères de Base64 coûte 78 octets ; le calcul complet se trouve dans le surcoût de taille de Base64. Pour produire vous-même une sortie découpée, utilisez l’encodeur Base64 MIME.

En-têtes encodés : =?UTF-8?B?…?=

Les en-têtes comme Subject, les noms affichés dans From et certains noms de fichiers doivent être en ASCII. La RFC 2047 encode le reste du texte sous forme d’encoded-words :

=?charset?encoding?encoded-text?=

encoding vaut B pour Base64 ou Q pour une variante de quoted-printable dans laquelle _ représente une espace. Les deux lettres sont insensibles à la casse, ce qui explique que Python ait écrit =?utf-8?b? ci-dessus. Quelques exemples réels, tous vérifiés avec email.header.decode_header de Python :

EncodéDécodé
=?UTF-8?B?0J/RgNC40LLQtdGC?=Привет
=?utf-8?q?Caf=C3=A9_menu?=Café menu
=?ISO-8859-1?Q?Caf=E9_menu?=Café menu
=?utf-8?b?Q2Fmw6kgbWVudSDigJMgR3LDtsOfZQ==?=Café menu – Größe

Un encoded-word fait au plus 75 caractères : les objets longs sont donc répartis sur plusieurs, un par ligne d’en-tête repliée. La RFC 2047 précise que l’espace entre deux encoded-words adjacents est ignoré, et chaque mot doit contenir des caractères entiers : un caractère UTF-8 multi-octet n’est jamais coupé entre deux mots. Un objet russe de 44 caractères est sorti de Python ainsi :

Subject: =?utf-8?b?0KHRh9GR0YIg0LfQsCDQvtC60YLRj9Cx0YDRjCDQuCDQsNC60YIg0YE=?=
 =?utf-8?b?0LLQtdGA0LrQuCDQtNC70Y8g0LHRg9GF0LPQsNC70YLQtdGA0LjQuA==?=

Chaque mot est une chaîne Base64 complète avec son propre remplissage. Décodés séparément, le premier donne Счёт за октябрь и акт с et le second верки для бухгалтерии ; un décodeur les réunit pour retrouver l’objet d’origine.

Les noms de pièces jointes ont une seconde norme, la RFC 2231, qui ressemble à filename*=UTF-8''%D0%BE%D1%82%D1%87%D1%91%D1%82.pdf. De nombreux logiciels de messagerie mettent aussi des encoded-words RFC 2047 dans filename="..." : un analyseur doit donc gérer les deux.

Extraire une pièce jointe d’un fichier .eml

Dans le navigateur. Ouvrez le .eml dans le décodeur EML pour lire les corps du message et enregistrer les pièces jointes. Tout se passe localement dans la page, ce qui compte quand le message contient des factures ou des données personnelles.

Avec Python. La bibliothèque standard analyse MIME et décode aussi bien les corps Base64 que les en-têtes RFC 2047 :

import sys
from email import policy
from email.parser import BytesParser

with open(sys.argv[1], "rb") as f:
    msg = BytesParser(policy=policy.default).parse(f)

print("Subject:", msg["Subject"])        # encoded-words decoded
for part in msg.iter_attachments():
    name = part.get_filename()
    data = part.get_content()             # Base64 already decoded to bytes
    with open(name, "wb") as out:
        out.write(data)
    print("saved", name, len(data), "bytes")

Exécuté sur l’exemple de message ci-dessus, le script a affiché Subject: Café menu – Größe et saved menu.pdf 83 bytes. Dans du code réel, nettoyez name avant d’écrire le fichier, puisqu’il vient de l’expéditeur.

À la main. Copiez les lignes Base64 de la partie dans un fichier et décodez-les. Les fichiers .eml bruts utilisent des fins de ligne CRLF, et GNU base64 -d s’arrête avec invalid input au premier retour chariot. Supprimez-les d’abord, ou utilisez -i pour ignorer les caractères hors de l’alphabet :

tr -d '\r' < block.txt | base64 -d > menu.pdf

Dans nos tests, openssl base64 -d -in block.txt -out menu.pdf a accepté la version CRLF telle quelle. Si vous n’avez que le texte Base64, collez-le dans Base64 en fichier, qui détecte le type d’après les premiers octets, ou directement dans Base64 en PDF pour les PDF.

Ouvrir un fichier .eml

Affichez les en-têtes, les corps et les pièces jointes d'un e-mail brut, décodés localement dans votre navigateur.

Décodeur d'e-mails

FAQ

Comment ouvrir une pièce jointe Base64 à partir de la source d'un e-mail ?

Copiez les lignes Base64 situées entre la ligne vide de la partie et la limite (boundary) suivante, puis décodez-les comme un fichier. Le décodeur EML extrait pour vous les pièces jointes d’un fichier .eml.

Que signifie =?UTF-8?B? dans l'objet d'un e-mail ?

C’est le début d’un encoded-word RFC 2047 : le jeu de caractères est UTF-8 et B signifie que le texte entre le ? suivant et ?= est en Base64. Un Q à la place du B désigne une variante de quoted-printable.

Pourquoi les lignes Base64 des e-mails font-elles 76 caractères ?

La RFC 2045 limite les lignes Base64 de MIME à 76 caractères. 76 caractères contiennent exactement 57 octets, et cette limite maintient les lignes bien en dessous du maximum de 998 caractères autorisé par l’e-mail.

Quoted-printable ou Base64 : lequel convient le mieux au texte d'un e-mail ?

Quoted-printable pour un texte essentiellement ASCII, car il reste lisible et grossit à peine. Base64 pour les données binaires et pour le texte en écritures non latines, que quoted-printable peut rendre presque trois fois plus volumineux.

Continuer la lecture