Шифрование и расшифровка данных

Материал из MIK32 микроконтроллер

В примере будет зашифрованы и расшифрованы данные алгоритмом кузнечик. Данный процесс будет рассмотрен с различными режимами шифрования. Шифрования для алгоритмов «Магма» и AES128 полностью аналогичны, за исключением разрядности обрабатываемых данных.

Работа с конфигуратором (В разработке)

Для начала настроем в конфигураторе тактирование mik32, например, от внешнего кварца 32МГц. Затем настроем делители шины. Так как крипто-блок тактируется от шины AHB_CLK, то зададим делитель AHB_DIV. В данном примере оставим делитель по умолчанию. В итоге вкладка с тактированием должна выглядеть так:

(Картинка тактирования из конфигуратора. В работе)

Затем перейдем к настройке самого крипто-блока. Для этого откроем вкладку крипто-блок и нажмем включить. После этого появятся несколько настроек.

Настройки крипто-блока в конфигураторе

Зададим им следующие значения:

  • Алгоритм шифрования - Кузнечик;
  • Режим шифрования - ECB;
  • В перестановке слова - нет перестановки;
  • Порядок загрузки/выгрузки - От старшего слова к младшему.

В итоге настройки таймера в конфигураторе должны выглядеть как на рисунке.

Нажимаем кнопку сохранения и генерации. В итоге у нас появится проект для PlatformIo. Далее работа идет в visual studio code.

Использование библиотеки HAL_DAC

В сгенерированном проекте в файле main.c должна быть функция Crypto_Init, в которой будут заданы настройки для крипто-блока. Выглядит она так:

static void Crypto_Init(void)
{
    hcrypto.Instance = CRYPTO;

    hcrypto.Algorithm = CRYPTO_ALG_KUZNECHIK;
    hcrypto.CipherMode = CRYPTO_CIPHER_MODE_ECB;
    hcrypto.SwapMode = CRYPTO_SWAP_MODE_NONE; 
    hcrypto.OrderMode = CRYPTO_ORDER_MODE_MSW;

    HAL_Crypto_Init(&hcrypto);
}

Кроме этого в функции SystemClock_Config приведены настройки для тактирования. Убедитесь что в PeriphClkInit.PMClockAHB присутствует PM_CLOCK_CRYPTO_M. Сама функция должна выглядеть примерно так:

void SystemClock_Config(void)
{
    RCC_OscInitTypeDef RCC_OscInit = {0};
    RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};

    RCC_OscInit.OscillatorEnable = RCC_OSCILLATORTYPE_OSC32K | RCC_OSCILLATORTYPE_OSC32M;   
    RCC_OscInit.OscillatorSystem = RCC_OSCILLATORTYPE_OSC32M;                          
    RCC_OscInit.AHBDivider = 0;                             
    RCC_OscInit.APBMDivider = 0;                             
    RCC_OscInit.APBPDivider = 0;                             
    RCC_OscInit.HSI32MCalibrationValue = 0;                  
    RCC_OscInit.LSI32KCalibrationValue = 0;
    HAL_RCC_OscConfig(&RCC_OscInit);

    PeriphClkInit.PMClockAHB = PMCLOCKAHB_DEFAULT | PM_CLOCK_CRYPTO_M;    
    PeriphClkInit.PMClockAPB_M = PMCLOCKAPB_M_DEFAULT | PM_CLOCK_WU_M | PM_CLOCK_PAD_CONFIG_M;     
    PeriphClkInit.PMClockAPB_P = PMCLOCKAPB_P_DEFAULT | PM_CLOCK_UART_0_M; 
    PeriphClkInit.RTCClockSelection = RCC_RTCCLKSOURCE_NO_CLK;
    PeriphClkInit.RTCClockCPUSelection = RCC_RTCCLKCPUSOURCE_NO_CLK;
    HAL_RCC_ClockConfig(&PeriphClkInit);
}

Для демонстрации вывода текста в PeriphClkInit.PMClockAPB_P присутствует PM_CLOCK_UART_0_M. У вас его может не быть так как UART нужно включить отдельно. В начале main.c можно видеть объявление структуры с набором настроек для крипто-блока, которую использует функция инициализации Crypto_Init.

Crypto_HandleTypeDef hcrypto;

void SystemClock_Config(void);
static void Crypto_Init(void);

Создадим два глобальных массива для ключа и вектора инициализации. Количество элементов массива можно задать с помощью макросов CRYPTO_KEY_KUZNECHIK (количество слов в ключе для алгоритма кузнечик).

uint32_t crypto_key[CRYPTO_KEY_KUZNECHIK] = {0x8899aabb, 0xccddeeff, 0x00112233, 0x44556677, 0xfedcba98, 0x76543210, 0x01234567, 0x89abcdef};

В функции main после функции инициализации Crypto_Init запишем ключ в регистр KEY с помощью функции HAL_Crypto_SetKey.

Теперь напишем функции для зашифровки и расшифровки данных в режиме шифрования ECB - kuznechik_ECB_code и kuznechik_ECB_decode соответственно.

В функции kuznechik_ECB_code будет три массива данных:

  • plain_text - незашифрованные данные;
  • expect_cipher_text - зашифрованные данные, которые ожидается получить;
  • cipher_text - полученные зашифрованные данные.

Количество слов в данных должно быть кратно 4 (блок - 128 бит). Если данных меньше, то их нужно дополнить вручную. ГОСТ 34.13—2015 определяет три возможные процедуры дополнения. Можно, например, дополнить остаток нулями до размера блока.

Кроме этого нам понадобится функция HAL_Crypto_Encode для зашифровки данных. После этого поочередно выведем каждый из массивов в UART. После этого проведем сравнение полученных данных и ожидаемых.

Функция kuznechik_ECB_code

void kuznechik_ECB_code()
{
    uint32_t plain_text[] = {            
                                0x11223344, 0x55667700, 0xffeeddcc, 0xbbaa9988,
                                0x00112233, 0x44556677, 0x8899aabb, 0xcceeff0a,
                                0x11223344, 0x55667788, 0x99aabbcc, 0xeeff0a00,
                                0x22334455, 0x66778899, 0xaabbccee, 0xff0a0011
                            };

                    

    uint32_t expect_cipher_text[] = {
                                        0x7f679d90, 0xbebc2430, 0x5a468d42, 0xb9d4edcd, 
                                        0xb429912c, 0x6e0032f9, 0x285452d7, 0x6718d08b, 
                                        0xf0ca3354, 0x9d247cee, 0xf3f5a531, 0x3bd4b157, 
                                        0xd0b09ccd, 0xe830b9eb, 0x3a02c4c5, 0xaa8ada98
                                    };


    uint32_t plain_text_length = sizeof(plain_text)/sizeof(*plain_text);

    uint32_t cipher_text[plain_text_length];
    
    HAL_Crypto_Encode(&hcrypto, plain_text, cipher_text, plain_text_length); 

    xprintf("KEY ");
    for (uint32_t i = 0; i < CRYPTO_KEY_KUZNECHIK; i++)
    {
        xprintf("0x%08x ", crypto_key[i]);
    }
    xprintf("\n");  

    xprintf("plain: ");
    for (uint32_t i = 0; i < plain_text_length; i++)
    {
        xprintf("0x%08x, ", plain_text[i]);
    }
    xprintf("\n");   

    xprintf("cipher: ");
    for (uint32_t i = 0; i < plain_text_length; i++)
    {
        xprintf("0x%08x, ", cipher_text[i]);
    }
    xprintf("\n");

    xprintf("expect: ");
    for (uint32_t i = 0; i < plain_text_length; i++)
    {
        xprintf("0x%08x, ", expect_cipher_text[i]);
    }
    xprintf("\n");

    uint8_t error = 0;
    for (uint32_t i = 0; i < plain_text_length; i++)
    {
        if (expect_cipher_text[i] != cipher_text[i])
        {
            error = 1;
        }  
    }
    if (error)
    {
        xprintf("Error\n");
    }
    else
    {
        xprintf("Matched\n");
    }
    
}

Функция kuznechik_ECB_decode аналогична, но в ней используются другие массивы:

  • cipher_text - зашифрованные данные;
  • expect_plain_text - расшифрованные данные, которые ожидается получить;
  • plain_text - полученные расшифрованные данные.

Для расшифровки данных используется функция HAL_Crypto_Decode.

Функция kuznechik_ECB_decode

void kuznechik_ECB_decode()
{
    uint32_t expect_plain_text[] =  {            
                                        0x11223344, 0x55667700, 0xffeeddcc, 0xbbaa9988,
                                        0x00112233, 0x44556677, 0x8899aabb, 0xcceeff0a,
                                        0x11223344, 0x55667788, 0x99aabbcc, 0xeeff0a00,
                                        0x22334455, 0x66778899, 0xaabbccee, 0xff0a0011
                                    };

                
    uint32_t cipher_text[] = {  
                                0x7f679d90, 0xbebc2430, 0x5a468d42, 0xb9d4edcd, 
                                0xb429912c, 0x6e0032f9, 0x285452d7, 0x6718d08b, 
                                0xf0ca3354, 0x9d247cee, 0xf3f5a531, 0x3bd4b157, 
                                0xd0b09ccd, 0xe830b9eb, 0x3a02c4c5, 0xaa8ada98
                             };

    uint32_t cipher_text_length = sizeof(cipher_text)/sizeof(*cipher_text);

    uint32_t plain_text[cipher_text_length];
    
    HAL_Crypto_Decode(&hcrypto, cipher_text, plain_text, cipher_text_length); 

    xprintf("KEY ");
    for (uint32_t i = 0; i < CRYPTO_KEY_KUZNECHIK; i++)
    {
        xprintf("0x%08x, ", crypto_key[i]);
    }
    xprintf("\n");  

    xprintf("cipher: ");
    for (uint32_t i = 0; i < cipher_text_length; i++)
    {
        xprintf("0x%08x, ", cipher_text[i]);
    }
    xprintf("\n");   

    xprintf("plain: ");
    for (uint32_t i = 0; i < cipher_text_length; i++)
    {
        xprintf("0x%08x, ", plain_text[i]);
    }
    xprintf("\n");

    xprintf("expect: ");
    for (uint32_t i = 0; i < cipher_text_length; i++)
    {
        xprintf("0x%08x, ", expect_plain_text[i]);
    }
    xprintf("\n");

    uint8_t error = 0;
    for (uint32_t i = 0; i < cipher_text_length; i++)
    {
        if (expect_plain_text[i] != plain_text[i])
        {
            error = 1;
        }  
    }
    if (error)
    {
        xprintf("Error\n");
    }
    else
    {
        xprintf("Matched\n");
    }
}

В функции main перед вызовом kuznechik_ECB_code для наглядности можно добавить функцию HAL_Crypto_SetCipherMode для активации нужного режима шифрования. Но этого можно не делать так как режим шифрования уже задан в Crypto_Init. Функция main

int main()
{    

    SystemClock_Config();

    Crypto_Init();

    /* Установка ключа */
    HAL_Crypto_SetKey(&hcrypto, crypto_key);

    xprintf("\nkuznechik_ECB_code\n");
    HAL_Crypto_SetCipherMode(&hcrypto, CRYPTO_CIPHER_MODE_ECB);     /* Настройка режима шифрования */ 
    kuznechik_ECB_code();
    xprintf("\nkuznechik_ECB_decode\n");   
    kuznechik_ECB_decode();

    while (1)
    {    

    }
       
}
Вывод в UART

Вывод в UART изображен на рисунке.

Функции для режимов CBC и CTR аналогичны, но содержат другие данные в массивах.

Отличие этих режимов в том, что они перед каждой новой расшифровкой или зашифровкой требуют записи вектора инициализации с помощью функции HAL_Crypto_SetINIT.

Для режимов ECB и CBC длина данных должна быть кратна длине блока. Для режима CTR длинна данных может быть любой. В этом режиме дополнения не требуются.

Синхропосылка(вектор инициализации) для разных режимов может иметь разную длину:

В режиме ECB синхропосылка не используется;

В режиме CBC синхропосылка должна быть длинной m*z, где m - длина блока (128 бит для алгоритма Кузнечик), z - целое число;

В режиме CTR длинна синхропосылки равна половине длинны блока.