Как скрыть данные в QR-коде методом LSB

Как скрыть данные в QR-коде методом LSB

@webware

t.me/webware

Привет! Сегодня будет рассмотрен один из примитивных, но рабочих способов сокрытия информации в QR коде методом LSB. Содержание этого способа заключается в следующем: QR-код состоит из чёрных квадратов, расположенных в квадратной сетке на белом фоне, которые считываются с помощью устройств обработки изображений. Проще говоря, QR код является совокупностью пикселей белого и черного цвета определенной последовательности.


Каждый пиксель имеет свой уникальный битовый код. Метод LSB (наименее значащий бит) подразумевает собой замену последних значащих битов в контейнере (изображения, аудио или видеозаписи) на биты скрываемого сообщения. Разница между пустым и заполненным контейнерами должна быть не ощутима для органов восприятия человека.

Суть заключается в следующем: имеется изображение трех пикселей RGB со значениями [0, 0, 0], каждый из них – абсолютно черный цвет. Если мы запишем в канал R значение, равное не нулю, а единице, то для нашего восприятия цвет не поменяется. Таким образом, в канал R изображения из трех пикселей мы можем спрятать три бита информации. Так же и в следующие каналы – по три бита в каждый. Получается 8 сочетаний нулей и единиц в группе по три. В конечном итоге мы сможем спрятать 24 бита информации, либо 3 байта.

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


Это мы рассмотрели возможность внедрения при глубине цвета от 8 бит. Глубина цвета (качество цветопередачи, би́тность изображения) - термин компьютерной графики, означающий количество бит (объём памяти), используемое для хранения и представления цвета при кодировании одного пикселя растровой графики или видео.

Изменив методом LSB черный цвет со значения «0» на значение «1» для нулевого бита и на значение «2» для бита единицы, мы сможем внедрить попиксельно определенный объем данных.


Ниже представлен QR-код, в котором уже закодированы данные поверх сообщения.

Если визуализировать данный метод, то получается следующее:

Используя специализированный сайт "ФотоФорензик" выявить данный метод также затруднительно:

Zsteg также не выявил метод LSB:

zsteg qr_encoded.png
/usr/lib/ruby/2.5.0/open3.rb:199: warning: Insecure world writable dir /mnt/c in PATH, mode 040777
[=] nothing :(

Алгоритм сокрытия данных в QR коде заключается в следующем:

  • Генерируем QR-код;
  • Переводим текстовую информацию для сокрытия в бинарный код из единиц и нулей;
  • Зашифровываем в пикселях черного цвета информацию по вышеописанному алгоритму.

Для реализации нашего проекта воспользуемся интерпретируемым языком Python версии 2.7.


Первостепенно установим необходимую библиотеку Pillow, позволяющую работать с изображениями. Данная библиотека является форком, то есть ответвлением, оригинальной библиотеки PIL. Этот форк был принят в качестве замены оригинальной библиотеки и включён в некоторые дистрибутивы Linux по умолчанию.


Для установки необходимых зависимостей воспользуйтесь командой от имени администратора:

pip install Pillow qrcode

Теперь приступим к разбору алгоритма нашей программы.


Импортируем библиотеки в скрипт:

from PIL import Image, ImageDraw
import qrcode

Image, ImageDraw являются компонентами библиотеки PIL для взаимодействия с изображениями на уровне чтения, создания и редактирования.


Модуль qrcode нужен нам для генерации нужных размеров QR-кода.


Объявляем класс qrHide:

class qrHide(object):

Скрипт необходимо грамотно оформить с выводом каких-либо сообщений в терминал. Объявляем стандартную функцию " __init__":

def __init__(self):
    print("Старт программы!")

Объявляем следующую немаловажную для нас функцию createQR, которая будет генерировать наш стегоконтейнер в виде QR-кода. Указываем параметры, которые будем передавать функции при ее вызове – containText и QRFileName. 


Параметр containText содержит тот текст, на основе которого будет генерация QR-кода, в свою очередь параметр QRFileName отвечает за имя файла, в который будет записан QR-код.

def createQR(self, containText, QRFileName):
    self.a = containText
    self.b = QRFileName

Далее устанавливаем равенство двух внутренних переменных self.a, self.b и containText, QRFileName соответственно. Это обуславливается удобством чтения кода и переменных.


Обработка ошибок будет делаться через связку try-except:

try:
    qr = qrcode.QRCode(
        version=20, #версия QR, чем выше, тем больше код;
        error_correction=qrcode.constants.ERROR_CORRECT_H, #уровень коррекции ошибок. H - 30%
        box_size=4,
        border=4,
    )

    qr.add_data(self.a)
    qr.make(fit=True)

    img = qr.make_image()
    img.save(self.b)
    print("QR создан!")
    return 1      

except:
    print("Ошибка на стадии генерации QR-кода!")
    return 0

Следующие функции предназначены для кодирования и декодирования текста в двоичный код и обратно.

def __encode_binary_string(self, s):
    try: 
        o = ''.join(format(ord(x), '08b') for x in s)
        print ("Текст преобразован в двоичный код успешно!")
        return o
    except:
        print("Ошибка на стадии преобразования текста в двоичный код!")
        return 0
      
def __decode_binary_string(self, s):
    return ''.join(chr(int(s[i*8:i*8+8],2)) for i in range(len(s)//8))

Разберем подробнее функцию, которая скрывает нужный текст методом LSB в QR-коде.

Параметр textToHide - это текст, который необходимо скрыть. QRFileName - имя исходного файла с QR-кодом, outNameFile - имя конечного файла с зашифрованными данными. Открываем сгенерированное изображение QR-кода и конвертируем в градацию серого, так как изначально оно у нас было в битовом представлении. Битовое изображение - бинарное изображение, для представления и хранения которого в цифровом виде используется битовая карта, где на каждый элемент изображения (пиксель) отводится 1 бит информации. Плюсы битовых изображений в том, что они хорошо сжимаются. Минусы - для представления информации отводится только ноль и единица.

Для конвертирования изображения используем функцию convert и выберем необходимым режимом.

  • 1 (Битовое изображение состоящее из черного и белого цвета)
  • L (черно-белое 8-ми битное изображение)
  • P (цветное 8-ми битное изображение )
  • RGB (24-х битное изображение, true color)
  • RGBA (32-х битное изображение, true color, с прозрачностью)
  • CMYK (32-битное изображение, true color, с разделением цветов)
  • YCbCr (32-битное изображение, видео формат)
  • I (32-разрядные целые пиксели со знаком)

F (32-битные пиксели с плавающей точкой)

def encode(self, textToHide, QRFileName, outNameFile): #,QRFileName,outNameFile):
        self.a = self.__encode_binary_string(textToHide)
        self.b = QRFileName
        self.c = outNameFile
  
        try:
            image = Image.open(self.b).convert('L') #Открываем и конвертируем изображение;
            pix = image.load() #Подгружаем его в переменную;
            draw = ImageDraw.Draw(image) #Инициализируем модуль рисования;

            i = 0 #Этот счетчик нужен нам для своевременного брейка;
      
            for y in range(image.size[1]):    #Вхождение первого цикла для отработки по оси координат Oy;
                for x in range(image.size[0]):   #Вхождение  второго цикла для отработки по оси координат Ox;
              
                    if i == len(self.a): #Наш брейк с условием если скрытое сообщение было отработано все, дабы не занимать процессорное время просто так, доводя цикл до конца;
                        break;
                    if pix[x,y][0] == 0: #Прячем информацию только в черные пиксели;

                        if int(self.a[i]) == 0:
                            draw.point((x,y), fill=(1,0,0))

                        elif int(self.a[i]) == 1:
                            draw.point((x,y), fill=(2,0,0))


                        i += 1
              
            image.save(self.c, "PNG") #Сохраняем результат;
            print("QR файл обработан! Текст сокрыт успешно!")
            return 1
      
        except:
            print("Ошибка на стадии внедрения информации!")
            return 0

Декодирование происходит с помощью функции decode, которой мы передаем лишь один параметр fileToDecode;

def decode(self, fileToDecode):

        self.a = fileToDecode
        self.lst = []

        try:
            image = Image.open(self.a)
            pix = image.load()
      
      
            for y in range(image.size[1]):
                    for x in range(image.size[0]):
                  
                        if int(pix[x,y]) == 1:
                            self.lst.append("0")
                        elif int(pix[x,y]) == 2:
                            self.lst.append("1")
                      
            text = self.__decode_binary_string(''.join(self.lst))
            print("Текст декодирован успешно!")
            print "Скрытое сообщение: ", text
            return text
      
        except:
            print("Ошибка на стадии декодирования!")
            return 0
    pass

Таким образом, мы рассмотрели один из возможных стеганографических приемов сокрытия данных методом LSB в QR-коде.

К преимуществам данного способа стоит отнести:

  1. отклонение на пикселях черного цвета трудно выявить специалисту (допустим, сотруднику правоохранительных органов), не знакомому с методом LSB;
  2. достаточно простой, но эффективный способ передачи скрытой информации с условием использования криптографических алгоритмов.

Из недостатков можно выделить следующее:

  1. невозможность применения в условиях компрессии, наличия посторонних шумов либо преобразования изображения;
  2. метод подвергается почти всем видам атак на стегографические алгоритмы.

P.S. Полная версия скрипта.

Источник codeby.net

Report Page