Безпечне хешування за допомогою Python Hashlib

Ця інструкція допоможе вам оволодіти навичками генерації захищених хешів, використовуючи можливості вбудованого модуля hashlib у Python. Розуміння принципів хешування та вміння створювати безпечні хеші програмно є цінними знаннями, навіть якщо ваша робота не пов’язана безпосередньо з кібербезпекою. Чому це важливо?

Під час розробки проєктів на Python ви, ймовірно, стикнетеся із ситуаціями, коли необхідно зберігати паролі або інші конфіденційні дані в базах даних чи файлах коду. Замість зберігання інформації у відкритому вигляді, набагато безпечніше використовувати алгоритми хешування для перетворення цих даних у хеші, які і будуть зберігатися.

У цьому керівництві ми розглянемо, що таке хешування, чим воно відрізняється від шифрування та які властивості має безпечна хеш-функція. Також ми навчимося обчислювати хеші звичайного тексту за допомогою Python та вбудованого модуля hashlib.

Отже, давайте почнемо!

Що таке хешування?

Процес хешування полягає у перетворенні вхідного рядка (повідомлення) у вихідні дані фіксованої довжини, які називаються хешем. Це означає, що довжина хешу, згенерованого певним алгоритмом, завжди буде однаковою, незалежно від довжини вхідних даних. Але як хешування відрізняється від шифрування?

При шифруванні, звичайний текст перетворюється на зашифрований текст за допомогою алгоритму шифрування. Зашифрований текст можна повернути до вихідного повідомлення за допомогою алгоритму дешифрування.

Хешування працює інакше. Шифрування є оборотним процесом: ви можете перетворити зашифроване повідомлення на звичайний текст і навпаки. Натомість хешування — це незворотний процес. Неможливо відновити вихідне повідомлення з його хешу.

Властивості хеш-функцій

Розглянемо основні властивості, які повинні мати хеш-функції:

  • Детермінованість: Хеш-функції є детермінованими, тобто для одного і того ж повідомлення m, хеш завжди буде однаковим.
  • Стійкість до попереднього образу: Хешування – це незворотний процес. Властивість стійкості до попереднього образу стверджує, що з вихідного хешу неможливо відновити вихідне повідомлення m.
  • Стійкість до колізій: Повинно бути складно (практично неможливо) знайти два різних повідомлення m1 і m2, такі, щоб їхні хеші збігалися. Ця властивість називається стійкістю до колізій.
  • Стійкість до другого попереднього образу: Для заданого повідомлення m1 і відповідного хешу, неможливо знайти інше повідомлення m2 таке, що hash(m1) = hash(m2).

Модуль hashlib в Python

Вбудований модуль hashlib в Python надає реалізації різних алгоритмів хешування та дайджестів повідомлень, включаючи SHA та MD5.

Щоб використовувати функції модуля hashlib, потрібно імпортувати його у ваш код:

import hashlib

Модуль hashlib містить константи algorithms_available та algorithms_guaranteed, які показують набір алгоритмів, реалізації яких доступні та гарантовані для використання на поточній платформі. Зокрема, algorithms_guaranteed є підмножиною algorithms_available.

Відкрийте Python REPL, імпортуйте hashlib і перегляньте значення констант algorithms_available та algorithms_guaranteed:

>>> hashlib.algorithms_available
# Output
{'md5', 'md5-sha1', 'sha3_256', 'shake_128', 'sha384', 'sha512_256', 'sha512', 'md4', 
'shake_256', 'whirlpool', 'sha1', 'sha3_512', 'sha3_384', 'sha256', 'ripemd160', 'mdc2', 
'sha512_224', 'blake2s', 'blake2b', 'sha3_224', 'sm3', 'sha224'}
>>> hashlib.algorithms_guaranteed
# Output
{'md5', 'shake_256', 'sha3_256', 'shake_128', 'blake2b', 'sha3_224', 'sha3_384', 
'sha384', 'sha256', 'sha1', 'sha3_512', 'sha512', 'blake2s', 'sha224'}

Як бачимо, algorithms_guaranteed дійсно є підмножиною algorithms_available.

Як створити хеш-об’єкти в Python

Далі ми навчимося створювати хеш-об’єкти в Python. Ми обчислимо хеш SHA256 для рядка, використовуючи два методи:

  • Загальний конструктор new().
  • Спеціальні конструктори для конкретних алгоритмів.

Використання конструктора new()

Спочатку ініціалізуємо рядок:

>>> message = "techukraine.net is awesome!"

Для створення екземпляра хеш-об’єкта скористаємося конструктором new(), передавши йому назву алгоритму:

>>> sha256_hash = hashlib.new("SHA256")

Тепер викличемо метод update() для хеш-об’єкта, передавши рядок в якості аргументу:

>>> sha256_hash.update(message)

При цьому виникне помилка, тому що алгоритми хешування працюють лише з байтовими рядками.

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Unicode-objects must be encoded before hashing

Щоб отримати байтовий рядок, потрібно викликати метод encode() для рядка, а потім використати його у методі update(). Після цього можна викликати метод hexdigest() для отримання хешу SHA256:

sha256_hash.update(message.encode())
sha256_hash.hexdigest()
# Output:'b360c77de704ad8f02af963d7da9b3bb4e0da6b81fceb4c1b36723e9d6d9de3d'

Замість кодування рядка за допомогою encode(), можна визначити його як байтовий рядок, додавши префікс b:

message = b"techukraine.net is awesome!"
sha256_hash.update(message)
sha256_hash.hexdigest()
# Output: 'b360c77de704ad8f02af963d7da9b3bb4e0da6b81fceb4c1b36723e9d6d9de3d'

Отриманий хеш збігається з попереднім, що підтверджує детермінованість хеш-функцій.

Крім того, навіть невелика зміна рядка має призвести до значної зміни хешу (ефект лавини). Перевіримо це, змінивши ‘a’ на ‘A’ у слові ‘awesome’:

message = "techukraine.net is Awesome!"
h1 = hashlib.new("SHA256")
h1.update(message.encode())
h1.hexdigest()
# Output: '3c67f334cc598912dc66464f77acb71d88cfd6c8cba8e64a7b749d093c1a53ab'

Як бачимо, хеш змінився кардинально.

Використання спеціальних конструкторів

У попередньому прикладі ми використовували загальний конструктор new(). Замість цього можна використовувати спеціальний конструктор sha256():

sha256_hash = hashlib.sha256()
message= "techukraine.net is awesome!"
sha256_hash.update(message.encode())
sha256_hash.hexdigest()
# Output: 'b360c77de704ad8f02af963d7da9b3bb4e0da6b81fceb4c1b36723e9d6d9de3d'

Отриманий хеш такий самий, як і раніше.

Атрибути хеш-об’єктів

Хеш-об’єкти мають ряд корисних атрибутів:

  • digest_size – розмір дайджесту в байтах. Наприклад, SHA256 повертає 256-бітовий хеш, що еквівалентно 32 байтам.
  • block_size – розмір блоку, який використовується в алгоритмі хешування.
  • name – назва алгоритму, яку можна використовувати в конструкторі new().

Перевіримо ці атрибути для об’єкта sha256_hash, який ми створили раніше:

>>> sha256_hash.digest_size
32
>>> sha256_hash.block_size
64
>>> sha256_hash.name
'sha256'

Далі розглянемо приклади практичного застосування хешування.

Практичні застосування хешування

Перевірка цілісності програмного забезпечення та файлів

Розробники постійно завантажують та встановлюють програмні пакети. Деякі дзеркала можуть бути небезпечними. Тому, щоб переконатися у цілісності завантаженого програмного забезпечення, можна порівняти хеш завантаженого файлу з офіційним хешем.

Це також можна використовувати для файлів на вашому комп’ютері. Навіть невелика зміна у файлі призведе до зміни хешу. Перевіряючи хеш, можна виявити зміни у файлі.

Створимо текстовий файл “my_file.txt” та додамо до нього текст:

$ cat my_file.txt
This is a sample text file.
We are  going to compute the SHA256 hash of this text file and also
check if the file has been modified by
recomputing the hash.

Відкриємо файл у двійковому режимі читання (‘rb’), прочитаємо вміст та обчислимо хеш SHA256:

>>> import hashlib
>>> with open("my_file.txt","rb") as file:
...     file_contents = file.read()
...     sha256_hash = hashlib.sha256()
...     sha256_hash.update(file_contents)
...     original_hash = sha256_hash.hexdigest()

Змінна original_hash містить хеш файлу “my_file.txt” у поточному стані.

>>> original_hash
# Output: '53bfd0551dc06c4515069d1f0dc715d002d451c8799add29f3e5b7328fda9f8f'

Тепер змінимо файл “my_file.txt”, наприклад видаливши зайві пробіли на початку рядка. Знову обчислимо хеш та збережемо його у змінній computed_hash.

>>> import hashlib
>>> with open("my_file.txt","rb") as file:
...     file_contents = file.read()
...     sha256_hash = hashlib.sha256()
...     sha256_hash.update(file_contents)
...     computed_hash = sha256_hash.hexdigest()

Тепер перевіримо, чи computed_hash дорівнює original_hash за допомогою assert:

>>> assert computed_hash == original_hash

Якщо файл змінено (як у цьому випадку), ви отримаєте AssertionError:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AssertionError

Хешування також можна використовувати для зберігання конфіденційної інформації, такої як паролі, у базах даних. Порівнюючи хеш введеного пароля з хешем, збереженим у базі даних, можна аутентифікувати користувача, не зберігаючи паролі у відкритому вигляді.

Висновок

Сподіваюся, ця інструкція допомогла вам навчитися створювати безпечні хеші за допомогою Python. Ось основні висновки:

  • Модуль hashlib надає готові реалізації алгоритмів хешування. Переглянути список доступних алгоритмів можна за допомогою hashlib.algorithms_guaranteed.
  • Для створення хеш-об’єкта можна використовувати загальний конструктор new(): hashlib.new("algo-name"). Або ж можна скористатися спеціальним конструктором, наприклад hashlib.sha256().
  • Після ініціалізації рядка та хеш-об’єкта, потрібно викликати метод update(), а потім метод hexdigest() для отримання хешу.
  • Хешування можна використовувати для перевірки цілісності файлів, зберігання конфіденційних даних, наприклад, паролів, та аутентифікації користувачів.

Далі ви можете дізнатися, як створити генератор випадкових паролів на Python.