Base64

Base64 uses a character set of 64 unique characters, which makes it compact, URL-safe and printable, at the cost of being harder for a human to read than hex or decimal.

There are some intricacies with the format, however. Because of how it works, the last few bits are sometimes unused (66% of the time), which means multiple different base64 strings can decode to the same underlying data.

This is the Base64 alphabet defined in RFC 4648. There are more variants listed in the Wikipedia page for Base64.

IndexCharIndexCharIndexCharIndexChar
0A16Q32g48w
1B17R33h49x
2C18S34i50y
3D19T35j51z
4E20U36k520
5F21V37l531
6G22W38m542
7H23X39n553
8I24Y40o564
9J25Z41p575
10K26a42q586
11L27b43r597
12M28c44s608
13N29d45t619
14O30e46u62+
15P31f47v63/
= (padding)

Encoding and decoding

  1. The plaintext: fslaktern

  2. Plaintext is converted to binary. The binary is split into octets (groups of 8), so one octet equals one plaintext byte:

    01100110 01110011 01101100 01100001 01101011 01110100 01100101 01110010 01101110
  3. Bits are split into sextets (groups of 6):

    011001 100111 001101 101100 011000 010110 101101 110100 011001 010111 001001 101110
  4. Each sextet is converted to an integer which is the index to a character in the base64 character set. One sextet can be at most 111111 (63 or 2^6 - 1) which translates to /.

    25 39 13 44 24 22 45 52 25 2 9 46
  5. Look up each index in the base64 character set and join all resulting characters to a base64 string

    ZnNsYWt0ZXJu
  6. Last step is to add padding if the resulting base64 isn’t of length divisible by 4. This only happens in cases where the length of the input isn’t divisible by 3. Recall that 3 input characters (3 octets or 24 bits) are encoded as 4 output characters (4 sextets or 24 bits).

Alignment and unused bits

Base64 encoding works in groups of 3 bytes (24 bits), split into 4 chunks of 6 bits each (called sextets). If the input length isn’t a multiple of 3 bytes, the final sextet will be partially filled and padded with zero bits. These unused bits don’t affect decoding.

Example hi (2 chars):

  1. hi as bits:

    ASCII:   h           i
    Bits:    01101000    01101001
  2. Split into 6-bit groups (sextets)

    011010  000110  1001..
  3. Pad the last sextet with zeroes

    011010  000110  100100
  4. Convert sextets into base64 indices

    26   6   36
  5. Look up in base64 charset:

    a G k
  6. Add padding to make the output length a multiple of 4 characters

    aGk=

Those final two unused bits in the last sextet can be any of 00, 01, 10, or 11, and the decoded plaintext will be identical, because the decoder ignores them.

This is how a machine would do it. But if we wanted we could populate the last 2 bits of the last sextet with 01, 10 or 11, and the resulting base64 would be:

  • 00 end: aGk=
  • 01 end: aGl=
  • 10 end: aGm=
  • 11 end: aGn=

All four decode to hi, because the decoder ignores those last two bits. So don’t rely on base64 to prove uniqueness. Strip the padding first and the input length is divisible by 3 (3 bytes × 8 bits per byte = 24 bits = 6 bits per sextet × 4 base64 characters), which removes the ambiguity.

See examples on Wikipedia