L’encodage base64

L’encodage base64 est très répandu et utilisé dans des cas d’applications très différents. Très souvent, il est pris pour un codage cryptographique alors que ce n’est pas le cas. Le but de cet article est d’expliquer l’intérêt et le procédé utilisé pour effectuer cet encodage.

Quel est l’intérêt du codage base64 ?

Le codage base64 utilise 64 caractères ASCII pour encoder des données binaires. Ainsi le 1er intérêt de cet encodage est de pouvoir encoder n’importe quelle donnée binaire sous la forme d’une chaîne de caractères utilisant des caractères interprétables par tous les systèmes (car étant en ASCII). Volontairement les 64 caractères ASCII utilisés sont restreints à des lettres, des chiffres et aux caractères spéciaux '+', '/', '='. Le but de cette restriction est d’éviter le plus possible d’utiliser des caractères qui pourraient mener à des interprétations différentes d’un système à l’autre.

Ainsi, on retrouve le codage base64 dans des cas où on souhaite transmettre ou stocker des données binaires en utilisant des systèmes n’autorisant que des caractères textuels:

  • Historiquement le protocole d’échange d’emails SMTP (i.e. Simple Mail Transfer Protocol) utilisait un encodage MIME Base64 faisant partie du protocole PEM (i.e. Privacy-enhanced Electronic Mail) décrit dans la publication RFC 989 en 1987. A l’origine ce protocole n’autorise que 64 caractères plus le caractère '='.
    Ainsi pour transmettre des pièces jointes non textuelles sous la forme binaire (comme des images), on utilisait l’encodage base64 pour transformer les données non textuelles en chaîne de caractères qu’on rajoutait au corps du mail.
    Cette restriction de SMTP n’est plus valable maintenant, des extensions ont été rajoutées (cf. RFC 6152) pour supporter 8BITMIME autorisant, entre autres, des données binaires directement.
  • L’encodage base64 est utilisé pour transmettre des données binaires ou non directement lisibles dans une URL lors d’une requête HTTP GET.
  • Des données binaires peuvent être stockées dans un fichier XML en utilisant l’encodage base64.
  • Base64 est souvent utilisé pour stocker dans des fichiers texte, des mots de passe dont on ne désire pas qu’ils soient lisibles directement. Bien que ce ne soit pas une méthode d’encodage cryptographique, elle permet de facilement rendre un mot de passe plus difficilement lisible.
Précisions concernant ASCII

ASCII (pour American Standard Code for Information Interchange) est une norme de codage de caractères datant des années 60. Elle comporte 128 caractères numérotés de 0 à 127. Ces caractères sont limités aux lettres sans accents majuscules et minuscules, aux chiffres et à des caractères spéciaux usuels, par exemple: rapidtables.com/code/text/ascii-table.html.

Cette norme est largement utilisée car tous les systèmes sont capables d’interpréter du texte utilisant cette norme. Toutefois la plus grosse restriction est que cette norme ne prend en compte que les caractères de la langue anglaise, il n’est pas possible de l’utiliser pour d’autres langues utilisant des caractères accentués, d’autres caractères spéciaux et plus largement pour des langues n’utilisant pas d’alphabet classique. Généralement tous les systèmes sont capables de lire des caractères ASCII toutefois le plus souvent, les encodages sont faits en utilisant au moins UTF-16, UTF-8 et plus largement Unicode (cf. Unicode en 5 min). Décoder des caractères Unicode permet de décoder des caractères ASCII, les numérotations des caractères entre ASCII et les encodages Unicode étant presque les mêmes:

  • En UTF-8: la longueur de l’encodage d’un caractère est variable. Elle commence à 8 bits (comme l’ASCII) pour aller jusqu’à 32 bits. La compatibilité avec l’ASCII est donc complète: un système ne décodant que de l’ASCII pourra décoder de l’UTF-8 (si tous les caractères font partie de l’ASCII).
  • En UTF-16: la longueur de l’encodage d’un caractère est de 16 bits. La compatibilité n’est donc pas totale avec l’ASCII, décoder des caractères UTF-16 permettra de reconnaître les caractères ASCII toutefois il y a un espace correspondant à un mot de 8 bits entre chaque caractère.

Par exemple, pour encoder la chaîne de caractères "AZERTY" en utilisant la norme ASCII:

Caractère 'A' 'Z' 'E' 'R' 'T' 'Y'
Hexadécimal 41 5A 45 52 54 59
Binaire 01000001 01011010 01000101 01010010 01010100 01011001

ASCII est une norme numérotant 128 caractères. Ainsi pour numéroter de 0 à 127, il faut 7 bits (27 = 128) toutefois on considère 8 bits car ASCII a été rapidement étendu pour inclure d’autres caractères liés à des spécificités régionales (cf. Code Pages).

Principe de l’encodage base64

Le but de l’encodage base64 est de transformer des données binaires en caractères textuels. 65 caractères sont utilisés:

  • 62 caractères correspondant à l’alphabet en majuscules, minuscules et les chiffres.
  • Les caractères '+' et '/'
  • Le caractère '=' qui sert à effectuer du padding. Ce caractère ne fait pas partie de l’encodage à proprement parlé.

Pour numéroter 64 caractères, il suffit de 6 bits (car 26 = 64). Les 6 bits utilisés sont appelés sextet. Ainsi pour encoder, il suffit de séparer la série de bits en lots de 6 bits et de faire la correspondance entre chaque sextet avec un caractère textuel:

Index décimal Sextet Caractère Index décimal Sextet Caractère Index décimal Sextet Caractère Index décimal Sextet Caractère
0 000000 'A' 1 000001 'B' 2 000010 'C' 3 000011 'D'
4 000100 'E 5 000101 'F' 6 000110 'G' 7 000111 'H'
8 001000 'I' 9 001001 'J' 10 001010 'K' 11 001011 'L'
12 001100 'M' 13 001101 'N' 14 001110 'O' 15 001111 'P'
16 010000 'Q' 17 010001 'R' 18 010010 'S' 19 010011 'T'
20 010100 'U' 21 010101 'V' 22 010110 'W' 23 010111 'X'
24 011000 'Y' 25 011001 'Z' 26 011010 'a' 27 011011 'b'
28 011100 'c' 29 011101 'd' 30 011110 'e' 31 011111 'f'
32 100000 'g' 33 100001 'h' 34 100010 'i' 35 100011 'j'
36 100100 'k' 37 100101 'l' 38 100110 'm' 39 100111 'n'
40 101000 'o' 41 101001 'p' 42 101010 'q' 43 101011 'r'
44 101100 's' 45 101101 't' 46 101110 'u' 47 101111 'v'
48 110000 'w' 49 110001 'x' 50 110010 'y' 51 110011 'z'
52 110100 '0' 53 110101 '1' 54 110110 '2' 55 110111 '3'
56 111000 '4' 57 111001 '5' 58 111010 '6' 59 111011 '7'
60 111100 '8' 61 111101 '9' 62 111110 '+' 63 111111 '/'
Les encodages ASCII et base64 ne sont pas similaires

Les caractères utilisés font partie de la norme ASCII mais la correspondance de code utilisé entre base64 et les caractères ASCII n’est pas la même:

  • Les caractères de base64 sont encodés sur 6 bits car 26 = 64.
  • Les caractères d’ASCII sont encodés sur 8 bits (dans le cas de 256 caractères) car 28 = 256.

Par exemple, 'Q' est:

  • ASCII: 01010001 (81 en décimal)
  • Base64: 010000 (17 en décimal)

Si on veut encoder le mot "PYTHON" avec le codage base64, il faut:

  1. Transformer au préalable des caractères en binaire avec les équivalents ASCII, chaque caractère correspond à 8 bits.
  2. Convertir les lots de 8 bits en lots de 6 bits pour former des sextets.
  3. Trouver la correspondance entre chaque sextet et un caractère suivant le codage base64.

Ainsi:

1. Caractère 'P' 'Y' 'T' 'H' 'O' 'N'
Equivalent décimal ASCII 80 89 84 72 79 78
Caractère ⇒ Binaire 01010000 01011001

01010100

01001000

01001111

01001110
2. Lots 8 bits ⇒ Sextet 010100 000101 100101 010100 010010 000100

111101

001110
3. Sextet ⇒ Index décimal 20 5 37 20 18 4 61 14
Index ⇒ Caractère base64 'U' 'F' 'l' 'U' 'S' 'E' '9' 'O'

Le résultat est une chaîne de caractères "UFlUSE9O".

Dans cet exemple, étant donné qu'on a encodé une chaîne de caractères, il était nécessaire d'effectuer une transformation préalable des caractères ASCII en binaire. L'encodage base64 à proprement parlé transforme les données binaires et caractères textuels.

Padding

L'exemple de l'encodage de la chaîne "PYTHON" est parfait puisque le nombre de bits correspondant est 48 qui est multiple de 8 et 6 (48 = 6 * 8 + 0). Dans un cadre plus général, le nombre de bits correspondant à une chaîne à encoder n'est pas forcément multiple de 6 et 8.

Exemple de padding avec 2 bits

Par exemple, si on considère la chaine "PYTHONES", en effectuant l'encodage:

Caractère 'P' 'Y' 'T' 'H' 'O' 'N' 'E' 'S'
Equivalent décimal ASCII 80 89 84 72 79 78 69 83
Caractère ⇒ Binaire 01010000

01011001

01010100

01001000

01001111

01001110

01000101

01010011
Lots 8 bits ⇒ Sextet 010100 000101 100101 010100 010010 000100

111101 001110 010001 010101 0011??
Sextet ⇒ Index décimal 20 5 37 20 18 4 61 14 17 21 ???
Index ⇒ Caractère base64 'U' 'F' 'l' 'U' 'S' 'E' '9' 'O' 'R' 'V' ???

La chaîne "PYTHONES" correspond à 64 bits qui n'est pas multiple de 6 (64 = 6 * 10 + 4). On ne peut pas former le dernier sextet car il manque 2 bits. On complète alors arbitrairement avec des 0 pour obtenir un sextet complet:

Lots 8 bits ⇒ Sextet 010100 000101 100101 010100 010010 000100

111101 001110 010001 010101 001100
Sextet ⇒ Index décimal 20 5 37 20 18 4 61 14 17 21 12
Index ⇒ Caractère base64 'U' 'F' 'l' 'U' 'S' 'E' '9' 'O' 'R' 'V' 'M'

Le sextet complet permet de rajouter un caractère qui dans notre cas est le 'M'.

Ajout du caractère '='

Lors du décodage, il suffirait dans la formule suivante d'identifier j pour savoir si des 0 de padding ont été rajoutés:

NbreBits = 8 * i + j

Dans notre cas NbreBits = 66 = 8 * 8 + 2 donc il y a 2 bits de padding.
Même s'il est possible d'effectuer cette déduction, il a été décidé de rajouter le caractère '=' pour indiquer qu'un padding a été effectué:

  • On rajoute une fois '=' si on a complété par 2 bits à 0 pour obtenir un sextet ou
  • On rajouter 2 fois '==' si on a complété par 4 bits à 0.

Pour cet exemple, la chaîne correctement encodée est donc:

"UFlUSE9ORVM="

Exemple de padding avec 4 bits

Si on prend l'exemple de la chaîne "PYTHONE", en effectuant l'encodage, on obtient:

Caractère 'P' 'Y' 'T' 'H' 'O' 'N' 'E'
Equivalent décimal ASCII 80 89 84 72 79 78 69
Caractère ⇒ Binaire 01010000 01011001 01010100 01001000 01001111 01001110 01000101
Lots 8 bits ⇒ Sextet 010100 000101 100101 010100 010010 000100 111101 001110 010001 01????
Sextet ⇒ Index décimal 20 5 37 20 18 4 61 14 17 ???
Index ⇒ Caractère base64 'U' 'F' 'l' 'U' 'S' 'E' '9' 'O' 'R' ???

La chaîne "PYTHONE" correspond à 56 bits non multiple de 6 (56 = 6 * 9 + 2). Comme l'exemple précédent, il manque 4 bits pour avoir un sextet complet. En complétant avec des bits à 0, on obtient:

Lots 8 bits ⇒ Sextet 010100 000101 100101 010100 010010 000100 111101 001110 010001 010000
Sextet ⇒ Index décimal 20 5 37 20 18 4 61 14 17 16
Index ⇒ Caractère base64 'U' 'F' 'l' 'U' 'S' 'E' '9' 'O' 'R' 'Q'

Le sextet complet permet de rajouter le caractère 'Q' qui dans notre cas est le 'M'. Comme on a rajouté 4 bits à 0, on complète la chaine en rajoutant les caractères '=='. La chaîne correctement encodée est:

'UFlUSE9ORQ=='

Comment encoder/decoder en base64 ?

On peut utiliser les sites suivants (toutefois attention à ne pas utiliser dans le cas de données sensibles):

Powershell

Voici des scripts pour encoder et décoder en base64:

  • Pour encoder en base64:
    $sourceText = $Args[0]
    Write-Host "Encoding: $sourceText"
    # ATTENTION encodage UTF-16LE
    $bytes = [System.Text.Encoding]::Unicode.GetBytes($sourceText)
    $encodedText =[Convert]::ToBase64String($bytes)
    Write-Host $encodedText
    
  • Pour décoder:
    $encodedText = $Args[0]
    Write-Host "Encoding: $encodedText"
    # ATTENTION encodage UTF-16LE
    $decodedText = [System.Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String($encodedText))
    Write-Host $decodedText
    
Attention au choix de l'encodage entre ASCII, UTF-8 et UTF-16

Les scripts précédents sont exécutables sur Windows où les chaines correspondent à un encodage UTF-16 "Little Endian" (pour plus de détails voir Unicode en 5 min). Sur internet et en particulier avec les sites base64encode.org et base64decode.org lorsqu'on écrit, par défaut c'est de l'UTF-8. Enfin dans les exemples présentés précédemment on a effectué un encodage en ASCII.

Donc il faut bien avoir en tête l'encodage utilisé pour la chaîne d'origine, ainsi:

  • Sur les sites base64encode.org et base64decode.org, pour avoir des résultats similaires aux scripts powershell, il faut sélectionner l'encodage UTF-16LE (pour "Little Endian").
  • Avec les scripts pour avoir une équivalence avec les exemples présentés précédemment, il faut effectuer les conversions à partir de l'ASCII en utilisant:
    • Encodage (de caractères ASCII):
      $bytes = [System.Text.Encoding]::ASCII.GetBytes($sourceText)
      
    • Décodage (de caractères ASCII):
      $decodedText = [System.Text.Encoding]::ASCII.GetString([System.Convert]::FromBase64String($encodedText))
      
  • Si les chaînes sont en UTF-8:
    • Encodage (de caractères UTF-8):
      $bytes = [System.Text.Encoding]::UTF8.GetBytes($sourceText)
      
    • Décodage (de caractères UTF-8):
      $decodedText = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($encodedText))
      
  • Enfin, UTF-8 est compatible avec l'ASCII c'est-à-dire des caractères ASCII seront encodés de la même façon en ASCII et en UTF-8 mais l'inverse n'est pas forcément vrai puisque UTF-8 comprend beaucoup plus de caractères que l'ASCII. L'encodage en UTF-16LE n'est pas complêtement compatible avec l'ASCII.

A la ligne de commande

Sur Linux et MacOS (sur ces systèmes l'encodage utilisé est UTF-8):

  • Encodage:
    echo -n '<chaîne à encoder>' | base64
    
  • Décodage:
    echo -n '<chaîne à decoder>' | base64 --decode
    

Avec OpenSSL

Avec la bibliothèque cryptographique OpenSSL, voici les commandes pour encoder et décoder en base64:

  • Pour encoder directement du texte à la ligne de commandes:
    echo -n '<chaîne à encoder>' | openssl base64
    
  • Pour décoder directement du texte à la ligne de commandes:
    echo -n '<chaîne à decoder>' | openssl base64 -d 
    
  • Pour encoder un fichier:
    openssl base64 -in <chemin du fichier à encoder> -out <chemin du fichier en sortie>
    
  • Pour décoder un fichier:
    openssl base64 -d -in <chemin du fichier à décoder> -out <chemin du fichier en sortie>
    
Références

Leave a Reply