/* * Шифр "Магма" в версии ГОСТ Р 34.12-2015. * Реализация шифра. * Примеры практического использования. * * Версия: 0.1 от 15/11/2016 * Автор: Александр Венедюхин * Сайт: https://dxdt.ru/ * Условия использования: свободное распространение, без ограничений. * * "Магма" (известен также как ГОСТ 28147-89) - блочный симметричный шифр, * с разрядностью блока 64 бита и разрядностью ключа - 256 бит. * * С целью повышения быстродействия и получения более компактного кода * сам шифр для микроконтроллера "Ардуино" целиком реализован на * inline-ассемблере - функция DoCipher(). * */ #define BLOCK_SIZE 8 // Длина блока шифра - 8 байтов (64 бита). #define KEY_SIZE 32 // Длина записи ключа - 32 байта (256 бит). #define NONCE_LEN 4 // Длина записи nonce - 4 байта. #define MAC_LEN 8 // Длина записи MAC - 8 байтов. #define HEADER_LEN 2 // Длина заголовка сообщения - 2 байта (записывается длина // открытого текста). const byte pi[] = /* * * 64 байта подстановок: оригинальные подстановки Pi (используются таблицы из ГОСТ Р 34.12-2015) * объединены в байты, соответственно 4-битным блокам, к которым они применяются. Это экономит * память. * Число 64 соответствует ограничениям команды ADIW ассемблера AVR. * */ { 0x6C,0x84,0x26,0x32,0x9A,0xA5,0x5B,0xC9,0x1E,0xE8,0x4D,0x77,0xB0,0xD3,0xF,0xF1, 0xCB,0x83,0x25,0x18,0xD2,0x4F,0xFA,0x6D,0x7E,0x1,0xA7,0x54,0x3C,0xE9,0x96,0xB0, 0x57,0xDF,0xF5,0x6A,0x98,0x21,0xC6,0xAD,0xB0,0x79,0x83,0x1E,0x4B,0x34,0xE2,0xC, 0x18,0x7E,0xE2,0xD5,0x6,0x59,0x81,0x3C,0x4F,0xF4,0xAB,0x60,0x9D,0xCA,0xB3,0x27 }; /* * Указатель, используемый в ассемблерной вставке для загрузки адреса массива подстановок. * Inline-ассемблер позволяет использовать символьные имена, заполняемые на этапе * линковки. * */ const byte *pi_ptr = pi; /* * Тестовые наборы данных (контрольные примеры) из ГОСТ Р 34.12-2015 для проверки работы * реализации шифра. * */ byte test_key[] = {0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x00, 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff}; byte test_plaintext[] = {0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10}; byte test_ciphertext[] = {0x4e, 0xe9, 0x01, 0xe5, 0xc2, 0xd8, 0xca, 0x3d}; // Набор указателей, используемых в реализации шифра. volatile byte *PT; // Указатель на блок открытого текста. volatile byte *CT; // Указатель на блок шифротекста. byte magma_round_keys[128]; // Массив для ключей раундов. byte *keys; // Указатель на (секретный) набор раундовых ключей. /* * Подстановки в исходной форме из ГОСТ Р 34.12-2015 - эквивалентны массиву Pi. * Не используются, приведены в качестве справочной информации. byte pi_0[] = {12, 4, 6, 2, 10, 5, 11, 9, 14, 8, 13, 7, 0, 3, 15, 1}; byte pi_1[] = {6, 8, 2, 3, 9, 10, 5, 12, 1, 14, 4, 7, 11, 13, 0, 15}; byte pi_2[] = {11, 3, 5, 8, 2, 15, 10, 13, 14, 1, 7, 4, 12, 9, 6, 0}; byte pi_3[] = {12, 8, 2, 1, 13, 4, 15, 6, 7, 0, 10, 5, 3, 14, 9, 11}; byte pi_4[] = {7, 15, 5, 10, 8, 1, 6, 13, 0, 9, 3, 14, 11, 4, 2, 12}; byte pi_5[] = {5, 13, 15, 6, 9, 2, 12, 10, 11, 7, 8, 1, 4, 3, 14, 0}; byte pi_6[] = {8, 14, 2, 5, 6, 9, 1, 12, 15, 4, 11, 0, 13, 10, 3, 7}; byte pi_7[] = {1, 7, 14, 13, 0, 5, 8, 3, 4, 15, 10, 6, 9, 12, 11, 2}; */ int DoCipher() /* * Функция, реализующая шифр. В данной версии - полностью на ассемблере AVR. * * Оперции зашифрования и расшифрования отличаются только порядком применения * ключей раундов. Поэтому, при необходимости использования шифра для * расшифрования блоков, задачу решает эта же функция. (См. примеры ниже.) * * Параметров нет - используются значения глобальных указателей: * PT - должен указывать на блок открытого текста (зашифровываемый блок); * CT - должен указывать на блок назначения, куда записывается шифротекст; * * keys - должен указывать на блок с раундовыми ключами (32 ключа по 32 бита - 128 байтов); * pi_ptr - должен указывать на массив подстановок (Pi). * * PT и CT должны указывать на области памяти размером не менее 64 бит (8 байт) каждая. * (При использовании в режиме расшифрования - фактическое назначение данных указателей * меняется на противоположное.) * * * Логика построения функции: блок открытого текста загружается в регистры, * где над ним проводятся преобразования шифра. Получившийся блок шифротекста выгружается * в область памяти (CT). * * Cхема представления блока (разбивается на две равные части, согласно алгоритму): * * r10, r11, r12, r13 = a1 \ * } PT - блок открытого текста. * r16, r17, r18, r19 = a0 / */ { asm volatile( "eor r23,r23\n" // r23 - значение 0, для использования в качестве операнда // при суммировании с флагом переноса. "lds r28,keys\n" // Y (ptr) - загружаем указатель на массив ключей раундов. "lds r29,keys+1\n" "lds r26,PT\n" // X (ptr) - загружаем указатель на блок открытого текста. "lds r27,PT+1\n" "lds r30,pi_ptr\n" // Z (ptr) - загружаем указатель на блок подстановок. "lds r31,pi_ptr+1\n" /* * Теперь регистры-указатели X,Y,Z - содержат, соответственно, адреса начала блока открытого * текста, начала массива раундовых ключей, начала массива подстановок. */ "ld r10,X+\n" // Загружаем байты открытого текста в регистры. "ld r11,X+\n" // Регистры r10-13 - "старшая" часть, a1 в стандарте. "ld r12,X+\n" "ld r13,X+\n" "ld r16,X+\n" // Регистры r16-19 - "младшая" часть, a0. "ld r17,X+\n" "ld r18,X+\n" "ld r19,X+\n" /* * Разделили исходный блок открытого текста на две части. Каждая по 32 бита, то есть 4 байта * или четыре регистра. */ /* * Шифр состоит из 32 раундов, но в последнем раунде не производится перестановка * частей блока. * * Каждый раунд получает на вход соответствующий 32-битный ключ раунда. * Раунд состоит из сложения с ключом, подстановок, циклического сдвига и суммирования * по модулую 2 (XOR) частей a1,a0. Завершается раунд (кроме последнего) перестановкой * частей блока a1<->a0. */ "ldi r21,32\n" // 32 раунда. "r:\n" // Метка начала цикла. "mov r2,r16\n" // Сохранение текущего значения a0. "mov r3,r17\n" "mov r4,r18\n" "mov r5,r19\n" "ld r6,Y+\n" // Загружаем ключ действующего раунда в регистры r6-9. "ld r7,Y+\n" // Раундовые ключи имеют разрядность 32 бита, "ld r8,Y+\n" // поэтому для хранения одного ключа нужно 4 регистра. "ld r9,Y+\n" "add r19,r9\n" // Вычисляем сумму (mod 2^32) раундового ключа и a0. "adc r18,r8\n" "adc r17,r7\n" "adc r16,r6\n" /* * Нелинейное преобразование. Подстановки. "Магма" использует подстановки полубайтов (4-бита). * То есть, каждое 4-битное значение преобразуемого блока - заменяется на соответствующее * значение из таблицы подстановок. В данной реализации, подстановки объединены в байты, поэтому * для загрузки нужного значения требуются дополнительные операции. * * Регистр r20 используется для хранения промежуточных результатов. */ "mov r20,r19\n" // Загружаем байт преобразуемого блока в r20. "andi r20,0xF0\n" // Маскируем старший полубайт. "swap r20\n" // Переставляем в младший полубайт, для правильной индексации ниже. "push r31\n" // Значения указателя Z должны быть сохранены, так как очередной "push r30\n" // индекс всегда вычисляется от начала массива. "add r30,r20\n" // Смещение для указателя - вычисляем индекс. "adc r31,r23\n" // Добавление флага переноса в старший байт 16-битного адреса (r23 = 0). // Указатель Z теперь показывает на байт с подстановками для старшего полубайта из r20. "ld r20,Z\n" // Загружаем подстановки (загружается байт). "pop r30\n" // Восстанавливаем значение указателя Z. "pop r31\n" "andi r20,0xF0\n" // Выбираем и применяем только старший полубайт подстановки. "andi r19,0x0F\n" "or r19,r20\n" // Старшая часть r19 заменена на значение подстановки. "mov r20,r19\n" // Дальнейшие операции - аналогичны. "andi r20,0x0F\n" // Младший полубайт. "push r31\n" "push r30\n" "add r30,r20\n" // Смещение для указателя. "adc r31,r23\n" // Добавление флага переноса (r23 = 0). "ld r20,Z\n" // Загружаем подстановки. "pop r30\n" "pop r31\n" "andi r20,0x0F\n" // Выбираем и загружаем только младший полубайт подстановки. "andi r19,0xF0\n" "or r19,r20\n" /* * Аналогичные операции с всеми оставшимися регистрами a0. */ // r18 "mov r20,r18\n" "andi r20,0xF0\n" "swap r20\n" "push r31\n" "push r30\n" "add r30,r20\n" "adc r31,r23\n" "adiw r30,16\n" // Сдвиг на очередной блок подстановок в таблице подстановок. "ld r20,Z\n" "pop r30\n" "pop r31\n" "andi r20,0xF0\n" "andi r18,0x0F\n" "or r18,r20\n" "mov r20,r18\n" "andi r20,0x0F\n" "push r31\n" "push r30\n" "add r30,r20\n" "adc r31,r23\n" "adiw r30,16\n" "ld r20,Z\n" "pop r30\n" "pop r31\n" "andi r20,0x0f\n" "andi r18,0xf0\n" "or r18,r20\n" // r17 "mov r20,r17\n" "andi r20,0xF0\n" "swap r20\n" "push r31\n" "push r30\n" "add r30,r20\n" "adc r31,r23\n" "adiw r30,32\n" "ld r20,Z\n" "pop r30\n" "pop r31\n" "andi r20,0xf0\n" "andi r17,0x0f\n" "or r17,r20\n" "mov r20,r17\n" "andi r20,0x0F\n" "push r31\n" "push r30\n" "add r30,r20\n" "adc r31,r23\n" "adiw r30,32\n" "ld r20,Z\n" "pop r30\n" "pop r31\n" "andi r20,0x0f\n" "andi r17,0xf0\n" "or r17,r20\n" // r16 "mov r20,r16\n" "andi r20,0xF0\n" "swap r20\n" "push r31\n" "push r30\n" "add r30,r20\n" "adc r31,r23\n" "adiw r30,48\n" "ld r20,Z\n" "pop r30\n" "pop r31\n" "andi r20,0xf0\n" "andi r16,0x0f\n" "or r16,r20\n" "mov r20,r16\n" "andi r20,0x0F\n" "push r31\n" "push r30\n" "add r30,r20\n" "adc r31,r23\n" "adiw r30,48\n" "ld r20,Z\n" "pop r30\n" "pop r31\n" "andi r20,0x0f\n" "andi r16,0xf0\n" "or r16,r20\n" /* * Подстановки завершены. */ /* * Циклический сдвиг влево, на 11 разрядок. */ "ldi r23,11\n" // Используем цикл, с регистром r23. "l:\n" // Метка цикла. "lsl r19\n" // Логический сдвиг влево на один бит. "rol r18\n" // Циклический сдвиг, использующий флаг переноса. "rol r17\n" "bst r16,7\n" // Старший бит регистра r16 копируется. "rol r16\n" "bld r19,0\n" // Переносим старший бит из r16. "dec r23\n" // Подсчёт итераций. "brne l\n" // Повтор. "eor r16,r10\n" // XOR двух частей блока - a1,a0. "eor r17,r11\n" "eor r18,r12\n" "eor r19,r13\n" /* * Мы получили в r16-19 промежуточный результат раунда для a1. * Остаётся произвести обмен двух частей блока. Этой операции эквивалентно * восстановление ранее сохранённой исходной части a0 в регистры, * выделенные для a1. * */ "dec r21\n" // Подсчёт раундов. "breq e\n" // Завершение цикла (выход происходит до // перестановки a0<->a1). "mov r10,r2\n" // Загрузка a1. "mov r11,r3\n" "mov r12,r4\n" "mov r13,r5\n" "jmp r\n" // Cледующий раунд. "e:\n" // Метка выхода из основного цикла. /* * Далее происходит выгрузка полученного шифротекста (r10-19) в блок памяти. */ "lds r30,CT\n" // В регистр Z загружаем указатель на блок для шифротекста. "lds r31,CT+1\n" "st Z+,r16\n" // Выгружаем регистры в память. "st Z+,r17\n" "st Z+,r18\n" "st Z+,r19\n" "st Z+,r2\n" "st Z+,r3\n" "st Z+,r4\n" "st Z+,r5\n" : : : "r2","r3","r4","r5","r6","r7","r8","r9","r10","r11","r12","r13","r14","r15","r16","r17","r18","r19","r20","r21","r22","r23","r26","r27","r28","r29","r30","r31","memory" ); return 1; } void ExpandKey(byte *SKey, byte *RoundKeys) /* * Функция развертывания ключа (для шифрования). * Шифр использует набор раундовых ключей, который строится из основного ключа * шифрования. * * Алгоритм развертывания ключа сводится к копированию 32-битных блоков * исходного ключа в прямом порядке раундов, за исключением последних 8 ключей, * которые копируются в обратном порядке. * * Схема: * * | k_1, k_2, k_3 ... k_8 | k_1 ... k_8 | k_1 ... k_8 | k_8, k_7 ... k_1 | * * * Параметры: * SKey - указатель на основной ключ (256 бит - массив из 32 байтов); * RoundKeys - указатель на массив, куда будут помещены ключи раундов. * */ { int i; byte *p, *d; p = SKey; d = RoundKeys; for(i = 0; i<8; i++){ *d = *(p+i*4); *(d+1) = *(p+1+i*4); *(d+2) = *(p+2+i*4); *(d+3) = *(p+3+i*4); *(d+8*4) = *(p+i*4); *(d+1+8*4) = *(p+1+i*4); *(d+2+8*4) = *(p+2+i*4); *(d+3+8*4) = *(p+3+i*4); *(d+16*4) = *(p+i*4); *(d+1+16*4) = *(p+1+i*4); *(d+2+16*4) = *(p+2+i*4); *(d+3+16*4) = *(p+3+i*4); *(d+24*4) = *(p+(7-i)*4); *(d+1+24*4) = *(p+1+(7-i)*4); *(d+2+24*4) = *(p+2+(7-i)*4); *(d+3+24*4) = *(p+3+(7-i)*4); d+=4; } } void ExpandKeyDecrypt(byte *SKey, byte *RoundKeys) /* * Функция развертывания ключа - версия для расшифрования. * Операция расшифрования отличается только обратным порядком раундовых ключей. * * Схема: * * | k_1 ... k_7, k_8 | k_8 ... k_1 | K_8 ... k_1 | k_8 ... k_1 | * * * Параметры: * SKey - указатель на основной ключ (256 бит - массив из 32 байтов); * RoundKeys - указатель на массив, куда будут помещены ключи раундов. * */ { int i; byte *p, *d; p = SKey; d = RoundKeys; for(i = 0; i<8; i++){ *d = *(p+i*4); *(d+1) = *(p+1+i*4); *(d+2) = *(p+2+i*4); *(d+3) = *(p+3+i*4); *(d+8*4) = *(p+(7-i)*4); *(d+1+8*4) = *(p+1+(7-i)*4); *(d+2+8*4) = *(p+2+(7-i)*4); *(d+3+8*4) = *(p+3+(7-i)*4); *(d+16*4) = *(p+(7-i)*4); *(d+1+16*4) = *(p+1+(7-i)*4); *(d+2+16*4) = *(p+2+(7-i)*4); *(d+3+16*4) = *(p+3+(7-i)*4); *(d+24*4) = *(p+(7-i)*4); *(d+1+24*4) = *(p+1+(7-i)*4); *(d+2+24*4) = *(p+2+(7-i)*4); *(d+3+24*4) = *(p+3+(7-i)*4); d+=4; } return; } void MagmaEncryptBlock(byte *k, byte *pt, byte *ct) /* * Функция шифрования блока. Приводится в качестве примера. В большинстве * случаев, не следует использовать шифр для непосредственного зашифрования * защищаемых данных. * * Параметры: * k - ключ; * pt - блок открытого текста; * ct - блок, куда будет помещён шифротекст. */ { ExpandKey(k,magma_round_keys); keys = magma_round_keys; CT = ct; PT = pt; DoCipher(); return; } void MagmaDecryptBlock(byte *k, byte *pt, byte *ct) /* * Функция расшифрования блока. * * Параметры: * k - ключ; * pt - блок, куда будет помещён открытый текст; * ct - блок шифротекста. */ { ExpandKeyDecrypt(k,magma_round_keys); keys = magma_round_keys; CT = pt; PT = ct; DoCipher(); return; } int MagmaEncrypt(byte *k, uint32_t nonce, byte *pt, byte *dst, uint16_t len) /* * Функция шифрования в режиме счётчика (гаммирования - в русскоязычной терминологии). * * Режим устроен следующим образом. Входное сообщение (открытый текст) разбивается на * блоки, длина которых соответствует разрядности шифра (64 бита). Последний блок может * быть неполным. На основе nonce (значения, используемого однократно) строится * последовательность блоков счётчика (в данной реализации, увеличивается * последний 32-битный полублок). Блоки счётчика зашифровываются, полученные блоки * шифротекста суммируются (XOR) c соответствующими блоками открытого текста. Если * последний блок - неполный, то используется только эквивалентная часть блока * шифротекста. Полученная последовательность байтов - представляет результат * зашифрования входного сообщения. * * Другими словами: поток открытого текста суммируется с ключевым потоком равной длины, * ключевой поток генерируется при помощи (последовательного) шифрования значений * счётчика. * * Параметры: * * k - ключ шифрования; * nonce - 32-битное значение, задающее начальное состояние счётчика; * pt - указатель на блок открытого текста; * dst - указатель на буфер, куда будет записан шифротекст; * len - длина открытого текста, в байтах. Совпадает с длиной шифротекста. * * Значение nonce с заданным ключом для зашифрования следует использовать только один раз. * */ { uint16_t bytes,blocks_num; // Число записанных байтов. Число блоков. uint32_t counter; // Значение счётчика. int i,n; // Переменные (индексы) циклов. byte *c,*p; // Указатели: буфер шифротекста, буфер открытого текста. byte block[8]; // Блок счётчика. byte g[8]; // Результат зашифрования блока счётчика - блок гаммы. c = dst; p = pt; bytes = 0; // Определение необходимого числа блоков. if((len) % BLOCK_SIZE){ blocks_num = (len) / BLOCK_SIZE + 1; }else{ blocks_num = (len) / BLOCK_SIZE; } // Вычисление раундовых ключей. ExpandKey(k,magma_round_keys); /* * Начальное значение счётчика - nonce. * Для формирования блока счётчика требуется 64 бита. В данной * реализации первая половина блока образуется nonce, вторая (32 бита) * представляет собой увеличивающееся значение счётчика nonce. Таким образом, * первый блок имеет следующий вид: nonce,nonce+1 (запятая обозначает * конкатенацию битовых строк). * */ counter = nonce+1; for(n = 0; n < 4; n++){ block[n] = *((byte *)&nonce+(3-n)); block[n+4] = *((byte *)&counter+(3-n)); } // Цикл по числу блоков. for(i = 0; i < blocks_num; i++){ /* * PT - указывает на блок счётчика, для зашифрования. * CT - результат зашифрования, блок гаммы. * keys = действующие раундовые ключи. */ PT = block; CT = g; keys = magma_round_keys; // Выполнение преобразования шифра. DoCipher(); // Вычисление шифротекста (XOR открытого текста и гаммы). for(n = 0; n < BLOCK_SIZE; n++){ *c++ = *p++^(g[n]); bytes++; if(bytes>=len){ // При достижении конца открытого текста - возврат. return 1; } } counter++; for(n = 0; n < 4; n++){ block[n+4] = *((byte *)&counter+(3-n)); } } return 1; } int MagmaDecrypt(byte *k, uint32_t nonce, byte *dst, byte *ct, uint16_t len) /* * Функция расшифрования. В режиме счётчика - обратная к MagmaEncrypt(). * Операция расшифрования сводится к генерации гаммы, с последующим сложением * блоков шифротекста с блоками гаммы. Для успешного расшифрования, * значения ключа и nonce должны соответствовать шифротексту. * * Параметры: * * k - ключ шифрования; * nonce - 32-битное значение, задающее начальное состояние счётчика; * dst - указатель на буфер, куда будет записан открытый текст; * ct - указатель на блок шифротекста; * len - длина открытого текста, в байтах. Совпадает с длиной шифротекста. * */ { uint16_t bytes,blocks_num; // Число записанных байтов. Число блоков. uint32_t counter; // Значение счётчика. int i,n; // Переменные (индексы) циклов. byte *c,*p; // Указатели: буфер шифротекста, буфер открытого текста. byte block[8]; // Блок счётчика. byte g[8]; // Результат зашифрования блока счётчика - блок гаммы. c = ct; p = dst; bytes = 0; // Определение необходимого числа блоков. if((len) % BLOCK_SIZE){ blocks_num = (len) / BLOCK_SIZE + 1; }else{ blocks_num = (len) / BLOCK_SIZE; } // Вычисление раундовых ключей. ExpandKey(k,magma_round_keys); counter = nonce+1; for(n = 0; n < 4; n++){ block[n] = *((byte *)&nonce+(3-n)); block[n+4] = *((byte *)&counter+(3-n)); } // Цикл по числу блоков. for(i = 0; i < blocks_num; i++){ PT = block; CT = g; keys = magma_round_keys; // Выполнение преобразования шифра. DoCipher(); // Вычисление шифротекста (XOR открытого текста и гаммы). for(n = 0; n < BLOCK_SIZE; n++){ *p++ = *c++^(g[n]); bytes++; if(bytes>=len){ // При достижении конца открытого текста - возврат. return 1; } } counter++; for(n = 0; n < 4; n++){ block[n+4] = *((byte *)&counter+(3-n)); } } return 1; } /* * Ниже даны некоторые примеры использования шифра. Результаты работы - выводим в терминал. * */ void setup() { byte ct[8]; byte flag; Serial.begin(115200); Serial.println("Test."); pinMode(A1,INPUT); pinMode(A2,INPUT); randomSeed(analogRead(A1)); // Проверка работы шифра с использованием контрольного примера из ГОСТ. Serial.print("Canonical test: "); MagmaEncryptBlock(test_key,test_plaintext,ct); flag = 0; for(int i = 0; i < 8; i++){ flag |= ct[i] ^ test_ciphertext[i]; } if(flag){ Serial.println("FAILED"); }else{ Serial.println("PASSED"); } } void loop() { byte t_pt[42] = {'T','h','i','s',' ','i','s',' ','a',' ','t','e','s','t',' ','s','t','r','i','n','g',' ','t','o',' ','c','h','e','c','k',' ','t','h','e',' ','c','i','p','h','e','r','.'}; byte t_ct[42]; uint32_t t_nonce; uint16_t pt_len; t_nonce = 1234567+random(8191)+analogRead(A1); delay(17); t_nonce += analogRead(A2); Serial.print("nonce: "); Serial.println(t_nonce,HEX); pt_len = 42; MagmaEncrypt(test_key,t_nonce,t_pt,t_ct,pt_len); Serial.print("Encrypt result: "); for(int i = 0; i<42; i++){ Serial.print('['); if( t_ct[i]<=15 ){ Serial.print("0"); } Serial.print(t_ct[i], HEX); Serial.print(']'); } Serial.println(); MagmaDecrypt(test_key,t_nonce,t_pt,t_ct,pt_len); Serial.print("Result: "); for(int i = 0; i < pt_len; i++){ Serial.write(t_pt[i]); } Serial.println(); delay(5050); }