Вступление
Статья частично является продолжениям идеи создания своего алгоритма шифрования на основе хеш функций из первой части. И так, продолжим. Большинство практических схем шифрования строятся вокруг проверенных стандартов вроде AES или RSA. В этом материале мы разберем альтернативный, исследовательский подход: шифрование, где для каждого байта сообщения подбирается собственное значение salt на основе SHA-256 и Blake3. Главная особенность алгоритма в том, что для шифрования используется хеш функции. Цель статьи — интуитивно и пошагово объяснить механику новой версии алгоритма, чтобы вы могли оценить его сильные и слабые стороны.
Идея алгоритма
Алгоритм обрабатывает сообщение как последовательность байтов. Для каждого байта используется текущее состояние ключа. Из ключа вычисляется хэш, а его первый байт играет роль маски XOR. Перед этим дополнительно считается 64-байтный хэш BLAKE3 от исходных данных: он подмешивается к начальному ключу и участвует в каждом шаге шифрования как контекст. Дальше происходит ключевой шаг, алгоритм подбирает число salt. Перебор продолжается, пока первый байт полученного хэша не совпадет с целевым значением, которое было рассчитано из исходного байта и маски XOR. Найденный salt и сохраняется в шифротекст. После этого ключ обновляется (снова через SHA-256), и следующий байт обрабатывается уже с новым ключом. Каждый шаг зависит от предыдущего состояния. В конце в шифротекст добавляется BLAKE3-хэш (как число), чтобы при расшифровке проверить целостность сообщения: если хэш не совпадает, данные считаются измененными.
Шифрования
Шаг 1. Из секретной фразы получаем хеш
Берем любую секретную фразу произвольной длины (лучше не меньше 16 символов) и хешируем ее через SHA-256. Результат всегда 32 байта — это базовый стартовый ключ алгоритма.
import hashlib secret_phrase = "my_very_secret_phrase_123" secret_seed = secret_phrase.encode("utf-8") initial_key = hashlib.sha256(secret_seed).digest() # 32 bytes
Шаг 2. Готовим данные для шифрования
Алгоритм работает с байтами, поэтому сообщение переводим в bytes.
message = "Hello!" message_bytes = message.encode("utf-8")
Шаг 3. Подготавливаем ключ
Перед шифрованием байтов алгоритм связывает ключ с конкретными данными сообщения:
сначала считается
BLAKE3хеш от всех исходных байтов (в реализации — 64 байта);затем этот хеш подмешивается к базовому ключу;
результат снова пропускается через
SHA-256— это рабочее стартовое состояние ключа;дальше этот же
BLAKE3хеш используется как постоянный контекст на каждом шаге.
from blake3 import blake3 HASH_SIZE_BYTES = 64 data_hash = blake3(message_bytes).digest(length=HASH_SIZE_BYTES) # 64 bytes hash_int = int.from_bytes(data_hash, byteorder="big") # для хранения в конце salts current_key = hashlib.sha256(initial_key + data_hash + b"|init-key|").digest()
Шаг 4. Шифруем один байт
Для каждого байта:
берем маску из текущего состояния (
SHA-256(current_key + data_hash + "|mask|"), первый байт);считаем
xor_target;подбираем
saltперебором, пока первый байтSHA-256(current_key + data_hash + salt + "|salt|")не совпадет сxor_target.
def encrypt_byte(single_byte: int, current_key: bytes, data_hash: bytes) -> tuple[int, bytes]: mask = hashlib.sha256(current_key + data_hash + b"|mask|").digest() mask_byte = mask[0] xor_target = single_byte ^ mask_byte salt = 0 while True: salt_bytes = salt.to_bytes(4, byteorder="big") attempt_hash = hashlib.sha256(current_key + data_hash + salt_bytes + b"|salt|").digest() if attempt_hash[0] == xor_target: break salt += 1 next_key = hashlib.sha256(current_key + data_hash + b"|next-key|").digest() return salt, next_key
Шаг 5. Шифруем все сообщение
Повторяем шаг для каждого байта. В конце добавляем BLAKE3 хеш (как int) в конец массива salts для проверки целостности.
salts = [] for b in message_bytes: salt, current_key = encrypt_byte(b, current_key, data_hash) salts.append(salt) # добавляем контроль целостности в конец шифротекста salts.append(hash_int) print("Ciphertext:", salts)
Расшифрования
Шаг 1. Извлекаем контекст и подготавливаем ключ
На расшифровке последний элемент массива — это BLAKE3 хеш сообщения (в виде числа). Преобразуем его обратно в 64 байта и подготавливаем стартовое состояние ключа так же, как на шифровании.
if not salts: raise ValueError("Ciphertext is empty") hash_int = salts[-1] salts_payload = salts[:-1] data_hash = hash_int.to_bytes(64, byteorder="big") current_key = hashlib.sha256(initial_key + data_hash + b"|init-key|").digest()
Шаг 2. Расшифровываем один байт
Перебора уже нет. Мы знаем salt, поэтому сразу восстанавливаем байт.
def decrypt_byte(salt: int, current_key: bytes, data_hash: bytes) -> tuple[int, bytes]: mask = hashlib.sha256(current_key + data_hash + b"|mask|").digest() mask_byte = mask[0] salt_bytes = salt.to_bytes(4, byteorder="big") attempt_hash = hashlib.sha256(current_key + data_hash + salt_bytes + b"|salt|").digest() xor_target = attempt_hash[0] single_byte = xor_target ^ mask_byte next_key = hashlib.sha256(current_key + data_hash + b"|next-key|").digest() return single_byte, next_key
Шаг 3. Расшифровываем все сообщение
Идем по salts_payload, восстанавливаем байты и собираем данные.
decrypted_bytes = bytearray() for salt in salts_payload: b, current_key = decrypt_byte(salt, current_key, data_hash) decrypted_bytes.append(b) decrypted_data = bytes(decrypted_bytes)
Шаг 4. Проверяем целостность сообщения
После расшифровки снова считаем BLAKE3 хеш от восстановленных данных и сравниваем с хешем из шифротекста.
check_hash = blake3(decrypted_data).digest(length=64) if check_hash != data_hash: raise ValueError("Integrity check failed: message hash mismatch")
Полный код
Перед запуском не забудьте утсановить пакет blake3 командой pip install blake3
import hashlib from blake3 import blake3 from typing import List, Tuple HASH_SIZE_BYTES = 64 def derive_initial_key(secret_phrase: str) -> bytes: secret_seed = secret_phrase.encode("utf-8") return hashlib.sha256(secret_seed).digest() def prepare_message_hash(message_bytes: bytes) -> bytes: return blake3(message_bytes).digest(length=HASH_SIZE_BYTES) def mix_initial_key(base_key: bytes, data_hash: bytes) -> bytes: return hashlib.sha256(base_key + data_hash + b"|init-key|").digest() def get_next_key(current_key: bytes, data_hash: bytes) -> bytes: return hashlib.sha256(current_key + data_hash + b"|next-key|").digest() def encrypt_byte(single_byte: int, current_key: bytes, data_hash: bytes) -> Tuple[int, bytes]: # Step A: derive mask byte mask = hashlib.sha256(current_key + data_hash + b"|mask|").digest() mask_byte = mask[0] xor_target = single_byte ^ mask_byte # Step B: brute-force salt salt = 0 while True: salt_bytes = salt.to_bytes(4, byteorder="big") attempt_hash = hashlib.sha256(current_key + data_hash + salt_bytes + b"|salt|").digest() if attempt_hash[0] == xor_target: break salt += 1 # Step C: evolve key next_key = get_next_key(current_key, data_hash) return salt, next_key def decrypt_byte(salt: int, current_key: bytes, data_hash: bytes) -> Tuple[int, bytes]: # Step A: derive mask byte mask = hashlib.sha256(current_key + data_hash + b"|mask|").digest() mask_byte = mask[0] # Step B: recover xor_target from known salt salt_bytes = salt.to_bytes(4, byteorder="big") attempt_hash = hashlib.sha256(current_key + data_hash + salt_bytes + b"|salt|").digest() xor_target = attempt_hash[0] # Step C: restore original byte single_byte = xor_target ^ mask_byte # Step D: evolve key next_key = get_next_key(current_key, data_hash) return single_byte, next_key def encrypt_message(secret_phrase: str, message: str) -> List[int]: message_bytes = message.encode("utf-8") base_key = derive_initial_key(secret_phrase) data_hash = prepare_message_hash(message_bytes) hash_int = int.from_bytes(data_hash, byteorder="big") current_key = mix_initial_key(base_key, data_hash) salts = [] for b in message_bytes: salt, current_key = encrypt_byte(b, current_key, data_hash) salts.append(salt) # Append integrity hash as last element salts.append(hash_int) return salts def decrypt_message(secret_phrase: str, salts: List[int]) -> str: if not salts: raise ValueError("Ciphertext is empty") hash_int = salts[-1] if hash_int < 0: raise ValueError("Invalid ciphertext hash value") salts_payload = salts[:-1] data_hash = hash_int.to_bytes(HASH_SIZE_BYTES, byteorder="big") base_key = derive_initial_key(secret_phrase) current_key = mix_initial_key(base_key, data_hash) decrypted_bytes = bytearray() for salt in salts_payload: b, current_key = decrypt_byte(salt, current_key, data_hash) decrypted_bytes.append(b) decrypted_data = bytes(decrypted_bytes) # Integrity check expected_hash = blake3(decrypted_data).digest(length=HASH_SIZE_BYTES) if expected_hash != data_hash: raise ValueError("Integrity check failed: message hash mismatch") return decrypted_data.decode("utf-8") if __name__ == "__main__": secret_phrase = "my_very_secret_phrase_123" message = "Hello!" print("=== ENCRYPTION ===") print(f"Source message: {message}") salts = encrypt_message(secret_phrase, message) payload_salts = salts[:-1] message_hash = salts[-1] for source_byte, salt in zip(message.encode("utf-8"), payload_salts): print(f" Byte {source_byte} encrypted with salt: {salt}") print(f" BLAKE3-64 integrity hash (as int): {message_hash}") print(f"\nCiphertext (salts): {salts}") metadata_size = sum(max(1, (x.bit_length() + 7) // 8) for x in salts) print(f"Metadata size: {metadata_size} bytes") print("\n=== DECRYPTION ===") decrypted_message = decrypt_message(secret_phrase, salts) print(f"Successfully decrypted: {decrypted_message}")
Итоги
Этот алгоритм — интересный пример того, как можно построить обратимое преобразование вокруг SHA-256 или другой хеш функции и использовать это для шифрования данных. Алгоритм конечно можно модифицировать и улучшать, здесь показана только идея и базовый принцип работы. Код можно найти на GitHub. Спасибо за внимание и комментарии.