Опубліковано: · Оновлено: · автор: kodewright
Кодування, шифрування і хешування: у чому різниця
Кодування, шифрування і хешування: хто може їх обернути, чому Base64 нічого не приховує, реальні випадки, коли його сприймали як захист, і що коли обирати.

Кодування, шифрування і хешування відрізняються одним: хто може обернути результат. Кодування (Base64, URL-кодування, UTF-8) переводить дані в інший формат, і повернути їх назад може будь-хто, бо жодного секрету тут немає. Шифрування (AES, ChaCha20) обертає лише той, у кого є ключ. Хешування (SHA-256, Argon2id) не обертається взагалі: з хешем можна лише обчислити його знову для здогадки й порівняти. Вивід Base64 виглядає як абракадабра, і саме тому його раз у раз видають за захист.
Кодування, шифрування і хешування поруч
| Кодування | Шифрування | Хешування | |
|---|---|---|---|
| Хто може обернути | Будь-хто | Лише власник ключа | Ніхто; можна лише вгадувати й порівнювати |
| Чи є секрет | Немає | Ключ | Немає (HMAC додає ключ, хеші паролів додають сіль) |
| Розмір результату | Росте разом із вхідними даними (Base64: +33%) | Приблизно як вхідні дані, плюс nonce і тег | Фіксований (SHA-256: 32 байти) |
| Задача | Передати байти каналом без змін | Зберегти дані конфіденційними | Перевіряти дані чи паролі, не зберігаючи їх |
| Приклади | Base64, percent-encoding, UTF-8 | AES-256-GCM, ChaCha20-Poly1305, RSA-OAEP | SHA-256, HMAC-SHA256, bcrypt, Argon2id |
Ті самі вхідні дані через кожне з трьох:
"secret" --Base64-----> c2VjcmV0 anyone: decode it
"secret" --AES-256-GCM-> <random-looking bytes> key holder: decrypt it
"secret" --SHA-256----> 2bb80d537b1da3e3…527a25b nobody: hash guesses and compare
У першому рядку декодувати може будь-хто, у другому розшифрувати може лише власник ключа, у третьому не може ніхто, лишається тільки хешувати здогадки й порівнювати. Якщо не певні, що саме робить шматок коду, поставте одне запитання: чи може людина без секрету отримати оригінал? Якщо так, це кодування, хоч би як називалася змінна.
Кодування: зміна формату, яку може скасувати будь-хто
Base64 (RFC 4648) бере по 3 байти й записує їх 4 символами з алфавіту на 64 знаки: A–Z, a–z, 0–9, + і /, а = слугує доповненням. URL-безпечний варіант замінює + і / на - і _. Це весь алгоритм, тож secret перетворюється на c2VjcmV0 і так само легко повертається назад. Текст спершу стає байтами, тому é кодується з двох байтів UTF-8 і виходить w6k=. Для кирилиці так само: кожна літера в UTF-8 займає два байти.
Кодування існують, бо в каналів є правила. Пошту створювали для 7-бітного тексту, тому MIME (RFC 2045) передає бінарні вкладення в Base64. В URL зарезервовані символи на кшталт ?, &, # і пробілу, тож значення всередині них кодуються відсотковим кодуванням за RFC 3986 (%20 для пробілу). У JSON немає бінарного типу, тож байти подорожують як рядки Base64. Мета щоразу одна: дані мають дійти без змін, а схема навмисно публічна, щоб інша сторона могла її декодувати.
Перевірити все це можна в Base64 кодувальнику / декодувальнику або в URL кодувальнику / декодувальнику. Обидва працюють повністю у вашому браузері, тож усе, що ви вставляєте, лишається на вашій машині.
Де Base64 плутають із захистом
Ця плутанина трапляється не лише в коді початківців. Кілька місць, де Base64 стоїть просто поруч із секретами:
- Kubernetes Secrets: поле
dataу Secret містить значення в Base64, а документація Kubernetes каже, що за замовчуванням Secrets зберігаються в etcd незашифрованими. Будь-хто, хто може прочитати об'єкт Secret або дані etcd, їх декодує. Захищають їх шифрування at rest і RBAC, а не кодування. - HTTP Basic Auth: заголовок
Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==з RFC 7617 — це простоAladdin:open sesameу Base64. Сам RFC називає це відкритим текстом і каже, що без чогось на кшталт TLS цей спосіб використовувати не слід. - JWT: заголовок і payload підписаного токена — це JSON у Base64url. Кожен, у кого є токен, читає всі claims; підпис лише доводить, що їх ніхто не змінив.
- «Обфусковані» ключі в конфігах і збірках застосунків: API-ключ, записаний у Base64 у файлі конфігурації чи в мобільному застосунку, — це той самий API-ключ, до якого лишилося одне декодування.
Та сама помилка на рівні коду:
// Not protection: anyone with read access to the column gets the password back
user.password = Buffer.from(password).toString('base64')
// Password storage: one-way, salted, deliberately slow
user.passwordHash = await argon2.hash(password, { type: argon2.argon2id })
Перший варіант нічого не захищає: кожен, хто може прочитати колонку, отримує пароль назад. Другий — одностороннє, засолене й навмисно повільне хешування.
Шифрування: обертається лише з ключем
Шифрування бере відкритий текст і ключ і видає шифротекст, який без цього ключа практично неможливо обернути. Самі алгоритми публічні (AES — опублікований стандарт із 2001 року); уся стійкість зосереджена в ключі.
- Симетричне: один ключ і шифрує, і розшифровує. Використовуйте автентифікований режим, AES-256-GCM або ChaCha20-Poly1305, який заодно виявляє підробку. Звичайний AES-CBC без MAC цього не робить, і саме з цієї прогалини виростають атаки padding oracle.
- Асиметричне: відкритий ключ шифрує або перевіряє, приватний розшифровує або підписує. На практиці воно здебільшого захищає симетричний ключ (обмін ключами в TLS,
age, PGP), а не самі дані, бо працює повільно і має обмеження на розмір.
Ключ рівно настільки стійкий, наскільки стійкий спосіб, яким його створили. 256-бітний ключ AES, отриманий із 40-бітного пароля, — це 40-бітний ключ, бо зловмисник вгадує пароль, а не ключ. Ключі мають братися безпосередньо з CSPRNG (криптографічно стійкого генератора псевдовипадкових чисел); генератор секретних ключів заповнює їх через crypto.getRandomValues(). Ключ, зібраний на Math.random(), походив би з генератора, чий внутрішній стан відновлюється з кількох значень, і 256 біт перетворилися б на передбачувану послідовність.
Хешування: одностороннє, і швидкість визначає, для чого воно
Хеш-функція відображає будь-які вхідні дані в дайджест фіксованого розміру. Ключа немає, кроку декодування немає; вхідні дані втрачено. Щоб звірити щось із хешем, його хешують знову й порівнюють.
Тому хешування підходить для двох різних задач, і вони потребують протилежної швидкості. Перевірка цілісності (завантажені файли, об'єкти Git, HMAC на запитах до API) хоче швидкий хеш. Зберігання паролів хоче повільний, бо злиту таблицю хешів атакують перебором, і швидкість хешу задає швидкість перебору. Виміри на одній RTX 4090 у hashcat 6.2.6:
| Алгоритм | Спроб на секунду, одна RTX 4090 | Для чого підходить |
|---|---|---|
| MD5 | ~164 млрд | Лише контрольні суми без вимог безпеки |
| SHA-256 | ~22 млрд | Цілісність, підписи, HMAC; не паролі |
| bcrypt, cost 10 | ~5 750 | Паролі |
| Argon2id | Залежить від налаштувань пам'яті й часу | Паролі; кожна спроба коштує зловмиснику ще й налаштований обсяг пам'яті |
Генератор хешів обчислює дайджести MD5, SHA-1, SHA-256 і SHA-512 та HMAC; Генератор bcrypt показує, як хеш пароля додає сіль і фактор вартості. Як правильно зберігати паролі, описано в гайді про сіль і хешування, а чому швидкі хеші для цього не годяться, у статті що таке bcrypt і чому MD5 мертвий.
Що коли використовувати
| Вам треба | Використовуйте | Чому |
|---|---|---|
| Покласти бінарні дані в JSON, data URI чи лист | Base64 | Канал передає лише текст |
| Вставити довільне значення в URL | Відсоткове кодування | Зарезервовані символи зламали б URL |
| Тримати файл або поле в базі даних у секреті | AES-256-GCM або ChaCha20-Poly1305 | Оригінал потрібен, але лише з ключем |
| Захистити дані під час передавання | TLS | Шифрування плюс автентифікація сервера |
| Зберігати паролі користувачів | Argon2id або bcrypt | Пароль назад ніколи не потрібен |
| Перевірити, що завантаження не змінили | SHA-256 | Порівняти дайджести |
| Довести, що запит до API надіслав власник ключа | HMAC-SHA256 | Хеш зі спільним секретним ключем |
Base64 немає в жодному рядку, де йдеться про збереження секрету.
JWT: усі три в одному токені
Підписаний JWT (JWS, RFC 7515) добре показує, як ці три речі працюють разом:
base64url(header) . base64url(payload) . base64url(signature)
encoding encoding HMAC or RSA/ECDSA signature
Заголовок і payload лише закодовані, тож вставте будь-який токен у Декодер JWT, і ви прочитаєте його claims без жодного ключа. Підпис, HMAC (хеш із ключем) або асиметричний підпис, доводить, що токен не змінювали. Конфіденційність забезпечує TLS навколо запиту. Якщо самі claims мають лишатися секретними, для цього є JWE (RFC 7516), зашифрований варіант, яким більшість API не користується. Тож не кладіть у payload JWT нічого, чого не повинен бачити власник токена.
Щоб швидко перевірити власні проєкти, пошукайте в конфігах, маніфестах і файлах змінних середовища довгі значення, що закінчуються на =, поруч з іменами на кшталт password, secret чи token, і декодуйте одне з них. Якщо вийшов читабельний текст, секрет зберігається відкритим текстом.
FAQ
Чи є Base64 шифруванням?
Ні. Це кодування з публічним алфавітом і без ключа, тож декодувати його може будь-хто. Kubernetes Secrets і HTTP Basic Auth передають секрети в Base64 і обидва залежать від інших рівнів захисту (шифрування at rest і контроль доступу, TLS).
Яка різниця між кодуванням, шифруванням і хешуванням?
У тому, хто може обернути результат. Кодування обертає будь-хто, шифрування — лише власник ключа, а хеш не обертає ніхто: можна лише знову захешувати кандидата й порівняти результати.
Чи можна декодувати Base64 без ключа?
Так, бо ключа немає. atob('c2VjcmV0') у консолі браузера повертає secret, і в стандартній бібліотеці кожної мови є відповідник.
Якщо хеш не можна обернути, як працює вхід за паролем?
Сервер хешує введений вами пароль тим самим алгоритмом, із тією самою сіллю й параметрами вартості, що збереглися під час реєстрації, і порівнює два хеші. Оригінальний пароль ніде не зберігається, тож і відновлювати його не потрібно.
Паролі треба шифрувати чи хешувати?
Хешувати, через Argon2id або bcrypt. Шифрування дозволило б кожному, в кого є ключ, розшифрувати всі паролі одразу, а оскільки застосунку цей ключ потрібен під час роботи, зазвичай він лежить на тих самих серверах, куди потрапляє зловмисник. Шифруйте дані, які вам потрібні назад, як-от файли чи API-токени, що їх треба передавати третій стороні; хешуйте те, що треба лише перевіряти.