Base64の仕組み

Base64とはバイナリーデータを扱えない環境において、マルチバイト文字や画像データなどの添付ファイルを扱えるようにするためのエンコード方式です。 その名の「64」が示すとおり、あらゆるデータを64個のASCII文字データと「=」に置き換えます。電子メールやBasic認証などで用いられています。

エンコードにより3バイトデータが4バイトになるため、データ量は133%に増えてしまいます。このため大容量のデータ転送には不向きです。

エンコードとは、後で元のデータに戻せるような変換方法です。元のデータに戻す変換をデコードといいます。

目次

変換方法

データをBase64エンコードする手順を示します。

  1. 変換したいデータを6ビットずつのブロックに分割します。
  2. 最終ブロックが6ビットに満たない場合、下位ビットに「0」を追加して6ビットにします。
  3. 変換表にしたがってASCIIデータに変換します。
  4. ASCIIデータ長が4で割り切れない場合、4の倍数になるまで後ろに「=」を追加します。

逆の手順で処理するだけで簡単にデコードできます。

エンコード例

ASCIIの「ABCDE」を変換するものとします。

  1. 2進数に置き換えます。
    “01000001, 01000010, 01000011, 01000100, 01000101”
  2. 6ビットずつに区切ります。
    “010000, 010100, 001001, 000011, 010001, 000100, 0101”
  3. 最終ブロックが6ビットに満たないため、「0」を追加して6ビットにします。
    “010000, 010100, 001001, 000011, 010001, 000100, 010100”
  4. 変換表にしたがってASCIIデータに変換します。
    “QUJDREU”
  5. ASCIIデータ長が4で割り切れないため、「=」を追加します。
    “QUJDREU=”

変換表

スクロールできます
2進10進文字2進10進文字
0000000A 10000032g
0000011B 10000133h
0000102C 10001034i
0000113D 10001135j
0001004E 10010036k
0001015F 10010137l
0001106G 10011038m
0001117H 10011139n
0010008I 10100040o
0010019J 10100141p
00101010K 10101042q
00101111L 10101143r
00110012M 10110044s
00110113N 10110145t
00111014O 10111046u
00111115P 10111147v
01000016Q 11000048w
01000117R 11000149x
01001018S 11001050y
01001119T 11001151z
01010020U 110100520
01010121V 110101531
01011022W 110110542
01011123X 110111553
01100024Y 111000564
01100125Z 111001575
01101026a 111010586
01101127b 111011597
01110028c 111100608
01110129d 111101619
01111030e 11111062+
01111131f 11111163/

サンプルコード

[base64.c]

/**
 * @file base64.c
 * @brief Base64処理
 */

//------------------------------------------------------------------------------
// include
//------------------------------------------------------------------------------
#include "base64.h"

//------------------------------------------------------------------------------
// private variable
//------------------------------------------------------------------------------
//! Base64エンコードテーブル
static const uint8_t m_enc_table[] =
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

// clang-format off
//! @brief Base64デコードテーブル
//! @details '+'~'z'のASCIIデータ
static const int8_t m_dec_table[] = {
                                              62, -1, -1, -1, 63,
  52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1,  0, -1, -1,
  -1,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,
  15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
  -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
  41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51
};
// clang-format on

//------------------------------------------------------------------------------
// private function
//------------------------------------------------------------------------------

/** ----------------------------------------------------------------------------
 * @brief Base64エンコード
 *
 * @param [in] bin : バイナリーデータ
 * @param bin_size : バイナリーデータ長
 * @param [out] ascii : ASCIIデータ
 * @return ASCIIデータ長
 */
int32_t base64_encode(const uint8_t bin[], uint32_t bin_size, uint8_t ascii[])
{
  uint8_t  bit6;
  uint8_t  buf_size   = 0;
  uint16_t buf        = 0;
  uint32_t ascii_size = 0;

  for (uint32_t i = 0; i < bin_size; i++) {
    buf = (uint16_t)(buf << 8) | bin[i];
    buf_size += 8;

    // 6ビット毎に変換
    do {
      bit6 = (uint8_t)(buf >> (buf_size - 6)) & 0x3f;
      buf_size -= 6;
      ascii[ascii_size] = m_enc_table[bit6];
      ascii_size++;
    } while (buf_size >= 6);
  }

  // 端数ビットがある場合は0を補完して変換
  if (buf_size > 0) {
    bit6              = (uint8_t)(buf << (6 - buf_size)) & 0x3f;
    ascii[ascii_size] = m_enc_table[bit6];
    ascii_size++;
  }

  // バイト数が4の倍数でないなら'='を追加
  while ((ascii_size % 4) != 0) {
    ascii[ascii_size] = (uint8_t)'=';
    ascii_size++;
  }
  ascii[ascii_size] = (uint8_t)'\0';

  return (int32_t)ascii_size;
}

/** ----------------------------------------------------------------------------
 * @brief Base64デコード
 *
 * @param [in] ascii : ASCIIデータ
 * @param ascii_size : ASCIIデータ長
 * @param [out] bin : バイナリーデータ
 * @return バイナリーデータ長
 * @retval データ長 >= 0 : バイナリデータ長
 * @retval データ長 < 0 : ASCIIデータ異常
 */
int32_t base64_decode(const uint8_t ascii[], uint32_t ascii_size, uint8_t bin[])
{
  int8_t   i;
  int8_t   tmp;
  uint8_t  buf[4];
  uint32_t size     = 0;
  uint32_t bin_size = 0;

  if ((ascii_size % 4) != 0) {
    return -1;
  }

  do {
    // 4バイトのASCIIデータを3バイトのバイナリーデータに変換
    for (i = 0; i < 4; i++) {
      if ((ascii[size] < (uint8_t)'+') || (ascii[size] > (uint8_t)'z')) {
        return -1;
      }

      tmp = m_dec_table[ascii[size] - (uint8_t)'+'];
      if (tmp < 0) {
        return -1;
      }
      buf[i] = (uint8_t)tmp;
      size++;
    }

    bin[bin_size] = (uint8_t)((uint8_t)(buf[0] & 0x3f) << 2) |
                    (uint8_t)((uint8_t)(buf[1] & 0x30) >> 4);
    bin_size++;
    bin[bin_size] = (uint8_t)((uint8_t)(buf[1] & 0x0f) << 4) |
                    (uint8_t)((uint8_t)(buf[2] & 0x3c) >> 2);
    bin_size++;
    bin[bin_size] = (uint8_t)((uint8_t)(buf[2] & 0x03) << 6) |
                    (uint8_t)((uint8_t)(buf[3] & 0x3f));
    bin_size++;
  } while (size < ascii_size);

  // '='分を削除
  if (ascii[ascii_size - 1] == (uint8_t)'=') {
    bin_size--;
  }
  if (ascii[ascii_size - 2] == (uint8_t)'=') {
    bin_size--;
  }

  return bin_size;
}

[base64.h]

#ifndef BASE64_H
#define BASE64_H

#ifdef __cplusplus
extern "C" {
#endif

//------------------------------------------------------------------------------
// include
//------------------------------------------------------------------------------
#include <stdint.h>

//------------------------------------------------------------------------------
// define
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
// function
//------------------------------------------------------------------------------
int32_t base64_encode(const uint8_t bin[], uint32_t bin_size, uint8_t ascii[]);
int32_t base64_decode(const uint8_t ascii[], uint32_t ascii_size,
                      uint8_t bin[]);

#ifdef __cplusplus
}
#endif

#endif

サンプルコードの使用例

使用例代わりに、CppUTestによるテストコードを掲載しておきます。

#include <string.h>

#include "CppUTest/TestHarness.h"
#include "base64.h"

// clang-format off
TEST_GROUP(base64){
  TEST_SETUP(){
  }

  TEST_TEARDOWN(){
  }
};
// clang-format on

TEST(base64, Test_base64_encode)
{
  uint8_t bin[128];
  uint8_t ascii[128];
  int32_t size;

  // 成功パターン
  strncpy((char *)bin, "ABCDE", 5);
  size = base64_encode(bin, 5, ascii);
  LONGS_EQUAL(size, 8);
  STRNCMP_EQUAL((char *)ascii, "QUJDREU=", size);
}

TEST(base64, Test_base64_decode)
{
  uint8_t bin[128];
  uint8_t ascii[128];
  int32_t size;

  // 成功パターン
  strncpy((char *)ascii, "QUJDREU=", 8);
  size = base64_decode(ascii, 8, bin);
  LONGS_EQUAL(size, 5);
  STRNCMP_EQUAL((char *)bin, "ABCDE", size);
}
目次