Есть ли какой-нибудь хороший способ «кодировать» двоичные данные как правдоподобные слова и обратно? - PullRequest
10 голосов
/ 20 января 2011

Чтобы дать вам очень простой и плохой пример.Данные разбиты на 4 бита.16 возможных номеров соответствуют первым 16 согласным.Вы добавляете случайный гласный, чтобы сделать его произносимым.Таким образом, «08F734F7» может стать «ба ло та ку фо та ка».Вы можете присоединиться к некоторым слогам и добавить знаки препинания и заглавные буквы, и это может стать "Бало та куфого, Така?"который выглядит как правдоподобный язык.

Просто чтобы прояснить, я не пытаюсь защитить двоичные данные.

Я хочу использовать это после того, как сожму и зашифрую свои (UTF-8) текстовый дневник.Полученные двоичные данные должны выглядеть довольно случайными.Мне нужно преобразовать эти данные в правдоподобно выглядящий язык и иметь возможность вернуть их обратно.Я собираюсь напечатать «язык» на бумаге и сделать собственную книгу.

Итак, я ищу лучший способ преобразования случайных данных в читаемые правдоподобные слова.Под хорошим я подразумеваю самое большое соотношение битов к буквам (при этом оно выглядит как настоящий язык)В моем примере это ровно 2 бита на букву.Или 4 буквы за байт.

Ответы [ 6 ]

3 голосов
/ 21 января 2011

ВОПРОСНЫЙ ВОПРОС!

Мое лучшее решение до сих пор кодирует 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 битов. Вы даже можете добавить заменить 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 голосов
/ 22 января 2011

Резюме


Это решение позволит вам использовать любой из большого количества языков, включая произносимую ерунду, с настраиваемой эффективностью. Вы даже можете создать что-то, что выглядит грамматически правильным, но бессмысленным английским или французским (или, что еще хуже, что-то, что перемещается между ними, как пьяный полиглот). Основная идея состоит в том, чтобы использовать ваши данные для непрерывного выбора путей из контекстно-свободной грамматики, пока у вас не закончатся данные.

Подробнее


Добавить строку в конец вашего ввода, которая не встречается нигде внутри него («Это конец моего ввода, большое спасибо» вряд ли будет происходить в строке зашифрованного текста, для Пример.) Вы можете сделать это без строки, но это облегчает.

Рассматривайте ваш вход как один очень длинный целочисленный кодированный младший бит первым. Очевидно, что ваша машина не сможет обрабатывать такое большое целое число, каждый раз, когда у вас будет нулевой старший байт, просто удалите значения из следующего байта из вашего файла и умножьте их на.

Создайте свой язык как контекстно-свободную грамматику . Чтобы не забыть, что такое кодировка, вы можете распечатать ее в конце своей книги. Избегайте двусмысленности. Если ваша грамматика неоднозначна, вы не сможете расшифровать. Это не сложно, по сути, не используйте один и тот же терминал в двух местах, убедитесь, что объединение двух терминалов не может сделать другой терминал, и убедитесь, что при чтении выходных данных вы можете сказать, куда вы помещаете символы форматирования.

Теперь, чтобы взять целое число и превратить его в язык, используйте следующий псевдокод, который использует 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-глагол Punct | __capital существительное T-глагол существительное пункт
  • Существительное -> Джо | Салли | пятно | машина | печенье | шланг
  • I-Verb -> работает | жизни | прыжки | летает
  • T-Verb -> перепрыгивает | ест | растет | спины
  • Пункт ->. | ! |

Вы можете так же легко сделать это с помощью слогов, как расширение глаголов и существительных. Вы также можете включить в свой род существительные и глагольные фразы, чтобы иметь прилагательные и т. Д. Возможно, вам также понадобятся символы абзацев и глав, которые разбиваются на соответствующие подразделения с форматированием. Количество альтернативных вариантов выбора на определенном уровне дерева определяет среднее количество битов, закодированных каждым символом. __capital - это пример символа форматирования, который при выводе использует заглавные буквы для следующего слова.

Итак, представьте, что мой ввод становится числом 77. Тогда я бы закодировал его следующим образом:

S идет к двум вещам. 77% 2 = 1. Остаток 77/2 = 38.

Теперь наш номер 38, и мы расширяем __capital, Существительное, T-глагол, Существительное, Пункту

Первое слово - __capital, который является конечным символом. Вывод __capital (настройка процедуры печати для прописного следующего слова).

Теперь расширяем существительное. Существительное имеет 6 вариантов. 38% 6 = 2. 38/6 = 6. Выбираем спот

Теперь расширяющееся пятно, T-глагол, существительное, пункт. Спот-терминал. Выходной спот. Принтер, находящийся в режиме прописной буквы, записывает «Spot» в выходной файл.

Теперь расширяем T-Verb. Наш номер 6. Т-глагол имеет 4 варианта. 6% 4 = 2. 6/4 = 1. Поэтому мы выбираем «растет». На следующем шаге мы вырастем до нашего файла, так как это терминал.

Теперь расширяем существительное, пункт. Существительное имеет 6 вариантов. Наше число равно 1. 1% 6 = 1 1/6 = 0. Поэтому мы выбираем «Салли», который выводится на следующем шаге.

Наконец, мы расширяем пункт, который имеет 3 варианта. Наше число равно 0 (и останется таким же навсегда - вот почему вы добавляете строку конца текста к концу ввода, иначе ваше декодирование закончилось бы бесконечной строкой нулей.) Мы выбираем «.», который выводится.

Теперь текущая строка для раскрытия пуста, поэтому мы возвращаем ее к корню "S". Но так как n равно 0, алгоритм завершается.

Таким образом, 77 стал "Пятно растет Салли".

Пример 2


Вещи станут более эффективными, если я заменю свои терминалы на:

  • I-Verb IVS _space | IVS I-Verb
  • IVS IVSS гласная
  • IVSS w | г
  • гласная а | е | я | о | ты | у
  • T-Verb TVS _space | TVS T-Verb
  • TVS TVSS гласная
  • TVSS p | s
  • Существительное NS _space | NS
  • NS NSS Vowel
  • NSS j | V

77 дает "Джо папа джа." в этой кодировке (и действительно кодируется только «Джо» и тем фактом, что папа имеет 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 (обратите внимание, что моя процедура печати удаляет пробелы перед пунктуацией)

0 голосов
/ 16 апреля 2013

Это старый вопрос, но очень интересный.

Когда-то я хотел сделать подобное преобразование, но с другими целями.Guid (uuids) обычно не радуют глаз, поэтому мне пришлось преобразовать его в правдоподобные слова.Окончательная система основывалась на появлении букв английского алфавита после двух предыдущих.Эта таблица была составлена ​​с использованием набора английских предложений, и те, которые использовались слишком редко, были исключены.Таким образом, финальная таблица содержала строки, похожие на

  ...
  (key:'_t'; next:'aehioruwy'),
  (key:'_u'; next:'lmnprst'),
  (key:'_w'; next:'aehiory'),
  (key:'ab'; next:'abeilorsuwy'),
  (key:'ac'; next:'_acehikloqrtuy'),
  ...

, содержащие около 200-300 строк, где «следующий» - это все возможные буквы, которые могут появляться после «ключевых» букв (_ - начало или конец словав зависимости от того, находится ли он в ключе или в следующем).

Процесс преобразования принимает текущее значение, делит его по длине по модулю (далее) и принимает остаток в качестве соответствующей буквы в качестве следующего «правдоподобного» символа, частное становится новым текущим значением.Чтобы избежать длинных слов, была хитрость для явного завершения слов, используемых симметрично путем кодирования и декодирования.Эта система может производить, например, такие последовательности (вход для каждой 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 бит наплотность букв.

0 голосов
/ 21 января 2011

Вы можете сделать простой алгоритм замещения с таблицей преобразования набора, которая изменяется в зависимости от мощности цифры в исходном числе. Взвесьте значения в таблицах преобразования, чтобы гласные и определенные согласные были более распространенными. Выберите базу, достаточно большую для разнообразия мест. Например. (данные в шестнадцатеричном формате):

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"
...

Расширьте это до достаточного количества столбцов, чтобы хорошо контролировать распределение всех персонажей. Если ваши исходные данные не имеют чисто случайного распределения, вы можете также перепутать порядок от столбца к столбцу. Обратите внимание, что некоторые символы существуют в каждом столбце, а некоторые существуют только один раз. Кроме того, частота гласных и согласных может быть изменена путем изменения среднего отношения в каждом столбце.

Возьмите большие порции данных фиксированного размера и пропустите их через конвертер, затем примените алгоритм пробела / пунктуации / капитализации.

(Нет никакой гарантии, что вы не получите в конечном итоге слова со всеми согласными или с чрезвычайно низким количеством гласных, но вы можете использовать алгоритм прописных букв, чтобы все заглавные буквы выглядели как акроним / инициализм)

0 голосов
/ 20 января 2011

Я бы лично использовал с ++.Для программы, которая будет выполнять то, что вы описываете, я бы сделал что-то вроде этого:

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++;
}

Это создаст путаницу из 3 буквенных слов для каждого кусочка из 6 битов.

0 голосов
/ 20 января 2011

Читать здесь 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.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...