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-ми битного байта. Затем делаем побитовый сдвиг влево.
Теперь, когда мы теоретически подковались можно посмотреть и исходный код, который находится в файле Base64Unit.pas
Примеры.
В первом примере (b64_Example1 5К) показана работа модуля base64-кодирования (декодирования). Три восьмибитных байта кодируются в 4-х символьный стринг и обратно. Понятно, что это всего лишь пример и при вводе цифр и букв следует соблюдать base64-алфавит.
Второй пример (b64_Example2 5К) - вполне работоспособная утилита, позволяющая кодировать любой файл в текстовый base64-формат. Кроме этого утилита позволяет base64-кодированный текст декодировать в двоичный файл. Например, Вы можете "вырвать" из тела e-mail сообщения base64-фрагмент и сохранить его в текстовом файле. А затем декодировать это файл в двоичный файл. Название файла и его расширение можно увидеть в MIME-секции письма - filename.
Конечно, при кодировании надо считывать по 58 байт, а не по 3, как это сделано в примере. Считывание по 58 байт значительно увеличит "скорострельность" утилиты. Но следует учитывать, что это всего лишь пример.
Пример очередного изобретения велосипеда :)
|