Шифрование и расшифровка данных: различия между версиями

Материал из MIK32 микроконтроллер
Нет описания правки
Нет описания правки
Строка 79: Строка 79:
*В режиме ECB синхропосылка не используется;
*В режиме ECB синхропосылка не используется;
*В режиме CBC синхропосылка должна быть длинной m*z, где m - длина блока (128 бит для алгоритма Кузнечик), z - целое число. Поддерживается только случай z=1, m=n, где n - длина блока (4 слова для Кузнечика и AES, 2 слова для Магмы);
*В режиме CBC синхропосылка должна быть длинной m*z, где m - длина блока (128 бит для алгоритма Кузнечик), z - целое число. Поддерживается только случай z=1, m=n, где n - длина блока (4 слова для Кузнечика и AES, 2 слова для Магмы);
*В режиме CTR в регистр INIT следует записывать синхропосылку, длинна которой равна половине длине блока, и такое же количество нулей. В итоге размер синхропосылки будет равен размеру блока.
*В режиме CTR в регистр INIT следует записывать значение счетчика состоящего из синхропосылки, длинна которой равна половине длине блока, и такого же количества нулей. В итоге размер синхропосылки будет равен размеру блока.


===Режим простой замены (Electronic Codebook, ЕСВ)===
===Режим простой замены (Electronic Codebook, ЕСВ)===

Версия от 08:15, 3 марта 2023

В примере будет зашифрованы и расшифрованы данные алгоритмом кузнечик. Данный процесс будет рассмотрен с различными режимами шифрования. Шифрования для алгоритмов «Магма» и 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};

Для режимов шифрования ECB и CBC длина данных должна быть кратна длине блока. Иначе данные следует дополнить.

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

Синхропосылка (вектор инициализации), в соответствии с ГОСТ 34.13—2015, для разных режимов может иметь разную длину :

  • В режиме ECB синхропосылка не используется;
  • В режиме CBC синхропосылка должна быть длинной m*z, где m - длина блока (128 бит для алгоритма Кузнечик), z - целое число. Поддерживается только случай z=1, m=n, где n - длина блока (4 слова для Кузнечика и AES, 2 слова для Магмы);
  • В режиме CTR в регистр INIT следует записывать значение счетчика состоящего из синхропосылки, длинна которой равна половине длине блока, и такого же количества нулей. В итоге размер синхропосылки будет равен размеру блока.

Режим простой замены (Electronic Codebook, ЕСВ)

Теперь напишем функции для зашифровки и расшифровки данных в режиме шифрования 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 cipher_text[] = {  
                                0x0, 0x0, 0x0, 0x0, 
                                0x0, 0x0, 0x0, 0x0, 
                                0x0, 0x0, 0x0, 0x0, 
                                0x0, 0x0, 0x0, 0x0
                             }; 

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


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


    /* Задать режим шифрования */
    HAL_Crypto_SetCipherMode(&hcrypto, CRYPTO_CIPHER_MODE_ECB);
    /* Установка ключа */
    HAL_Crypto_SetKey(&hcrypto, crypto_key);


    /* Зашифровать данные */
    HAL_Crypto_Encode(&hcrypto, plain_text, cipher_text, plain_text_length); 


    xprintf("KEY ");
    for (uint32_t i = 0; i < key_length; 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");

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

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

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

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

Функция kuznechik_ECB_decode

void kuznechik_ECB_decode()
{
    uint32_t plain_text[] =     {            
                                    0x0, 0x0, 0x0, 0x0, 
                                    0x0, 0x0, 0x0, 0x0, 
                                    0x0, 0x0, 0x0, 0x0, 
                                    0x0, 0x0, 0x0, 0x0
                                };
    
    uint32_t cipher_text[] = {  
                                0x7f679d90, 0xbebc2430, 0x5a468d42, 0xb9d4edcd, 
                                0xb429912c, 0x6e0032f9, 0x285452d7, 0x6718d08b, 
                                0xf0ca3354, 0x9d247cee, 0xf3f5a531, 0x3bd4b157, 
                                0xd0b09ccd, 0xe830b9eb, 0x3a02c4c5, 0xaa8ada98
                             }; 
    
    uint32_t expect_plain_text[] =  {            
                                        0x11223344, 0x55667700, 0xffeeddcc, 0xbbaa9988,
                                        0x00112233, 0x44556677, 0x8899aabb, 0xcceeff0a,
                                        0x11223344, 0x55667788, 0x99aabbcc, 0xeeff0a00,
                                        0x22334455, 0x66778899, 0xaabbccee, 0xff0a0011
                                    };

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


    /* Задать режим шифрования */
    HAL_Crypto_SetCipherMode(&hcrypto, CRYPTO_CIPHER_MODE_ECB);
    /* Установка ключа */
    HAL_Crypto_SetKey(&hcrypto, crypto_key);


    /* Расшифровать данные */
    HAL_Crypto_Decode(&hcrypto, cipher_text, plain_text, cipher_text_length); 


    xprintf("KEY ");
    for (uint32_t i = 0; i < key_length; 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");

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

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

int main()
{    
    SystemClock_Config();

    Crypto_Init();

    xprintf("\nkuznechik_ECB_code\n");
    kuznechik_ECB_code();
    xprintf("\nkuznechik_ECB_decode\n");   
    kuznechik_ECB_decode();

    while (1)
    {    

    }
       
}
Вывод в UART

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

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

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

Режим гаммирования (Counter, CTR)

Режим простой замены с зацеплением (Cipher Block Chaining, СВС)