Пишу, по большей части, про историю, свою жизнь и немного про программирование.

Стеганография и пробел нулевой длины

Болею, не спится мне. Вспомнил старую свою идею.

В Юникоде есть такой символ замечательный — пробел нулевой длины (код 0x200B), на печать не выводится, понимается всеми современными браузерами и большинством редакторов. Интервала между буквами, как следует из названия, не даёт.

Идея простая, позволяет прятать в текст другой текст, так, чтобы первый не менялся, а второй всегда копировался вместе с первым. Основная мысль — пользуясь буквами исходного текста, как разделителем, ставим между ними столько пробелов нулевой длины, чтобы их число равнялось коду скрываемого символа.

Например. Дан текст: «Болк», в нём надо скрыть короткий текст: «yes». Я взял английские символы, чтобы не заморачиватсья с кодировкой.

Коды символов «yes» — 121, 101, 115. Значит текст приобретает следующий вид:

[121 символ пробела нулевой длины]Б[101 символ пробела нулевой длины]о[115 символов пробела нулевой длины]лк

Можно, кстати, вычитать из кода символа 31, если мы не планируем использовать символы перевода строки и табуляции в скрываемом тексте. Небольшой код на Пайтоне, приведённый ниже, иллюстрирует идею.

​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​#​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​ ​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​E​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​x​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​a​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​m​p​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​l​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​e​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​ ​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​b​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​y​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​ ​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​E​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​v​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​g​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​e​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​n​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​y Stepanischev
from itertools import groupby, izip_longest
import sys
import codecs

sys.stdin = codecs.getreader('utf-8')(sys.stdin)
sys.stdout = codecs.getwriter('utf-8')(sys.stdout)

toenc = 'Evgeny Stepanischev'
input = sys.stdin.read()

def decode(input):
    return ''.join(chr(31+len(list(x[1])))
            for x in groupby(input, lambda x: x == u"\u200b") if x[0])

def encode(input):
    if len(input) < len(toenc):
        raise ValueError()

    return ''.join(x[0] + x[1]
            for x in izip_longest((u"\u200b" * (ord(x)-31)
            for x in toenc), input, fillvalue=''))


print(encode(input) if input.find(u"\u200b") == -1 else decode(input))

Если на вход ему подать текст без пробелов нулевой длины, он добавит в него скрытый текст, иначе попытается его оттуда извлечь.

В принципе, этот подход можно применять в вебе для маркировки своих текстов — браузеры, кроме вымирающих, нормально относятся к этому символу и отображают его адекватно.

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

14 комментариев
Трудорг 2012

Ничего себе! Не знал о таком символе. Интересно. Спасибо. :)
Скорейшего выздоровления!

Евгений Геращенко 2012

Стеганография, а не стенография.

Кстати, если бы был какой-нибудь еще символ с тем же самым смыслом, то количество лишних байтов можно было бы сильно сократить, а то 120 × (2-3) байтов на один символ — это как-то чересчур.

Евгений Степанищев (bolknote.ru) 2012

Комментарий для Евгений Геращенко:

Стеганография, а не стенография.

В два ночи с температурой я ещё и не такое напишу.

Евгений Степанищев (bolknote.ru) 2012

Комментарий для Евгений Геращенко:

Кстати, если бы был какой-нибудь еще символ с тем же самым смыслом, то количество лишних байтов можно было бы сильно сократить, а то 120 × (2-3) байтов на один символ — это как-то чересчур.

Да, я вчера поленился уже об этом писать.

lapinmax@yandex.ru 2012

А поисковые системы индексировать такую страницу как будут? С их точки зрения в примере будет три слова Б, О, ЛК ?

Евгений Степанищев (bolknote.ru) 2012

Комментарий для lapinmax@yandex.ru:

Это надо спросить у поисковых машин. Например, эксперимент провести.

unno (unno.me) 2012

Можно использовать азбуку Морзе, тогда кол-во дополнительных символов в тексте резко уменьшится. Например, видимые символы — это тире, а невидимые пробелы — точки.

Евгений Степанищев (bolknote.ru) 2012

Комментарий для unno.me:

Можно использовать азбуку Морзе, тогда кол-во дополнительных символов в тексте резко уменьшится

Да, но мне сильно лениво было в два ночи это программировать :) Я вообще о кодах Хаффмана подумал сначала.

vtd 2012

а urlencode() как этот символ воспримет?

Евгений Степанищев (bolknote.ru) 2012

Комментарий для vtd:

Закодирует, как ещё. Или в чём вопрос?

Fulcrum (fulc.ru) 2012

Конечно, важна длина текст — таким способом не скрыть текст, длина которого много больше исходной

Не «много больше», а просто «больше», нет? Ну или «больше или равно», если запретить нулевые пробелы в конце строки.

Можно использовать азбуку Морзе, тогда кол-во дополнительных символов в тексте резко уменьшится. Например, видимые символы — это тире, а невидимые пробелы — точки.

Зато и длина текста, который можно будет закодировать, уменьшится.

Евгений Степанищев (bolknote.ru) 2012

Комментарий для fulc.ru:

Не «много больше», а просто «больше», нет? Ну или «больше или равно», если запретить нулевые пробелы в конце строки.

Да, была два ночи и я болею, к чёрту такие подробности :)

Agonych 2012

return ’’.join(chr(31+len(list(x[1]))) for x in groupby(input, lambda x: x == u«\u200b») if x[0])

Вот все-же этот Питонский стиль кодированя, от конца к началу, он для меня совершенно не натурален. Наверно все-же мозги слишком заражены Явой и PHP.

Евгений Степанищев (bolknote.ru) 2012

Комментарий для Agonych:

Вот все-же этот Питонский стиль кодированя, от конца к началу, он для меня совершенно не натурален.

Это не питоновский стиль кодирования, а функциональщина. Можно записать иначе, более похоже на Яву и ПХП.