Вступление

Статья частично является продолжениям идеи создания своего алгоритма шифрования на основе хеш функций из первой части. И так, продолжим. Большинство практических схем шифрования строятся вокруг проверенных стандартов вроде 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. Спасибо за внимание и комментарии.

Комментарии (0)