Есть ли хороший способ "кодировать" двоичные данные как правдоподобные слова и обратно?
Чтобы дать вам очень простой и плохой пример. Данные разделяются на 4 бита. 16 возможных номеров соответствуют первым 16 согласным. Вы добавляете случайный гласный, чтобы сделать его произносимым. Так что "08F734F7" может стать "ba lo ta ku fo go ta ka". Вы можете присоединиться к некоторым слогам и добавить знаки препинания и капитализации, и это может стать "Бало та куфого, Така"? который выглядит как правдоподобный язык.
Просто, чтобы все было ясно, я не пытаюсь защитить двоичные данные.
Я хочу использовать это после сжатия и шифрования обычного (UTF-8) обычного текстового дневника. Получающиеся двоичные данные должны выглядеть довольно случайными. Мне нужно преобразовать эти данные в правдоподобный язык и иметь возможность вернуть его обратно. Я собираюсь напечатать "язык" на бумаге и сделать собственную книгу.
Итак, я ищу лучший метод преобразования случайных данных в читаемые правдоподобные слова. По хорошему я имею в виду отношение наибольших бит к письмам (при этом он выглядит как настоящий язык). В моем примере это ровно 2 бита на букву. Или 4 буквы для байта.
Ответы
Ответ 1
ВОСПРОИЗВЕДЕНИЕ вопроса!
Мое лучшее решение до сих пор кодирует 12 бит в 2-4 символах, давая от 3 до 6 бит на букву. (Пятница - не лучший день, чтобы сделать необходимые математические данные о неравномерном распределении длин слов, поэтому я не разработал средние биты на букву).
Идея состоит в том, чтобы работать с "фонемами", которые начинаются с одного или двух согласных, и заканчиваются одной или двумя гласными. Есть 21 согласный, и я чувствую, что за каждым из них могут следовать h, l, r, w или y и по-прежнему выглядят разумно. Итак, ваша фонема начинается с одной из 126 согласных частей - b, bh, bl, br, bw, by, c, ch, cl, cr,..., z, zh, zl, zr, zw, zy (по общему признанию, думает вроде yy и zl выглядят немного странно, но это иностранный язык в конце концов:))
126 настолько мучительно близко к 128, что мы могли бы добавить t 'и b' (например) для двух последних значений - давая нам словарь из 128 значений, чтобы сохранить 7 бит. Вы даже можете добавить replace yy с d 'и zl с p' или что-то еще.
Аналогично, гласная часть может представлять собой один гласный или пару гласных. Я устранил aa, ii и uu, потому что они выглядят слишком странно для меня (личное предпочтение), даже если они происходят в некоторых реальных словах (кто решил, что "континуум" должен быть записан таким образом в любом случае!). Таким образом, это дает 27 возможных частей гласных: a, e, i, o, u, ae, ai, ao,..., ue, ui, uo.
27 близок к 32, поэтому забрасывайте 5 значений, используя акцентированные гласные (é, â и т.д.). Это дает нам 5 бит с дополнительным преимуществом некоторых редких акцентов.
Итак, 12 бит в 2, 3 или 4 буквы.
Для большего удовольствия, если следующий бит равен 1, вставьте пространство 90% времени (случайным образом) или знак препинания остальные 10% - но если следующий бит равен 0, не вставляйте ничего - просто начните следующую фонему. Заглавные буквы первой буквы после пунктуации.
Это должно дать вам что-то вроде:
Bwaijou t'ei plo ku bhaproti! Llanoi proimlaroo jaévli.
Возможно, кто-то может принять это дальше.
Ответ 2
Резюме
Это решение позволит вам использовать любое из большого количества языков, включая произносимую ерунду с настраиваемой эффективностью. Вы даже можете создать что-то похожее на грамматически правильное, но бессмысленное английское или французское (или, что еще хуже, то, что смещается между ними, как пьяный полиглот). Основная идея состоит в том, чтобы использовать ваши данные для постоянного выбора путей из контекстной свободной грамматики до тех пор, пока вы не закончите данные.
Подробнее
Добавьте строку в конец ввода, которая не встречается нигде внутри нее ( "Это конец моего ввода, спасибо вам очень", вряд ли произойдет в строке зашифрованного текста, для пример.) Вы можете сделать это без строки, но это упростит.
Рассматривайте свой ввод как один очень длинный целочисленный кодированный с низким битом. Очевидно, что ваша машина не сможет обрабатывать такое большое целое число, каждый раз, когда у вас есть нулевой старший байт, просто отмените следующий размер байта значений из вашего файла и умножьте их.
Создайте свой язык как контекстная свободная грамматика. Чтобы не забыть, что такое кодировка, вы можете распечатать ее в конце своей книги. Избегайте двусмысленности. Если ваша грамматика неоднозначна, вы не сможете ее декодировать. Это не сложно, по сути, не использовать один и тот же терминал в двух местах, убедитесь, что конкатенация двух терминалов не может сделать другой терминал, и убедитесь, что при чтении вывода вы можете указать, где вы помещаете символы форматирования.
Теперь, чтобы взять целое число и превратить его в язык, используйте следующий псевдокод, который использует n, чтобы выбрать, какую продукцию взять.
cur=grammar.root (cur is a list of tokens)
n=my input as one big integer
while(n > 0 || cur != grammar root){
if (cur.first.isTerminalSymbol) {
output cur.first
cur.pop_first
if(cur.isEmpty){
cur = grammar root
}
}else{
p = grammar[cur.first].number of productions
t = n mod p // t = low order digit base p
n = n div p // Shift left in base p
cur.pop_first
cur.push_first( grammar[cur.first].productionNumber[t] )
}
}
Для декодирования вы используете стандартный генератор синтаксического анализатора, например GNU bison, который также должен помочь вам избежать создания двусмысленной грамматики.
Запустите анализатор на входе. Теперь запустите n в 0. Вы можете получить серийный номер каждый раз, ссылаясь на дерево синтаксиса, сгенерированное парсером. Затем умножьте n на число производств и добавьте производственный номер, чтобы получить n после этого конкретного ввода. По мере заполнения младшего байта вашего машинного слова переместите его в свой декодированный файл. Когда вы читаете свою уникальную фразу, прекратите декодирование.
Пример 1
Это будет более понятным с примером или тремя.
Мой простой простой язык выглядит следующим образом (нетерминалы капитализируются). Обратите внимание, что из-за большого размера терминалов по сравнению с их глубиной дерева это не очень эффективно, но я думаю, что наличие большего количества терминалов или их сокращение может дать вам любую эффективность, которую вы хотите (до количества бит, потраченного впустую на символ используя n бит на символ).
- S → __capital Существительное I-Verb Punct | __capital Существительное T-Verb Существительное Punct
- Существительное → joe | sally | пятно | автомобиль | печенье | шланг
- I-Verb → работает | жизни | прыжки | мухи
- T-Verb → перескакивает | ест | растет | спины
- Punct → . |! |?
Вы могли бы так же легко сделать это с помощью слогов, как расширение глаголов и существительных. Вы могли бы также включить существительные-фразы и фразы глагола, чтобы иметь прилагательные и т.д. На вашем языке. Возможно, вам также понадобятся символы абзаца и главы, которые разбиваются на соответствующие подразделения с форматированием. Количество альтернативных вариантов на определенном уровне дерева определяет среднее количество бит, закодированных каждым символом. __capital - это пример символа форматирования, который на выходе заглавные буквы следующего слова.
Итак, представьте, что мой вход становится числом 77. Тогда я буду кодировать его следующим образом:
S переходит к двум вещам. 77% 2 = 1. Остаток 77/2 = 38.
Теперь наш номер 38, и мы расширяем __capital, Noun, T-Verb, Noun, Punct
Первое слово - __capital, которое является терминальным символом. Вывод __capital (настройка программы печати для заглавной буквы следующего слова).
Теперь расширяем существительное. Существительное имеет 6 вариантов. 38% 6 = 2. 38/6 = 6. Выберем пятно
Теперь расширяющееся пятно, Т-глагол, Существительное, Пункт. Точечный терминал. Выходное пятно. Принтер, находящийся в режиме "Капитал", записывает "Spot" в выходной файл.
Теперь расширяется T-Verb. Наш номер 6. T-глагол имеет 4 варианта. 6% 4 = 2. 6/4 = 1. Таким образом, мы выбираем "растет". На следующем шаге мы выводим файл в наш файл, так как он является терминалом.
Теперь расширяется Существительное, Punct. Существительное имеет 6 вариантов. Наше число равно 1. 1% 6 = 1 1/6 = 0. Таким образом, мы выбираем "sally", который выводится на следующем шаге.
Наконец, мы расширяем Punct, который имеет 3 варианта. Наш номер равен 0 (и останется таким образом навсегда - вот почему вы добавляете конец текста в конец вашего ввода, иначе ваше декодирование закончится бесконечной цепочкой нулей.) Мы выбираем ".", который выводится.
Теперь текущая строка для расширения пуста, поэтому мы вернем ее в корневой "S". Но так как n равно 0, алгоритм завершается.
Таким образом, 77 стал "Точечным произрастанием".
Пример 2
Вещи становятся более эффективными, если заменить терминалы на:
- I-Verb IVS _space | IVS I-Verb
- глас IVS IVSS
- IVSS w | г
- Гласный a | e | я | o | u | у
- T-Verb TVS _space | TVS T-Verb
- Голосование TVS TVSS
- TVSS p | s
- Существительное NS _space | NS
- Глас NSS NSS
- NSS j | v
77 дает "Jo papa ja". под этим кодированием (и действительно кодируется только "Джо" и тем фактом, что у папы есть 2 слога. Дополнительная будет очень маленькой долей в любом файле длины книги.)
Пример 3
Ваш пример "08F734F7" будет "1000111101110011010011110111" в двоичном формате, который будет "1110111100101100111011110001" при обратном и то есть 250793713 в десятичной форме.
Если я запустил это через более компактную грамматику, я получаю:
25079713 % 2 = 1 n=125396856, S-> __capital Noun T-Verb Noun Punct
125396856 % 2 = 0 n=62698428, Noun->NS _space-> NSS Vowel _space
62698428 % 2 = 0 n=31349214, NSS->j
31349214 % 6 = 0 n=5224869, Vowel->a
5224869 % 2 = 1 n=2612434, T-Verb->TVS T-Verb->TVSS Vowel T-Verb
2612434 % 2 = 0 n=1306217, TVSS->p
1306217 % 6 = 5 n=217702, Vowel->y
217702 % 2 = 0 n=108851, T-Verb->TVSS Vowel _space
108851 % 2 = 1 n=54425, TVSS->s
54425 % 6 = 5 n=9070, Vowel->y
9070 % 2 = 0 n=4535, Noun->NSS Vowel _space
4535 % 2 = 1 n=2267 NSS->v
2267 % 6 = 5 n=377 Vowel->y
377 % 3 = 2 n=125 Punct->?
125 % 2 = 1 n=62 S->__capital Noun T-Verb Noun Punct
62 % 2 = 0 n=31 Noun->NSS Vowel _space
31 % 2 = 1 n=15 NSS->v
15 % 6 = 3 n=2 Vowel->o
2 % 2 = 0 n=1 T-Verb->TVSS Vowel _space
1 % 2 = 1 n=0 TVSS->p
n=0 Vowel _space Noun Punct -> "a ja."
Это дает:
"Ja pysy vy? Vo pa ja". от 08F734F7
(обратите внимание, что моя программа печати удаляет пробелы перед пунктуацией)
Ответ 3
Я лично использовал бы С++. Для программы, которая будет делать то, что вы описали, я бы сделал что-то вроде этого:
void JumbleData(const void *src, int srcLen, char *dest)
{
for(int i = 0; i < srcLen; i++)
{
unsigned char data = *((unsigned char*)src+i);
unsigned char lower = data & 0x0F;
unsigned char higher = (data & 0xF0) >> 4;
dest = 'a' + lower; dest++;
dest = 'a' + higher; dest++
}
}
Это должно разбить данные src на 4-битные разделы, добавить это в 'a' и поместить в пункт назначения. Затем вы можете пройти и добавить дополнительные буквы между ними, но только до тех пор, пока у вас есть строковый способ обращения вспять процесса.
Чтобы сделать его немного менее очевидным, я бы использовал более 4 бит за раз, но не даже 8. Вот пример с использованием кусков 6 бит:
void AddData(char* &dest, unsigned char data);
void JumbleData(const void *src, int srcLen, char *dest)
{
for(int i = 0; i < srcLen; i+=3)
{
unsigned char data0 = *((unsigned char*)src+i);
unsigned char data1 = *((unsigned char*)src+i+1);
unsigned char data2 = *((unsigned char*)src+i+2);
unsigned char chunk0 = data0 & 0x3F;
unsigned char chunk1 = (data0 >> 6) | ((data1 & 0x0F) << 2);
unsigned char chunk2 = (data1 >> 4) | ((data2 & 0x03) << 4);
unsigned char chunk3 = data2 >> 2;
AddData(dest, chunk0);
AddData(dest, chunk1);
AddData(dest, chunk2);
AddData(dest, chunk3);
}
}
void AddData(char* &dest, unsigned char data)
{
const char vowels[] = {'a', 'e', 'i', 'o'};
char letter1 = 'a' + (data & 0x0F);
char letter2 = vowels[((data & 0x0C) >> 2)];
char letter3 = 'n' + ((data & 0x3C) >> 2);
*dest = letter1;
dest++;
*dest = letter2;
dest++;
*dest = letter3;
dest++;
*dest = ' ';
dest++;
}
Это создаст беспорядок из трех буквенных слов для каждого фрагмента из 6 бит.
Ответ 4
Вы можете сделать простой алгоритм подстановки с таблицей преобразования, которая изменяется в зависимости от мощности цифры в исходном номере. Вес значений в таблицах преобразования, чтобы более гласные и некоторые согласные. Выберите некоторую базу, достаточно большую, чтобы иметь разнообразие по всем местам. Например. (данные на основе шестнадцатеричных данных):
value | place
| 0 1 2 ...
------|------ - - -
0 | a a a ...
1 | e e e
2 | i i i
3 | o o q
4 | u u w
5 | y q r
6 | q w f
7 | w r g
8 | r f h
9 | t g j
A | p h k
B | s j c
C | d k v
D | f l b
E | g z n
F | h x m ...
(Это также можно сделать с помощью простых хорошо выбранных формул для каждого столбца...)
Итак,
B4B => "suc"
3AA => "ohk"
F62 => "iwm"
...
Расширьте это до достаточного количества столбцов, чтобы хорошо управлять распределением всех символов. Если исходные данные не имеют чисто случайного распределения, вы можете также смешать порядок от столбца к столбцу. Обратите внимание, как некоторые символы существуют в каждом столбце, а некоторые существуют только один раз. Также гласную на согласную частоту можно изменить, изменив среднее соотношение в каждом столбце.
Возьмите большие блоки фиксированного размера данных и запустите их через конвертер, затем примените алгоритм интервала/пунктуации/капитализации.
(Нет гарантии, что вы не закончите созвучное или очень низкое слово подсчета гласных, но вы можете иметь алгоритм капитализации, чтобы все кепки выглядели как аббревиатура/инициализм)
Ответ 5
Это старый вопрос, но очень интересный.
Как только я захотел сделать подобное преобразование, но имел другие цели. Guid (uuids) обычно не привлекательны для глаз, поэтому мне пришлось преобразовать его в правдоподобные слова. Окончательная система была основана на появлении английской буквы после двух предыдущих. Эта таблица была составлена с использованием корпуса английских предложений, а те, которые использовались слишком редко, были исключены.
Итак, в финальной таблице были строки, похожие на
...
(key:'_t'; next:'aehioruwy'),
(key:'_u'; next:'lmnprst'),
(key:'_w'; next:'aehiory'),
(key:'ab'; next:'abeilorsuwy'),
(key:'ac'; next:'_acehikloqrtuy'),
...
содержит около 200-300 строк, где "next" - все возможные буквы, которые могут появляться после букв "key" (_ - начало или конец слова в зависимости от того, находится ли он в ключе или в следующем).
Процесс преобразования принял текущее значение, разделил его по модулю (далее) и взял остаток как соответствующую букву в качестве следующего "правдоподобного" символа, а частное становится новым текущим значением. Чтобы избежать длинных слов, существует трюк, чтобы явно закрыть слова, используемые симметрично с помощью кодирования и декодирования. Эта система могла бы производить, например, такие последовательности (вход для каждого из них - 128-бит guid/uuid)
Furepas_Wann_Hunkare_Rylacid_Makinuag_Dreem
Togo_Ragam_Omb_Bonsbe_Gonn_Eclecki_Op
или если мы возьмем некоторые широко используемые наборы, например MS IWebBrowser2 {D30C1661-CDAF-11D0-8A3E-00C04FC9E26E}
Lakar_Rupplex_Waylagit_Munghim_Paddato_Molu
( "Lakar Rupplex" - хорошее человеческое имя для браузера, не так ли?)
Что касается плотности, эта система давала около 3 бит на плотность письма.
Ответ 6
Читайте здесь http://email.about.com/cs/standards/a/base64_encoding.htm
Кодирование Base64 занимает три байта, каждый из которых состоит из восьми бит и представляет их как четыре печатных символов в стандарте ASCII. Это делает это по существу двумя шагами.
Первым шагом является преобразование трех байтов до четырех чисел из шести бит. Каждый символ в стандарте ASCII состоит из семи бит. Только Base64 использует 6 бит (соответствует 2 ^ 6 = 64 символов), чтобы обеспечить кодирование данных пригодный для печати и по-человечески читаемым. Никто специальных символов, доступных в Используются ASCII. 64 символа (отсюда и имя Base64) - 10 цифр, 26 строчных букв, 26 прописных букв символы, а также '+' и '/'.
Если, например, три байта 155, 162 и 233, соответствующие (и пугающего) битового потока 100110111010001011101001, который в поворот соответствует 6-битным значениям 38, 58, 11 и 41.