Есть ли хороший способ "кодировать" двоичные данные как правдоподобные слова и обратно?

Чтобы дать вам очень простой и плохой пример. Данные разделяются на 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.