Base64 для непродвинутых
Автор: Александр Терехов
Вступление
Изначально для передачи электронной почты в Интернет использовался
только текст (RFC822). Затем, с развитием компьютерных девайсов,
потребовалась возможность передачи нетекстовой информации: аудио,
видео, графических файлов, файлов приложений и т.д. Однако почтовые
сервера как понимали только текст, так и остались понимать только его.
Поэтому появилась необходимость каким-то образом преобразовать двоичный
файл в текстовый. Вообще-то способ такого преобразования уже имел место
- это UUE кодирование. Но появился еще один - base64. Этот способ
используется в спецификации MIME (RFC2045-2049).
Что такое MIME? Если говорить вкратце, то это стандарт описания
заголовков e-mail сообщений. Используя этот стандарт, в одном письме
можно отправить сразу несколько различных вложений. Например, можно
положить в аттачмент письма архивированный файл, оформить сообщение как
просто текст, а также поместить HTML-страницу. И все это отправить
получателю. Почтовая программа-получатель, понимающая MIME, совершенно
свободно из файла электронной почты (который на самом деле является
"обычным" текстовым файлом) извлечет архив, покажет сообщение и
обработает тэги HTML. Некоторые почтовики, например Outlook Express, на
радость вирмейкерам без спроса пользователя еще и запустят вложенные в
HTML-страницу скрипты.
Идеология base64
Как известно, байт состоит из восьми битов :)
В один байт можно вложить 256 цифр, от 0 до 255. Однако, если вместо
восьми байт использовать только шесть, то объем вложенной информации
уменьшается до 64 цифр, от 0 до 63. Теперь главное: любую цифру 6-ти
битового байта можно представить в виде печатного символа. 64 символа
это не так много, us-ascii символов вполне хватит. Ниже представлен
64-х символьный base64 "алфавит".
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/
где код символа A - 0, а код символа / - 63.
Вроде, понятно. Что дальше?
А далее берутся три последовательных байта по восемь бит (всего 24
бита), и побитно делятся на четыре 6-ти битных байта (всего 24 бита).
Немного странно звучит: "шестибитный байт". На самом деле бит восемь,
однако используются только 6 младших бит, два старших бита
игнорируются. Схематично такое "деление три к четырем" можно
представить себе так:
В приведенном примере три числа 103, 193 и 58 были закодированы в
base64 формат. В результате мы получили 4-х символьный стринг Z8E6.
Т.о. на практике увидели идеологию перевода двоичной информации в текст
по принципу 3 к 4.
Основываясь на этом принципе, мы можем закодировать любую двоичную
информацию в текст, причем не очень сильно увеличивая ее объем (на
30%). Затем наша информация через почтовый сервер попадет к нужному
адресату, почтовик которого декодирует текст в двоичный файл.
Все довольны, все смеются.
"Но..", скажите Вы, - "что делать, если у нас нет трех байтов? Есть
только один или два!" В этом случае в конец четырех символьного стринга
добавляется символ = (равно). Если не хватает (до трех) одного байта,
то добавляется один символ "равно":
Z8E=
если не хватает двух байт, то добавляются два символа "равно":
Z8==
Что радует: с символами "равно" надо разбираться только один раз - при
чтении конца файла.
На этом вроде бы все... Нет, еще не все. Формат base64 имеет
ограничение - общая длина строки, состоящая из 4-х символьных стрингов
составляет 72 символа (за исключением самой последней строки - там уже
сколько получится).
Для тех, кто не особо понял, о чем выше шла речь, рекомендую сделать следующее:
- Имеющейся почтовой программой сохранить в файл какое-нибудь письмо.
Письмо, естественно, надо выбрать со вложенным файлом. При сохранении
письма, тип файла следует выбрать "почтового формата" (e-mail message),
например *.msg или *.eml
- Включить самый мощный текстовый редактор - Notepad ("Блокнот") и
открыть сохраненный файл письма. Тем, у кого п.2 не получился с первого
раза - тип файла при открытии его "Блокнотом" надо указать "Все файлы"
(*.*)
- В "Блокноте" (а если файл большой, то в WordPad'е) откроется примерно нижеследующее:
Date: Sat, 12 Oct 2002 10:09:59 +0000
From: "Mr.Dark"
X-Mailer: The Bat! (v1.47 Halloween Edition)
Reply-To: "Mr.Dark"
Organization: Dark Company
X-Priority: 3 (Normal)
Message-ID: <5614244603.20021012100959@online.ru>
To: =?koi8-r?B?5czFzsEg5snMydDQz9fB?=
Subject: Base64
Mime-Version: 1.0
Content-Type: multipart/mixed; boundary="----------73121211AFBA8E3"
------------73121211AFBA8E3
Content-Type: text/plain; charset=koi8-r
Content-Transfer-Encoding: 8bit
Hello еМЕОБ,
--
Best regards,
Mr.Dark mailto:dark@online.ru
Delphi-РТПЗТБННЙТПЧБОЙЕ http://www.inta.portal.ru/dark/
------------73121211AFBA8E3
Content-Type: image/jpeg; name="ex1.jpg"
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="ex1.jpg"
/9j/4AAQSkZJRgABAQEBLAEsAAD/2wBDAAUDBAQEAwUEBAQFBQUGBwwIBwcHBw8LCwkMEQ8SEhEP
ERETFhwXExQaFRERGCEYGh0dHx8fExciJCIeJBweHx7/2wBDAQUFBQcGBw4ICA4eFBEUHh4eHh4e
Hh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh7/wAARCADFApsDASIA
AhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQA
============================вырезано==================================
FABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUU
AFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQA
UUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAf/Z
------------73121211AFBA8E3--
Полный текст можно посмотреть в файле EMail.txt
Content, boundary и все остальное, что идет после строки Mime-Version:
1.0 - это стандарт MIME. Буквы, написанные в koi8-r, читаются в виде
абракадабры. И это правильно, т.к. кодировка настоящего документа
установлена в windows-1251. Если в Вашем броузере установить кодировку
koi8-r (в Internet Explorer 5.0 в главном меню выбрать "Вид"-"Вид
кодировки"-"Кириллица (КОИ8-Р)"), то эти буквы можно будет прочитать.
Остальной текст, правда, разобрать вряд ли удастся :) В конце текста
письма находится очень большой блок еще одной абракадабры. В
приведенном тексте он немного порезан. Так вот этот блок и есть
двоичный файл закодированный base64 в текст. Чуть выше "большого блока"
видно, что для кодировки применялся base64, видны также названия файла.
То, что выше - реальное название файла и его тип: 'ex1.jpg', тип файла
image/jpeg, которое пониже - название этого файла в окне аттача письма.
Теперь, надеюсь, все понятно. Да, еще. Немного отвлекаясь от base64,
скажу, что чтение заголовков писем - наиувлекательнейшее занятие.
Например, с помощью одной из моих программ (IPScaner, лежит на моем сайте
в разделе "Программы"), по заголовку письма можно очень даже конкретно
определить географическое место отправки e-mail. Однажды вычислил даже
номер кабинета в одном из московских институтов, откуда ко мне пришло
письмо.
Алгоритм base64 кодирования и декодирования.
Наверное существует самый оптимальный и быстрый алгоритм кодирования и
декодирования base64.
Но... Почему-то хочется в очередной раз самому изобрести велосипед...
Нисколько не претендуя на оптимальность, скорострельность и
оригинальность...
Итак, рассматривая идеологию base64, первое, что приходит в голову -
это устроить небольшой битовый конвейер. Т.е. (для случая кодирования)
взять 8-ой (старший) бит исходного байта и поместить его в начало 6-ти
битового байта (по поводу термина "6-ти битовый байт" см. выше). Затем
на место 8-го бита исходного байта поместить 7-й бит, а в 6-ти битовом
байте первый бит (младший) переместить на место 2-го бита. После такого
перемещения освобождается первый (младший) бит 6-ти битного числа. В
него и поместим старший (бывший седьмой) бит исходного байта. Затем еще
раз передвинем биты в обоих байтах. И т.д. Примерно так:
Рассмотрим шаг 1.
Как известно стандартные операнды Паскаля AND и OR могут выступать в
двух ипостасях: битовая арифметика и логика. В первом шаге используем
операнд AND как битовую ипостась :)
Для проверки установки старшего бита исходного 8-ми битного байта
наложим на него так называемую "маску". Т.е. применим к нему побитовую
операцию AND с числом 128 (10000000).
Как видно из приведенной схемы, проверить установку старшего бита совсем
несложно. Если результатом операции получается число 128, значит бит установлен,
а если в результате получаем 0, значит старший бит не установлен. Далее в
зависимости от полученного результата применим к 6-ти битовому байту битовую
операцию OR с числом 1, которая в любом случае применения устанавливает первый
(младший) байт в 1.
Здесь необходимо учитывать то, что перед применением побитовой операции OR с
числом 1 младший бит 6-ти битового байта всегда 0. В случае для первых двух
шагов потому, что мы сами обнуляем его при инициализации, для последующих шагов,
потому что применяем операцию SHR.
Итак, посмотрим как выглядит реализация первого шага в Паскале:
if (Source and 128) = 128 then Destination := Destination or 1;
|
где Source - 8-ми битный байт источник, а Destination - 6-ти битный байт приемник.
Шаг 2.
Сместим первый бит 6-ти битного байта на вторую позицию, одновременно обнуляя его первый бит.
Destination:=Destination SHR 1;
Сместим седьмой байт 8-ми битного байта на 8-е(старшее) место.
Source:=Source SHR 1;
Операция X SHR Y, смещает в байте X биты на Y-мест влево и
автоматически устанавливает Y-младшие байты в 0, следовательно значение
числа изменяется. Поэтому в реальном коде мы будем работать не с самим
байтом-источником, а с его временной копией.
Дальнейшие шаги.
Повторяем шаги 1 и 2 шесть раз для каждого бита 6-ти битного байта.
Одновременно необходимо следить за тем, что если мы обработали восемь
бит байта-источника, следует перейти к новому байту. И последнее - если
обработаны все три байта-источники, следует выйти из функции
кодирования.
Теперь о декодировании.
Декодирование по сути ничем не отличается от кодирования. Делаем тоже
самое, только источник и приемник меняются местами. А битовый конвейер
должен продолжать работать в ту же сторону - справа - на лево:
проверяем установку первого бита 6-ти битного байта (маску в этом
случае надо накладывать с числом 32 (0010000) - не восемь битов, а
шесть) и в зависимости от результата устанавливаем (или не
устанавливаем) младший байт 8-ми битного байта. Затем делаем побитовый
сдвиг влево.
Примеры.
В первом примере (b64_Example1
5К) показана работа модуля base64-кодирования (декодирования). Три
восьмибитных байта кодируются в 4-х символьный стринг и обратно.
Понятно, что это всего лишь пример и при вводе цифр и букв следует
соблюдать base64-алфавит.
Второй пример (b64_Example2
5К) - вполне работоспособная утилита, позволяющая кодировать любой файл
в текстовый base64-формат. Кроме этого утилита позволяет
base64-кодированный текст декодировать в двоичный файл. Например, Вы
можете "вырвать" из тела e-mail сообщения base64-фрагмент и сохранить
его в текстовом файле. А затем декодировать это файл в двоичный файл.
Название файла и его расширение можно увидеть в MIME-секции письма -
filename.
Конечно, при кодировании надо считывать по 58 байт, а не по 3, как это
сделано в примере. Считывание по 58 байт значительно увеличит
"скорострельность" утилиты. Но следует учитывать, что это всего лишь
пример.
Пример очередного изобретения велосипеда :)