Можно ли построить шифрование, если под рукой есть только SHA-256? В этой статье разберем рабочую схему, где каждый байт кодируется через подбор salt.
Статья частично является продолжениям идеи создания своего алгоритма шифрования на основе хеш функций из первой части. И так, продолжим. Большинство практических схем шифрования строятся вокруг проверенных стандартов вроде AES или RSA. В этом материале мы разберем альтернативный, исследовательский подход: шифрование, где для каждого байта сообщения подбирается собственное значение salt на основе SHA-256 и Blake3. Главная особенность алгоритма в том, что для шифрования используется хеш функции. Цель статьи — интуитивно и пошагово объяснить механику новой версии алгоритма, чтобы вы могли оценить его сильные и слабые стороны.
Идея алгоритмаАлгоритм обрабатывает сообщение как последовательность байтов. Для каждого байта используется текущее состояние ключа. Из ключа вычисляется хэш, а его первый байт играет роль маски XOR. Перед этим дополнительно считается 64-байтный хэш BLAKE3 от исходных данных: он подмешивается к начальному ключу и участвует в каждом шаге шифрования как контекст. Дальше происходит ключевой шаг, алгоритм подбирает число salt. Перебор продолжается, пока первый байт полученного хэша не совпадет с целевым значением, которое было рассчитано из исходного байта и маски XOR. Найденный salt и сохраняется в шифротекст. После этого ключ обновляется (снова через SHA-256), и следующий байт обрабатывается уже с новым ключом. Каждый шаг зависит от предыдущего состояния. В конце в шифротекст добавляется BLAKE3-хэш (как число), чтобы при расшифровке проверить целостность сообщения: если хэш не совпадает, данные считаются измененными.
Берем любую секретную фразу произвольной длины (лучше не меньше 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. Спасибо за внимание и комментарии.