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

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

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

Евгений Геращенко № 2
Стеганография, а не стенография.

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

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

lapinmax@yandex.ru № 5
А поисковые системы индексировать такую страницу как будут? С их точки зрения в примере будет три слова Б, О, ЛК ?
unno (unno.me) № 7
Можно использовать азбуку Морзе, тогда кол-во дополнительных символов в тексте резко уменьшится. Например, видимые символы — это тире, а невидимые пробелы — точки.
Евгений Степанищев (bolknote.ru) автор № 8
Можно использовать азбуку Морзе, тогда кол-во дополнительных символов в тексте резко уменьшится

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

Fulcrum (fulc.ru) № 11
Конечно, важна длина текст — таким способом не скрыть текст, длина которого много больше исходной

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

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

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

Евгений Степанищев (bolknote.ru) автор № 12
Не «много больше», а просто «больше», нет? Ну или «больше или равно», если запретить нулевые пробелы в конце строки.

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

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

Вот все-же этот Питонский стиль кодированя, от конца к началу, он для меня совершенно не натурален. Наверно все-же мозги слишком заражены Явой и PHP.
Евгений Степанищев (bolknote.ru) автор № 14
Вот все-же этот Питонский стиль кодированя, от конца к началу, он для меня совершенно не натурален.

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