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

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

В Юникоде есть такой символ замечательный — пробел нулевой длины (код 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))
Если на вход ему подать текст без пробелов нулевой длины, он добавит в него скрытый текст, иначе попытается его оттуда извлечь.

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

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

Трудорг (инкогнито)
23 января 2012, 01:54

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

Евгений Геращенко (инкогнито)
23 января 2012, 02:12

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

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

bolk (bolknote.ru)
23 января 2012, 06:44, ответ предназначен Евгению Геращенко

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

bolk (bolknote.ru)
23 января 2012, 06:45, ответ предназначен Евгению Геращенко

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

lapinmax@yandex.ru (инкогнито)
23 января 2012, 07:39

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

bolk (bolknote.ru)
23 января 2012, 07:50, ответ предназначен lapinmax@yandex.ru

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

unno (unno.me)
23 января 2012, 08:25

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

bolk (bolknote.ru)
23 января 2012, 08:47, ответ предназначен unno (unno.me):

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

vtd (инкогнито)
23 января 2012, 09:09

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

bolk (bolknote.ru)
23 января 2012, 09:17, ответ предназначен vtd

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

Fulcrum (fulc.ru)
23 января 2012, 16:04

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

bolk (bolknote.ru)
23 января 2012, 16:16, ответ предназначен Fulcrum (fulc.ru):

Не «много больше», а просто «больше», нет? Ну или «больше или равно», если запретить нулевые пробелы в конце строки.
Да, была два ночи и я болею, к чёрту такие подробности :)

Agonych (инкогнито)
27 января 2012, 05:42

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

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

bolk (bolknote.ru)
27 января 2012, 06:41, ответ предназначен Agonych

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

Ваше имя или адрес блога (можно OpenID):

Текст вашего комментария, не HTML:

Кому бы вы хотели ответить (или кликните на его аватару)