Делаем текст невидимым

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

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

Как же все-таки сделать текст невидимым?

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

Отметим, что индекс находится в диапазоне от 0 до 7, что очень напоминает цифры восьмеричной системы. А это обстоятельство позволяет перевести обычную последовательность печатных символов в серию символов из уже подготовленного массива: для этого достаточно преобразовать последовательность в беззнаковое байтовое представление и каждый байт представить как некое число в восьмеричной системе, после чего использовать цифры каждого полученного числа как индексы для массива невидимых символов.

Общий принцип, я думаю, понятен, однако, для упрощения работы можно вместо массива использовать ассоциативный массив и не из восьми невидимых символов, а специально предрасчитанный массив  в котором роль ключей играют байты (т.е. значения от 0 до 255), а роль значений — соответствующие байтам последовательности невидимых символов. Подобный трюк может облегчить задачу не только кодирования, но и декодирования: достаточно поменять местами ключи и значения — и массив для декодирования уже готов.

Но вернемся к кодированию.

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

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

enum INVISIBLE_CHARS = ["\u2060", "\u200B", "\u2061", "\u2062", "\u2063", "\uFEFF", "\u200C", "\u200D"];
        
        
        // конвертация в невидимые символы
        string hideSymbol(ubyte number)
        {
            string accumulator;

            accumulator ~= (number & 0xc0) >> 6;
            accumulator ~= (number & 0x38) >> 3;
            accumulator ~= (number & 0x07);

            return accumulator
                              .map!(a => INVISIBLE_CHARS[a])
                              .join
                              .to!string;
        }

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

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

import std.algorithm;
import std.conv : to;
import std.range;
import std.stdio;
import std.string;

class TextHider
{
    private
    {
        enum INVISIBLE_CHARS = ["\u2060", "\u200B", "\u2061", "\u2062", "\u2063", "\uFEFF", "\u200C", "\u200D"];
        string[ubyte] symbols;
        ubyte[string] bytes;
        
        
        // конвертация в невидимые символы
        string hideSymbol(ubyte number)
        {
            string accumulator;

            accumulator ~= (number & 0xc0) >> 6;
            accumulator ~= (number & 0x38) >> 3;
            accumulator ~= (number & 0x07);

            return accumulator
                              .map!(a => INVISIBLE_CHARS[a])
                              .join
                              .to!string;
        }


        auto createSymbolsTable()
        {
            string[ubyte] table;

            foreach (ubyte e; 0..256)
            {
                table[e] = hideSymbol(e);
            }

            return table;
        }
    }

    this()
    {
        symbols = createSymbolsTable;
        bytes = symbols.swapKeyWithValues;
    }

    string encode(string text)
    {
        return text
                .representation
                .map!(a => symbols[a])
                .join
                .to!string;
    }

    string decode(string text)
    {
        return text
                .chunks(3)
                .map!(a => bytes[a.to!string])
                .array
                .assumeUTF;
    }
}

void main()
{
    auto h = "Скрытый текст приветствия";
    auto v = h.hideText;
    writeln(v);
    writeln(v.showText);     

    auto tp = new TextHider;
    auto w = tp.encode("мяу");
    w.writeln;
    tp.decode(w).writeln;
}

Небольшой комментарий: обратная процедура (т.е. декодирование) делает все с точностью наоборот, для чего из кодированных символов собирается их байтовое представление, которое превращается в строку с помощью процедуры assumeUTF из std.string. Эта функция по своей сути является обратной для функции representation из того же самого модуля (спасибо за эту подсказку-напоминание группе dlang в Telegram).

Теперь и вы можете спрятать текст от посторонних глаз.

P.S: В текст этой статьи мы тоже поместили невидимые символы. Может быть вы их найдете?
         
        
       
            
     

aquaratixc

Программист-самоучка и программист-любитель

Добавить комментарий