ВАЖНАЯ ИНФОРМАЦИЯ Привет, читатель. Статья больше про исследование и тестирование метода, я не пытаюсь навязать все нижеописанное и не заявляю что это 100% рабочее шифрование. Но как по мне идея шифрования через обобщение нейросетей достойна внимания. Обзор метода. Само сообщение шифруется через симметричное шифрование AES-CBC, где нужен ключ (16 байт) и IV (начальный вектор, чтобы шифр был разным). Ключ генерируется из эмбеддингов, полученных моделью. Но особенность метода в том, что изображение не является секретом, любой желающий может увидеть изображение и зашифрованное сообщение. Но при этом изображение не является публичным ключом. Ключ может получить только конкретная модель нейросети. Поэтому чтобы это работало, yolo-seg должен быть обучен на своем датасете. Одна и та же модель должна быть у обоих участников. Веса модели и датасет должны храниться в строгом секрете. Любой человек в здравом уме скажет, что yolo не сможет уникально сегментировать изображения и на этом моменте закроет статью. Эмбеддинги из 4 и 16 слоя это 192 числа, типа float32, обычно в диапазоне от -10 до 10. Самый важный момент, который позволяет создать уникальный ключ для каждого изображения. По крайней мере это позволяет учитывать малейшие изменения в изображении. Учитывается все изображение, сам класс и его фон. Даже если меняется всего 1 пиксель, создается уникальный ключ. Провел тест из 1000 одинаковых изображений где рандомно заменены 1-5 пикселей. Результат в статье. На 2 разных системах всегда генерируются одинаковые ключи к одним и тем же изображениям. Самое важное условие чтобы на изображении был класс, который может быть сегментирован, иначе ключа просто не будет и сообщение нельзя будет отправить. Чтобы расшифровать сообщение нужно получить изображение, на основе которого был получен ключ и само зашифрованное сообщение. Через такую же модель yolo-seg получаются эмбеддинги чтобы повторить ключ. Эмбеддинги yolo11n-seg. В основе метода лежит использование эмбеддингов, полученных из модели YOLO11n-seg, для генерации ключа из изображения. Эмбеддинги представляют собой числовое описание изображения, которое позволяет учесть как общие характеристики (например, объект на изображении), так и мелкие детали (изменение одного пикселя). В этом разделе я подробно объясню, что такое эмбеддинги, как они формируются в модели YOLO11n-seg, почему выбраны слои 4 и 16, как обеспечивается их чувствительность к изменениям изображения и как они используются для что такое Эмбеддинги Эмбеддинги — это компактное числовое представление изображения, которое извлекается из внутренних слоёв нейронной сети. В контексте метода они являются результатом обработки изображения моделью yolo11n-seg и представляют собой массив из 192 чисел с плавающей запятой (float32). Эти числа описывают различные аспекты изображения: объект (например, кот), фон, текстуры, края и даже отдельные пиксели. Каждое число отражает вклад определённой особенности изображения, выявленной нейросетью. Но самое важное что каждое число имеет свой диапазон влияния. И этот уровень влияния определяется датасетом, обучением, разметкой, пайлпайном обучения и инференса. В моём методе эмбеддинги состоят из двух частей: 64 числа из 16 слоя, Описывает более крупные структуры, такие как форма класса, его цвет. Диапазон значений чисел обычно находится в пределах от -10 до 10, хотя в зависимости от изображения и характеристик модели значения могут быть больше или меньше. Например, типичный фрагмент эмбеддингов для изображения кота может выглядеть следующим образом: так [0.1234, -0.5678, 1.2345, -0.0987, 2.3456, 0.6789, -1.5432, 0.4321, ...] Если изменить один пиксель изображения, например, с [100, 100, 100] на [101, 100, 100], некоторые числа в эмбеддингах слегка изменятся: так [0.1235, -0.5678, 1.2344, -0.0987, 2.3457, 0.6789, -1.5431, 0.4321, ...] Эти небольшие изменения играют ключевую роль: они обеспечивают чувствительность метода к минимальным различиям в изображении. Это позволяет генерировать уникальный ключ для каждой версии изображения. Формирование эмбеддингов. YOLO11n-seg изначально нейронная сеть, предназначенная для обнаружения и сегментации объектов. Она состоит из 23 слоёв, каждый из которых обрабатывает изображение на разном уровне абстракции. В моём методе я использую не итоговые маски сегментации, а промежуточные активации двух слоёв — 4 и 16, которые извлекаются во время обработки изображения. Сперва загружается изображение. Изображение (maryJane.png размером 1088x1920 пикселей) загружается и преобразуется в тензор с тремя каналами (RGB). Тензор имеет размер [1, 3, 1088, 1920], где: 128 чисел из 4 слоя. Отвечает за мелкие детали изображения, фон 1 количество изображений, 3 каналы цвета, 1088 потому что 1080 не кратно 32, а самое близкое число 1088. Это связано с архитектурой модели. Обработка через yolo11n-seg. Модель прогоняет тензор через свои слои. Каждый слой выполняет операции свёртки, нормализации и активации, создавая всё более абстрактные представления изображения. На выходе слоя 4 получается тензор размером [1, 128, 34, 60], где: 128 — количество каналов, 34x60 — уменьшенное пространственное разрешение изображения. На выходе слоя 16 — тензор [1, 64, 17, 30], где: 64 — каналы, 17x30 — ещё меньшее разрешение. Извлечение активаций. Я использую механизм хуков (register_forward_hook) для получения активаций слоёв 4 и 16 во время обработки изображения. Это позволяет заглянуть внутрь модели и извлечь промежуточные данные, которые более уникальны чем итоговый результат сегментации. код для извлечения activations = {} layers[4].register_forward_hook(lambda m, i, o: activations.update({"layer_4": o})) layers[16].register_forward_hook(lambda m, i, o: activations.update({"layer_16": o})) with torch.no_grad(): model(image, conf=0.4, imgsz=(1088, 1920)) Усреднение активаций.Чтобы получить массив из 192 чисел (128 + 64), активации каждого слоя усредняются по пространственным измерениям (высоте и ширине). Для слоя 4: тензор [1, 128, 34, 60] → массив [128] Усреднение по 34x60, Для слоя 16: тензор [1, 64, 17, 30] → массив [64] Усреднение по 17x30 код embeddings = [activations[k].mean(dim=[2, 3]) for k in ["layer_4", "layer_16"]] Округление.На разных системах результат может отличаться. Вычисления на GPU и CPU дают разные результаты. Поэтому они округляются до 4 знаков после запятой. Это по прежнему сохраняет устойчивость к изменению даже 1 пикселя. код ыыыыыы flat_embeddings = np.round(torch.cat(embeddings).cpu().numpy(), decimals=4) Почему выбраны слои 4 и 16?Выбор слоёв 4 и 16 не случаен. Слой 4: Ранний слой модели, который обрабатывает изображение на низком уровне. Он чувствителен к мелким деталям, таким как отдельные пиксели, края, текстуры и локальные цветовые переходы. Активации слоя 4 содержат 128 каналов, что позволяет уловить широкий спектр низкоуровневых признаков. Слой 16: Более глубокий слой, который анализирует изображение на высоком уровне. Он распознаёт крупные структуры, такие как форма объекта (например, кота), его поза и общий контекст сцены. Активации слоя 16 содержат 64 канала, что достаточно для описания объектов, но менее детализировано, чем слой 4. Возможно стоит попробовать комбинации других слоев, но с этими все в порядке пока что. Меня результат удовлетворил. Чувствительность YOLO11n-seg.Изменение одного пикселя, например с [100, 100, 100] на [101, 100, 100]) влияет на активации слоя 4, потому что он анализирует локальные детали. Это изменение распространяется на тензор [1, 128, 34, 60], слегка сдвигая некоторые значения. Слой 16 менее чувствителен к отдельным пикселям, но может уловить изменения, если они влияют на форму объекта или контекст После усреднения сдвиг в активациях приводит к изменению одного или нескольких чисел в эмбеддингах. Работа с SHA-256. Эмбеддинги (192 числа) преобразуются в байты и хешируются с помощью SHA-256. SHA-256 чувствителен к входным данным, изменение одного числа в эмбеддингах на 0.0001 приводит к совершенно другому хешу. Это гарантирует, что ключ и IV для каждой версии изображения будут уникальными. код hash_digest = hashlib.sha256(flat_embeddings.tobytes()).digest() key = hash_digest[:16] # Ключ (16 байт) iv = hash_digest[16:32] # IV (16 байт) Пример. Исходное изображение, эмбеддинги [0.1234, -0.5678, 1.2345, ...]. Ключ c87bce1c05dd27f6f9c256399c43aade. Изображение с изменённым пикселем, эмбеддинги [0.1235, -0.5678, 1.2344, ...]. Ключ 1279567999291717328cd268d7bde881. Тестирование уникальности ключа. Как обсуждалось выше, ключевая проблема, уникальность ключей для каждого изображения. Чтобы проверить метод я сгенерировал 1000 копий изображений своей кошки, заменив на каждом по 1-5 пикселей рандомно, RGB-значения выбирались случайно. код для создания 1000 копий ориг фото random.randint(0, 255), random.randint(0, 255), random.randint(0, 255) ) elif modified_img.mode == 'RGBA': new_color = ( random.randint(0, 255), random.randint(0, 255), random.randint(0, 255), pixels[x, y][3] # Сохраняем альфа-канал ) else: # Для других режимов (например, grayscale) преобразуем в RGB modified_img = modified_img.convert('RGB') pixels = modified_img.load() new_color = ( random.randint(0, 255), random.randint(0, 255), random.randint(0, 255) ) # Изменяем пиксель pixels[x, y] = new_color # Сохраняем результат base_name = os.path.splitext(os.path.basename(image_path))[0] output_path = os.path.join(output_dir, f"{base_name}_mod_{i+1}.{img_format.lower()}") modified_img.save(output_path, format=img_format) print(f"Создано: {output_path} (изменено {num_pixels_to_change} пикселей)") if __name__ == "__main__": input_image = "images/maryJane.png" # Путь к исходному изображению output_directory = "new_images" # Папка для сохранения результатов num_iterations = 1000 # Количество изображений для генерации modify_random_pixels(input_image, output_directory, num_iterations) Как только изображения сгенерировались, запускаю код который симулирует работу шифрования, а в конце сравнивает результаты. Если хотя бы 1 раз результаты повторятся, метод не рабочий. Можно попробовать учесть эмбеддинги из еще одного слоя, но пока нет нужды делать этого не нужно. Мы все прекрасно понимаем, что метод и так не рабочий, рано или поздно копии ключей случатся, но ведь интересно когда это случится. По большей части это обусловлено обучением и датасетом, чем больше и разнообразнее тем лучше результат в плане уникальности в теории. Но есть еще очень большое количество параметров, которые могут влиять на это. Размер обучаемой модели. Разнообразие датасета и общее количество изображений. Качество разметки. Пайплайн обучения. Пайплайн инференса. код для проверки уникальности print("\n=== Результаты теста ===") print(f"Обработано изображений: {len(results)}") print(f"Уникальных ключей: {len(key_to_images)}") duplicates_found = False for key, images in key_to_images.items(): if len(images) > 1: duplicates_found = True print(f"\nОШИБКА: Одинаковый ключ найден для {len(images)} изображений!") print(f"Ключ: {key}") print("Изображения:") for img in images: print(f" - {img}") if not duplicates_found: print("\nУСПЕХ: Все ключи уникальны! Метод прошёл тест.") else: print("\nПРОВАЛ: Найдены одинаковые ключи. Метод недостаточно чувствителен!") # Выводим все результаты print("\nПодробные результаты:") for result in results: print(f"Изображение: {result['image']}") print(f" Ключ: {result['key']}") print(f" IV: {result['iv']}") print(f" Шифротекст: {result['ciphertext']}\n") return results, duplicates_found # Основной код device = torch.device("cpu") model = YOLO("yolo11n-seg.pt").to(device) image_folder = "modified_images" # Папка с 1000 изображениями message = "doza_pidor".encode() delay = 0.5 # Задержка в секундах results, duplicates_found = test_images(image_folder, model, message, delay) Тестирование пройдено успешно, 0 копий. В следующем попробую собрать просто разные, но похожие изображения, без замены пикселей. Для меня такой результат говорит о том что метод имеет место быть или как минимум еще одно тестирование можно провести. Про безопасность. В своей статье я опираюсь на результаты предобученной модели yolo11n-seg. В боевых условия учить ее нужно самому, на кастомном датасете, объяснять думаю не нужно. Датасет частично должен быть собран в реальной жизни буквально. Можно шифроваться на чем угодно. Какие самые популярные изображения в сети ? Голые девки и кошки. Уловили ? Идете на улице и делаете фотографии котов Затем мешаете с фотографиями из сети, желательно так чтобы зацепить в датасет как можно больше пород. Понимаю, что это та еще задница, но метод считается "околобезопасным" только при таких условиях. Если размечать лень берите самую маленькую модель и делайте сегментацию на 500 изображениях. Тут можно сделать разметку намеренно криво, в теории признаки должны стать более непредсказуемыми. А чтобы модель сегментировала все изображения, то нужно выкрутить conf=0.2, так появится неуверенная, кривая, непредсказуемая сегментация. Даже если второе тестирование будет тоже успешным и получится доказать решение проблем с коллизией окончательно. То это не отменяет того факта, что если атакующий получит доступ к модели, то вся переписка тут же будет расшифрована. Поэтому ему нужна не только модель, но и пайплайн инференса и вообще код в целом. Иначе взлом шифра займет какое то время. Откуда атакующий узнает про 4 и 16 слой ? Из этой статьи, но это уже другая история. Реализация. В предыдущих разделах я описал, как эмбеддинги, полученные из модели yolo11n-seg, используются для генерации ключа. Теперь я опишу полный код шифрования и расшифровки, а затем подробно объясню, как он работает, опираясь на принципы, описанные ранее. Код показывает, как изображение превращается в ключ для шифрования сообщения через AES-CBC, и как то же изображение используется для расшифровки. Шифрование. код import cv2 import numpy as np import torch from ultralytics import YOLO from Crypto.Cipher import AES from Crypto.Util.Padding import pad import hashlib # Настройка детерминизма torch.backends.cudnn.deterministic = True torch.backends.cudnn.benchmark = False torch.use_deterministic_algorithms(True) def generate_key_and_iv(embeddings): flat_embeddings = torch.cat([e.flatten() for e in embeddings]).cpu().numpy() flat_embeddings = np.round(flat_embeddings, decimals=4) hash_digest = hashlib.sha256(flat_embeddings.tobytes()).digest() return hash_digest[:16], hash_digest[16:32] def encrypt_data(data, key, iv): cipher = AES.new(key, AES.MODE_CBC, iv=iv) ct_bytes = cipher.encrypt(pad(data, AES.block_size)) return ct_bytes def process_image(image_path, model): image = cv2.imread(image_path) if image is None: raise ValueError("Не удалось загрузить изображение!") original_shape = image.shape[:2] yolo_size = (max(32, (original_shape[0] + 31) // 32 * 32), max(32, (original_shape[1] + 31) // 32 * 32)) image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB).astype(np.uint8) image_tensor = torch.from_numpy(image_rgb).permute(2, 0, 1).float() / 255.0 image_tensor = image_tensor.clamp(0.0, 1.0).unsqueeze(0).to(model.device) activations = {} def get_activation(name): def hook(module, input, output): activations[name] = output return hook layers = model.model.model layers[4].register_forward_hook(get_activation("layer_4")) layers[16].register_forward_hook(get_activation("layer_16")) with torch.no_grad(): results = model(image, conf=0.4, imgsz=yolo_size) embeddings = [] for layer_name in ["layer_4", "layer_16"]: activation = activations.get(layer_name, torch.zeros(1)) embedding = activation.mean(dim=[2, 3]) if len(activation.shape) == 4 else torch.zeros(1) embeddings.append(embedding) return embeddings # Шифрование device = torch.device("cpu") model = YOLO("yolo11n-seg.pt").to(device) image_path = "images/maryJane.png" message = "doza_pidor".encode() embeddings = process_image(image_path, model) key, iv = generate_key_and_iv(embeddings) ct_bytes = encrypt_data(message, key, iv) # Сохранение шифротекста и IV with open("ciphertext.bin", "wb") as f: f.write(iv + ct_bytes) # IV (16 байт) + шифротекст print("Ключ:", key.hex()) print("IV:", iv.hex()) print("Шифротекст:", ct_bytes.hex()) Код реализует метод шифрования, описанный в статье, и состоит из трёх ключевых функций: Обработка изображения (process_image), Генерация ключа и IV (generate_key_and_iv), Шифрование и расшифровка (encrypt_data, decrypt_data). Я разберу каждую часть, связав её с предыдущими разделами. Настройка детерминизма. В начале кода включается детерминизм для PyTorch, чтобы эмбеддинги были одинаковыми на разных системах. torch.backends.cudnn.deterministic = True torch.backends.cudnn.benchmark = False torch.use_deterministic_algorithms(True) Как писал ранее в разделе про эмбеддинги, это устраняет вычислительные различия между CPU и GPU, обеспечивая одинаковые ключи для одного изображения. Без детерминизма эмбеддинги могут слегка отличаться, что приведёт к неверному ключу при расшифровке. Обработка изображения (process_image) Функция process_image загружает изображение и извлекает эмбеддинги из слоёв 4 и 16 модели YOLO11n-seg. Загрузка. Изображение (maryJane.png, 1088x1920) читается с помощью OpenCV и преобразуется в тензор [1, 3, 1088, 1920]. Размер 1088 кратен 32, так требует архитектура yolo. Извлечение активаций. Хуки (register_forward_hook) захватывают активации слоёв 4 и 16 ыыы я еблан layers[4].register_forward_hook(get_activation("layer_4")) layers[16].register_forward_hook(get_activation("layer_16")) На выходе. Слой 4 - тензор [1, 128, 34, 60], слой 16 - [1, 64, 17, 30]. Усреднение. Активации усредняются по высоте и ширине, давая 128 чисел (слой 4) и 64 числа (слой 16). В итоге 192 числа (float32, диапазон -10 до 10). Функция возвращает эмбеддинги, которые затем используются для генерации ключа. Генерация ключа и IV (generate_key_and_iv) Функция преобразует эмбеддинги в ключ (16 байт) и IV (16 байт) для AES-CBC. я усталь def generate_key_and_iv(embeddings): flat_embeddings = torch.cat([e.flatten() for e in embeddings]).cpu().numpy() flat_embeddings = np.round(flat_embeddings, decimals=4) hash_digest = hashlib.sha256(flat_embeddings.tobytes()).digest() return hash_digest[:16], hash_digest[16:32] Объединение. Эмбеддинги (128 + 64 числа) объединяются в массив из 192 чисел. Округление. Числа округляются до 4 знаков (decimals=4), чтобы устранить шум, сохраняя чувствительность к пикселям. Еще я это делал для того чтобы запустить на разных системах. Хеширование. Массив преобразуется в байты и хешируется через SHA-256, давая 32 байта. Первые 16 байт — ключ, следующие 16 — IV. SHA-256 обеспечивает уникальность хэша. Изменение одного пикселя меняет эмбеддинги. Шифрование (encrypt_data) Функция шифрует сообщение через AES-CBC. погавкайте в лс пж def encrypt_data(data, key, iv): cipher = AES.new(key, AES.MODE_CBC, iv=iv) ct_bytes = cipher.encrypt(pad(data, AES.block_size)) return ct_bytes Вход. Сообщение ("doza_pidor".encode()), ключ и IV. Паддинг. Сообщение дополняется до кратности 16 байт. Шифрование. AES-CBC шифрует сообщение, используя ключ и IV Выход. "9c901f2b17cd0a91a9ef3995310db530" Используется то же самое изображение, модель ИИ и зашифрованное сообщение. Расшифровка. import cv2 import numpy as np import torch from ultralytics import YOLO from Crypto.Cipher import AES from Crypto.Util.Padding import unpad import hashlib # Настройка детерминизма torch.backends.cudnn.deterministic = True torch.backends.cudnn.benchmark = False torch.use_deterministic_algorithms(True) def generate_key_and_iv(embeddings): flat_embeddings = torch.cat([e.flatten() for e in embeddings]).cpu().numpy() flat_embeddings = np.round(flat_embeddings, decimals=4) hash_digest = hashlib.sha256(flat_embeddings.tobytes()).digest() return hash_digest[:16], hash_digest[16:32] def decrypt_data(ct_bytes, key, iv): cipher = AES.new(key, AES.MODE_CBC, iv=iv) pt_bytes = unpad(cipher.decrypt(ct_bytes), AES.block_size) return pt_bytes def process_image(image_path, model): image = cv2.imread(image_path) if image is None: raise ValueError("Не удалось загрузить изображение!") original_shape = image.shape[:2] height, width = original_shape yolo_size = (max(32, (height + 31) // 32 * 32), max(32, (width + 31) // 32 * 32)) image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB).astype(np.uint8) image_tensor = torch.from_numpy(image_rgb).permute(2, 0, 1).float() / 255.0 image_tensor = image_tensor.clamp(0.0, 1.0).unsqueeze(0).to(model.device) activations = {} def get_activation(name): def hook(module, input, output): activations[name] = output return hook layers = model.model.model layers[4].register_forward_hook(get_activation("layer_4")) layers[16].register_forward_hook(get_activation("layer_16")) with torch.no_grad(): results = model(image, conf=0.4, imgsz=yolo_size) embeddings = [] for layer_name in ["layer_4", "layer_16"]: activation = activations.get(layer_name, torch.zeros(1)) if len(activation.shape) == 4: embedding = activation.mean(dim=[2, 3]) else: embedding = torch.zeros(1) embeddings.append(embedding) return embeddings # Основной код расшифровки device = torch.device("cpu") model = YOLO("yolo11n-seg.pt").to(device) image_path = "images/mary1_1_modified_10.png" ct_bytes = bytes.fromhex("9c901f2b17cd0a91a9ef3995310db530") # Замените на реальное значение # Расшифровка embeddings = process_image(image_path, model) key, iv = generate_key_and_iv(embeddings) decrypted_message = decrypt_data(ct_bytes, key, iv) print("Расшифрованное сообщение:", decrypted_message.decode()) Настройка детерминизмаКак и в шифровании, код начинается с настройки детерминизма PyTorch. код torch.backends.cudnn.deterministic = True torch.backends.cudnn.benchmark = False torch.use_deterministic_algorithms(True) кто вообще эти статьи нахуй читает Это гарантирует, что эмбеддинги, полученные из изображения (maryJane.png), будут такими же, как при шифровании. Без детерминизма даже незначительные вычислительные различия при вычислениях на CPU и GPU дадут разные эмбеддинги, что приведёт к неверному ключу и провалу расшифровки. Обработка изображения (process_image) Функция process_image идентична той, что используется в шифровании. Она загружает изображение и извлекает эмбеддинги из слоёв 4 и 16 модели. кыд layers[4].register_forward_hook(get_activation("layer_4")) layers[16].register_forward_hook(get_activation("layer_16")) Функция возвращает эмбеддинги, которые должны совпадать с теми, что использовались при шифровании, если изображение не изменилось. Генерация ключа и IV (generate_key_and_iv) Функция идентична шифрованию. спс я def generate_key_and_iv(embeddings): flat_embeddings = torch.cat([e.flatten() for e in embeddings]).cpu().numpy() flat_embeddings = np.round(flat_embeddings, decimals=4) hash_digest = hashlib.sha256(flat_embeddings.tobytes()).digest() return hash_digest[:16], hash_digest[16:32] Расшифровка (decrypt_data) Функция восстанавливает сообщение через AES-CBC. кнчл def decrypt_data(ct_bytes, key, iv): cipher = AES.new(key, AES.MODE_CBC, iv=iv) pt_bytes = unpad(cipher.decrypt(ct_bytes), AES.block_size) return pt_bytes Вход. Зашифрованное сообщение (например, 9c901f2b17cd0a91a9ef3995310db530), ключ и IV. Расшифровка. AES-CBC декодирует данные, используя ключ и IV, полученные от эмбеддингов. Удаление паддинга. Функция unpad убирает дополнение, добавленное при шифровании. Выход. Исходное сообщение. "doza_pidor"
Blya какой же ты еблан на дозе. Ты даже скопировать статьи нормально не можешь я хуею. В следуюший раз просто копируй не думая чтобы материал распространялся в нормальном виде.