Эзотерическое кодирование
В прошлый раз мы выяснили, что стойкий пароль содержит не менее 128 бит. При записи в base_16 такой пароль займет 32, а при переводе в base_10 до 39 байт. Нас окружают цифры и при желании можно “загримировать” данные под любые бытовые записи. Но как кодировать информацию не оставляя следов на бумаге?
Порядок у карт в киоске был взят
Сколько существует комбинаций у колоды из 36 карт? Для расчёта необходимо взять факториал от числа элементов. В нашем случае 36! примерно 10^41. Это 136 бит! Больше чем требуется для хранения пароля.
Но как же кодировать его картами?
Назначим каждой карте номер, тогда их можно представить как набор чисел [1,2,(…),36]. Записав номера по порядку мы получим первую комбинацию, переставив две последние карты местами - вторую. Для генерации следующих существует алгоритм Нарайаны, но он не дает информации как получить произвольную комбинацию по её номеру.
Решение было найдено в книге “Программирование в алгоритмах / С. М. Окулов”, по мотивам которой был написан следующий код.
import math
def permute_by_index(arr_len, need_index):
in_arr = list(range(arr_len + 1))
out_arr = []
while arr_len:
arr_len -= 1
top_range = math.factorial(arr_len)
first_index = (need_index // top_range) + 1
need_index = need_index % top_range
poped = in_arr.pop(first_index)
out_arr.append(poped)
return out_arr
def index_by_permute(arr_state):
out_index = 0
for ind, one in enumerate(arr_state):
arr_part = arr_state[ind:]
cur_count = 0
for sub in arr_part:
if sub < one: cur_count += 1
var_count = math.factorial(len(arr_state) - ind - 1)
out_index += var_count * cur_count
return out_index
Не буду утомлять вас деталями алгоритма, давайте посмотрим как он работает:
# Первая комбинация для массива из трёх
assert(permute_by_index(3, 0) == [1, 2, 3])
# Вторая, напоминаю что индекс от нуля
assert(permute_by_index(3, 1) == [1, 3, 2])
# Ну и наконец шестая
assert(permute_by_index(3, 5) == [3, 2, 1])
# Обратная процедура, индекс из массива
assert(index_by_permute([3, 2, 1]) == 5)
assert(index_by_permute([1, 3, 2]) == 1)
assert(index_by_permute([1, 2, 3]) == 0)
Всё готово чтобы добавить карты! Следующий код предложит конкретным картам номер:
def get_indexed_cards():
indexed_cards = []
suits_values = ['\u2660', '\u2665', '\u2663', '\u2666']
rank_values = ['A', 'K', 'Q', 'J', '10', '9', '8', '7', '6']
for suit in suits_values:
for rank in rank_values:
indexed_cards.append(suit + rank)
return indexed_cards
def print_cards():
indexed_cards = get_indexed_cards()
for ind in range(0, 36):
number = str(ind + 1).zfill(2)
print(number, indexed_cards[ind])
print_cards() # 01 ♠A (...)
Боевое крещение
Представим пароль из прошлой части в виде набора карт:
key_int = 0x2af0f3958e106d1621658dbd7bd7653b
ind_list = permute_by_index(36, key_int)
# 1, 2, 9, 22, 34, 18, 32, 6, 14, 23, 28, 11, 36, 35, 20, 13, 24, 30,
# 19, 10, 16, 12, 8, 31, 25, 4, 27, 15, 5, 29, 33, 7, 17, 26, 21, 3
# '♠A', '♠K', '♠6', '♣J', '♦8', '♥6', '♦10', '♠9', '♥10',
# '♣10', '♦A','♥K', '♦6', '♦7', '♣K', '♥J', '♣9', '♦Q',
# '♣A', '♥A', '♥8', '♥Q', '♠7', '♦J', '♣8', '♠J', '♣6',
# '♥9', '♠10', '♦K', '♦9', '♠8', '♥7', '♣7', '♣Q', '♠Q'
Осталось его правильно декодировать:
new_key = index_by_permute(ind_list)
print(hex(new_key))
# 0x2af0f3958e106d1621658dbd7bd7653b
Заработало! Разумеется, карты это абстрактный пример. В реальности это могут быть:
- отсортированные по алфавиту книги
- разложенные по датам письма
- маркеры расположенные по цвету
- ярлыки на рабочем столе, ну и т.д
Метод подходит для создания копии критических данных, вроде биткойн кошельков и ключей от FullDiskEncryption и поддерживает более чем достоверное отрицание. Такие вещи несложно сохранить на фотографии и никто в здравом уме не станет их “декодировать”