В этой статье мы поговорим об алгоритме хеширования SHA-256, подробно разберём что это такое, для чего нужно и как это работает. Повествование будет вестись в стиле «SHA-256 — это не просто, а очень просто» и рассчитано на неподготовленных пользователей и любителей электроники и программирования. В завершающей части статьи будут даны практические примеры работы с алгоритмом хеширования SHA-256 на микроконтроллерах в среде разработки Arduino IDE.
Криптография и DIY
Вообще, работа с криптографическими функциями предполагает некоторой базовый уровень знаний в математике и программировании, что отпугивает многих любителей микроконтроллеров. Но криптография и, в частности, хеширование по алгоритму SHA-256 очень широко применяются в современных коммуникациях (например для шифрования беспроводных сообщений) и игнорировать эту тему становится практически невозможно.
Поэтому в этой статье мы максимально просто и доходчиво объясним что такое SHA-256 и как работать на практике с этим алгоритмом хеширования, так, чтобы стало понятно даже самому неподготовленному читателю.
Ради простоты изложения будут опущены многие «заумные» подробности и всё будет изложено максимально простым языком. Те, кому нужны подробности, могут более детально ознакомиться с этой темой на множестве ресурсов в интернете.
Что такое хеширование
Хеширование — это алгоритм, который преобразует исходные данные (любого объёма) в небольшой набор выходных данных фиксированной длины. Входными данными может быть одно слово или целый роман, а выходными будет набор из нескольких байт. Важным для нас в этом преобразовании являются:
- Длина выходного сообщения (хеша) всегда одинакова, независимо от длины входных данных
- Любые, даже самые минимальные изменения в исходном сообщении приводят к генерации уникального хеша
- Практическая невозможность восстановления исходного сообщения по известному хешу
Исходя из этих свойств хеширования, становятся понятными области его применения — это контроль за целостностью различных сообщений, хранение «отпечатков» паролей и т. д. и т. п. Например, вы посчитали хеш какого-то документа — изменение хотя бы одного байта в нём приведёт к генерации другого хеша и вам (получателю) станет понятно, что документ кто-то изменил.
Что важно понимать: SHA-256 хотя и криптографический алгоритм, но он не предназначен для шифрования сообщений, для этого есть другие алгоритмы, например AES-128. SHA-256 предназначен исключительно для создания хешей и контроля целостности информации. А связка шифрующих алгоритмов с SHA-256 в практических приложениях позволяет создавать надёжно защищённые коммуникации.
На практике существуют проблемы с обеспечением уникальности хешей и некоторые другие проблемы чисто математического характера, но для простоты изложения мы их тут рассматривать не будем, а оставим профессиональным математикам.
SHA-256
SHA-256 — это реализация одного из множества возможных алгоритмов хеширования. Благодаря своей (относительной) простоте, достаточной надёжности и криптостойкости, а также приемлемой скорости работы, SHA-256 получил широкое распространение в современном мире. Он используется в телекоммуникациях, интернете, для шифрования беспроводных пакетов в IoT и т. д. и т. п. В общем, это один из наиболее широко используемых алгоритмов хеширования.
Для простоты изложения и восприятия материала мы здесь опустим всё математическое обоснование работы этого алгоритма, а сосредоточимся на практической части работы с ним на микроконтроллерах.
В практическом плане для нас важно знать, что выходное сообщение (хеш) алгоритма SHA-256 составляет 32 байта или 256 бит, собственно отсюда и цифра «256» в его названии.
Для чего нам может понадобится SHA-256 (в связке с другими алгоритмами, шифрованием и т. д.):
- Для защиты передачи беспроводных пакетов в IoT
- Для защиты передачи проводных данных и команд по проводным сетям
- Для контроля целостности данных при их хранении и передаче
- Для контроля целостности кодов и прошивок
- Для генерации уникальных кодов с хорошей энтропией*
- И т. д. и т. п. области применения
* Пояснение: алгоритм SHA-256 обладает одним (из многих) полезным свойством — он позволяет генерировать данные (хеши) с хорошей энтропией («мерой хаоса») на основе «бедных» входных данных. Это может пригодиться для генерации уникальных паролей, создания «магических» чисел для генераторов псевдо-случайных последовательностей и т. п. применений.
На этом теоретическую подготовку можно считать законченной и далее мы перейдём к рассмотрению практических примеров работы с алгоритмом SHA-256 на микроконтроллерах.
Библиотека SHA-256
В своих экспериментах мы будем использовать библиотеку, реализующую алгоритм SHA-256 на Arduino. Эта библиотека содержит несколько примеров, которые мы подробно разберём далее.
Перед использованием функций SHA-256, библиотеку нужно установить стандартным для Arduino способом. Здесь предполагается, что вы достаточно опытный пользователь и умеете работать с Arduino IDE, создавать и загружать скетчи в микроконтроллер.
Версия Arduino IDE может быть любая, начиная с 1.6.5, микроконтроллер подойдёт тоже почти любой из линейки Arduino, например Uno, Nano, Mega и т. д.
Пример 1: получаем хеш SHA-256
Цель этой статьи — объяснить простыми словами что такое SHA-256 и дать практические навыки работы с хешированием для использования его в ваших проектах. Поэтому далее мы подробно разберём работу функций библиотеки SHA-256.
В первом примере мы просто получим хеш SHA-256 строки «abc». Вместо этой строки могла бы быть любая другая. Обратите внимание, что длина исходной строки меньше длины генерируемого хеша.
Теперь полный код первого примера:
/* SHA-256 Test 1 */ #include "sha256.h" void printHash(uint8_t* hash) { for (byte i = 0; i < 32; i++) { Serial.print("0123456789abcdef"[hash[i] >> 4]); Serial.print("0123456789abcdef"[hash[i]&0xf]); } Serial.println(); } void setup() { Serial.begin(115200); Serial.println(F("SHA-256 Test 1 start...")); Serial.println(F("Test: FIPS 180-2 B.1")); Serial.println(F("Expect: ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad")); Serial.print(F("Result: ")); Sha256.init(); Sha256.print("abc"); printHash(Sha256.result()); } void loop() { }
И далее подробно разберём его работу. Вначале скетча мы подключаем библиотеку SHA-256
#include "sha256.h"
Далее идёт функция setup(), в которой и происходят все основные действия. Вначале мы инициализируем работу последовательного порта и выводим сообщение о старте скетча в Serial:
void setup() { Serial.begin(115200); Serial.println(F("SHA-256 Test 1 start..."));
Затем выводим вспомогательное сообщение об ожидаемом результате работы нашей функции вычисления хеша SHA-256.
Serial.println(F("Test: FIPS 180-2 B.1")); Serial.println(F("Expect: ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad")); Serial.print(F("Result: "));
И затем начинаем работу собственно по вычислению хеша SHA-256, а именно, инициализируем объект Sha256.
Sha256.init();
Далее идёт строка
Sha256.print("abc");
в которой мы направляем исходное сообщение "abc" на вход библиотечной функции вычисления хеша SHA-256. Несмотря на присутствие слова «print» в названии этой функции, она ничего не выводит на печать, а просто получает входные данные.
И наконец, функция Sha256.result() возвращает нам массив из 32-х байт, который и является хешем SHA-256 для входящей строки, в данном случае, "abc".
printHash(Sha256.result());
Поскольку хеш (набор байтов) нужно представить в удобном для восприятия виде, то результат работы функции Sha256.result() направляется на вход функции печати хеша в Serial.
void printHash(uint8_t* hash) { for (byte i = 0; i < 32; i++) { Serial.print("0123456789abcdef"[hash[i] >> 4]); Serial.print("0123456789abcdef"[hash[i]&0xf]); } Serial.println(); }
Сама по себе работа этой функции довольно интересна, но, поскольку это выходит за рамки текущей статьи, то мы оставляем анализ и разбор её работы заинтересованному читателю в качестве «домашнего задания».
Нам же здесь нужно знать только то, что эта функция выводит хеш в стандартном и читаемом виде.
Как вы видите, результат работы нашего скетча по вычислению хеша SHA-256 строки "abc" полностью совпал с ожидаемым. Этот простой пример показывает как вы можете вычислять хеш SHA-256 в своих реальных проектах.
Пример 2: работаем с HMAC
В первом примере мы разобрали самый простой случай, когда мы просто получали SHA-256 хеш исходных данных. Во втором примере мы рассмотрим несколько более сложный вариант, когда в получении хеша участвует некий секретный ключ (секретная последовательность байт).
Этот вариант называется использованием HMAC (Hash-based Message Authentication Code или «основанный на хеше код проверки подлинности сообщения»). Звучит пугающе, но на самом деле всё очень просто — в процесс получения хеша добавляется секретный ключ (который знаете вы и получатель вашего сообщения) и на принимающей стороне могут удостовериться, что ваше сообщение не было изменено.
Полный код второго примера:
/* SHA-256 Test 2 (HMAC) */ #include "sha256.h" uint8_t hmacKey[] = { 0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b }; void printHash(uint8_t* hash) { for (byte i = 0; i < 32; i++) { Serial.print("0123456789abcdef"[hash[i] >> 4]); Serial.print("0123456789abcdef"[hash[i]&0xf]); } Serial.println(); } void setup() { Serial.begin(115200); Serial.println(F("SHA-256 Test 2 (HMAC) start...")); Serial.println(F("Test: RFC4231 4.2")); Serial.println(F("Expect: b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7")); Serial.print(F("Result: ")); Sha256.initHmac(hmacKey, 20); Sha256.print("Hi There"); printHash(Sha256.resultHmac()); } void loop() { }
Теперь разберём подробно работу второго примера. В начале мы объявляем секретный HMAC ключ
uint8_t hmacKey[] = { 0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b };
который будет участвовать в получении SHA-256 хеша и который должен иметь получатель вашего сообщения. В данном случае это последовательность из 20-и байт 0x0b. Этот набор байт может быть любым, например, значения могут быть разными или длина ключа может быть меньше или больше 20-и байт. Здесь важно только то, что и вы и получатель вашего сообщения должны иметь одинаковый секретный ключ.
Далее идут стандартные действия по инициализации Serial порта и вывод сообщения о старте нашего скетча, а также вывод ожидаемого значения HMAC хеша.
void setup() { Serial.begin(115200); Serial.println(F("SHA-256 Test 2 (HMAC) start...")); Serial.println(F("Test: RFC4231 4.2")); Serial.println(F("Expect: b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7")); Serial.print(F("Result: "));
Затем начинается непосредственно работа по генерации хеша. Мы инициализируем объект Sha256 нашим ключом и указываем его длину 20 байт.
Sha256.initHmac(hmacKey, 20);
Далее подаём на вход строку "Hi There", хеш которой нам нужно получить
Sha256.print("Hi There");
Получаем результат при помощи функции
Sha256.resultHmac()
и выводим результат на печать.
printHash(Sha256.resultHmac());
Вот результат работы нашего скетча.
Как видите, работа с использованием секретного HMAC ключа не намного сложнее простого вычисления SHA-256 хеша. Фактически, библиотека SHA-256 берёт на себя все трудности по вычислению хешей и нам остаётся только воспользоваться её функциями.
Заключение
Библиотека SHA-256 содержит ещё множество примеров использования функций получения SHA-256 хешей, но все они являются вариациями вышеприведённых примеров и, если вы поняли общие принципы работы с хешированием SHA-256, то для вас не составит труда использовать эту современную и актуальную технологию в своих проектах.
Ссылки по теме
ATSHA204 - Библиотека и примеры
ATSHA204A - Чтение зоны конфигурации 1
ATSHA204A - Чтение зоны конфигурации 2
ATSHA204A - Чтение зоны конфигурации 3
ATSHA204A - Запись конфигурации 1
ATSHA204A - Запись конфигурации 2
ATSHA204A - Запись конфигурации 3
ATSHA204A - Запись конфигурации 4
ATSHA204A - Работа в режиме Config Lock
ATSHA204A - Работа с зонами памяти
ATSHA204A - Чтение Data и OTP зон памяти
ATSHA204A - Аутентификация. Базовый блок
ATSHA204A - Аутентификация. Датчик
ATSHA204A - Криптография и команды
ATSHA204A - nRF24 аутентификация. База
ATSHA204A - nRF24 аутентификация. Датчик