Сегодня рассмотрим один из примитивных, но рабочих способов сокрытия информации в QR коде методом LSB. Содержание этого способа заключается в следующем: QR-код состоит из чёрных квадратов, расположенных в квадратной сетке на белом фоне, которые считываются с помощью устройств обработки изображений. Проще говоря, QR код это совокупность пикселей белого и черного цвета определенной последовательности.
Каждый пиксель имеет свой уникальный битовый код. Метод LSB (наименее значащий бит) подразумевает собой замену последних значащих битов в контейнере (изображения, аудио или видеозаписи) на биты скрываемого сообщения. Разница между пустым и заполненным контейнерами должна быть не ощутима для органов восприятия человека.
Суть заключается в следующем: имеется изображение трех пикселей RGB со значениями [0, 0, 0], каждый из них – абсолютно черный цвет. Если мы запишем в канал R значение, равное не нулю, а единице, то для нашего восприятия цвет не поменяется. Таким образом, в канал R изображения из трех пикселей мы можем спрятать три бита информации. Так же и в следующие каналы – по три бита в каждый. Получается 8 сочетаний нулей и единиц в группе по три. В конечном итоге мы сможем спрятать 24 бита информации, либо 3 байта.
Стоит отметить недостатки метода. Методы LSB являются неустойчивыми ко всем видам атак и могут быть использованы только при отсутствии шума в канале передачи данных. Обнаружение LSB-кодированного стего осуществляется по аномальным характеристикам распределения значений диапазона младших битов отсчётов цифрового сигнала.
Это мы рассмотрели возможность внедрения при глубине цвета от 8 бит. Глубина цвета (качество цветопередачи, би́тность изображения) - термин компьютерной графики, означающий количество бит (объём памяти), используемое для хранения и представления цвета при кодировании одного пикселя растровой графики или видео.
Изменив методом LSB черный цвет со значения «0» на значение «1» для нулевого бита и на значение «2» для бита единицы, мы сможем внедрить попиксельно определенный объем данных.
Ниже представлен QR-код, в котором уже закодированы данные поверх сообщения.
Если визуализировать данный метод, то получается следующее:
Используя специализированный сайт "ФотоФорензик" выявить данный метод также затруднительно:
Zsteg также не выявил метод LSB:
Алгоритм сокрытия данных в QR коде заключается в следующем:
Первостепенно установим необходимую библиотеку Pillow, позволяющую работать с изображениями. Данная библиотека является форком, то есть ответвлением, оригинальной библиотеки PIL. Этот форк был принят в качестве замены оригинальной библиотеки и включён в некоторые дистрибутивы Linux по умолчанию.
Для установки необходимых зависимостей воспользуйтесь командой от имени администратора:
Теперь приступим к разбору алгоритма нашей программы.
Импортируем библиотеки в скрипт:
Image, ImageDraw являются компонентами библиотеки PIL для взаимодействия с изображениями на уровне чтения, создания и редактирования.
Модуль qrcode нужен нам для генерации нужных размеров QR-кода.
Объявляем класс qrHide:
Скрипт необходимо грамотно оформить с выводом каких-либо сообщений в терминал. Объявляем стандартную функцию " __init__":
Объявляем следующую немаловажную для нас функцию createQR, которая будет генерировать наш стегоконтейнер в виде QR-кода. Указываем параметры, которые будем передавать функции при ее вызове – containText и QRFileName.
Параметр containText содержит тот текст, на основе которого будет генерация QR-кода, в свою очередь параметр QRFileName отвечает за имя файла, в который будет записан QR-код.
Далее устанавливаем равенство двух внутренних переменных self.a, self.b и containText, QRFileName соответственно. Это обуславливается удобством чтения кода и переменных.
Обработка ошибок будет делаться через связку try-except:
Следующие функции предназначены для кодирования и декодирования текста в двоичный код и обратно.
Разберем подробнее функцию, которая скрывает нужный текст методом LSB в QR-коде.
Параметр textToHide - это текст, который необходимо скрыть. QRFileName - имя исходного файла с QR-кодом, outNameFile - имя конечного файла с зашифрованными данными. Открываем сгенерированное изображение QR-кода и конвертируем в градацию серого, так как изначально оно у нас было в битовом представлении. Битовое изображение - бинарное изображение, для представления и хранения которого в цифровом виде используется битовая карта, где на каждый элемент изображения (пиксель) отводится 1 бит информации. Плюсы битовых изображений в том, что они хорошо сжимаются. Минусы - для представления информации отводится только ноль и единица.
Для конвертирования изображения используем функцию convert и выберем необходимым режимом.
Декодирование происходит с помощью функции decode, которой мы передаем лишь один параметр fileToDecode;
Таким образом, мы рассмотрели один из возможных стеганографических приемов сокрытия данных методом LSB в 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-код;
- Переводим текстовую информацию для сокрытия в бинарный код из единиц и нулей;
- Зашифровываем в пикселях черного цвета информацию по вышеописанному алгоритму.
Первостепенно установим необходимую библиотеку Pillow, позволяющую работать с изображениями. Данная библиотека является форком, то есть ответвлением, оригинальной библиотеки PIL. Этот форк был принят в качестве замены оригинальной библиотеки и включён в некоторые дистрибутивы Linux по умолчанию.
Для установки необходимых зависимостей воспользуйтесь командой от имени администратора:
Bash:
pip install Pillow qrcode
Импортируем библиотеки в скрипт:
Python:
from PIL import Image, ImageDraw
import qrcode
Модуль qrcode нужен нам для генерации нужных размеров QR-кода.
Объявляем класс qrHide:
Python:
class qrHide(object):
Python:
def __init__(self):
print("Старт программы!")
Параметр containText содержит тот текст, на основе которого будет генерация QR-кода, в свою очередь параметр QRFileName отвечает за имя файла, в который будет записан QR-код.
Python:
def createQR(self, containText, QRFileName):
self.a = containText
self.b = QRFileName
Обработка ошибок будет делаться через связку try-except:
Python:
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
Python:
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))
Параметр 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-битные пиксели с плавающей точкой)
Python:
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
Python:
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;
- достаточно простой, но эффективный способ передачи скрытой информации с условием использования криптографических алгоритмов.
- невозможность применения в условиях компрессии, наличия посторонних шумов либо преобразования изображения;
- метод подвергается почти всем видам атак на стегографические алгоритмы.