'2020/04/16'에 해당되는 글 1건
- 2020.04.16 Hardware | ESP32 Cryptographic HW 가속 확인해 보기 2
ESP32 에 관한 글들은 아래 링크들을 참고해 주세요.
* Hardware | EPS32 PWM 기능 확인해 보기
- https://chocoball.tistory.com/entry/Hardware-EPS32-PWM
* Hardware | ESP32 의 internal sensor 확인해 보기
- https://chocoball.tistory.com/entry/Hardware-ESP32-internal-sensors
* Hardware | ESP32 의 Dual core 확인해 보기
- https://chocoball.tistory.com/entry/Hardware-ESP32-Dual-core
* Hardware | ESP32 스펙 확인해 보기
- https://chocoball.tistory.com/entry/Hardware-ESP32-spec-check
* Hardware | ESP32 간단 사용기
- https://chocoball.tistory.com/entry/Hardware-simple-review-ESP32
1. Cryptographic Hardware Acceleration
한 13여년 전에 웹 서비스 구축 시, HTTPS (SSL) 처리에 CPU 연산을 너무 많이 사용해서 골치가 아팠던 경험이 있습니다.
당시에는 NIC 에 붙어있는 Intel 칩에서 SSL 가속 처리를 못해줘, OS 에서 처리하다 보니 CPU 들이 죽어 나갔죠.
막 HW 가속기 (PCI daughter card 형식) 들이 등장하기도 했습니다만, 어디까지나 실험적인 제품들이었고, OS 와 HW 특성을 많이 타다 보니 PoC 단계에서도 그닥 실효를 거두지 못했었습니다.
암호 연산에 대해서는 요즘 NIC 나 CPU 자체적으로 전용 명령어 set을 가지고 지원하는 시대이다 보니, 예전같은 걱정은 말끔히 사라졌네요.
근래에 출시된 ESP32 에도, 이 암호 연산용 HW 가속 기능이 내장되어 있습니다!
다이어그램 상, SHA / RSA / AES / RNG 등이 있네요.
사양서에도 이 HW Accelerator 에 대한 안내가 되어 있습니다.
Cryptographic hardware acceleration
- AES, SHA-2, RSA, elliptic curve cryptography (ECC), random number generator (RNG)
이번 글은 위의 HW Accelerator 의 몇 가지 기능 중, ESP32 의 hardware AES 에 대해 알아보고자 합니다.
2. AES
미국 정부가 1990년 후반까지 사용하고 있던 DES 암호화 기법이, 약 30대 정도의 PC 를 가지고 뚫리면서, 새로운 암호화 기법을 찾게 됩니다. 공모 결과 AES 가 채택되면서 유명해진 암호화 기법이에요.
AES 는 "Advanced Encryption Standard" 의 약자로 cryptographic symmetric cipher algorithm 을 기반으로 encryption 과 decryption 양쪽에 사용될 수 있는 장점을 가지고 있습니다.
참고로 아래 두 가지의 parameter 를 필요로 합니다.
IV (Initial Vector)
맨 처음 block 을 XOR 처리를 할 때, 덮어 씌우는 data block 이 존재하지 않습니다. 이를 보충해 주기 위한 인위적인 블럭이 IV 입니다.
Encryption Key
암호화 / 복호화에 사용되는 고유의 키 입니다.
너무 자세한 설명에 들어가면, 저의 지식이 바닦 치는 것이 보이기에 여기서 그만 합니다.
인터넷에 관련된 문서 및 동영상들이 어마어마 하니, 자세히 공부해 보고 싶으신 분을 넓은 인터넷의 세계로...
3. Software AES - ESP32
HW 가속을 시험해 보기에 앞서, AES 를 소프트웨어적으루 구현해본 분이 계셔서 따라 해봤습니다.
* AES Encryption/Decryption using Arduino Uno
- https://www.arduinolab.net/aes-encryptiondecryption-using-arduino-uno/
우선 필요한 것은, Spaniakos 라는 분이 만드신 AES 라이브러리를 설치해 줍니다. 아래 Github 에서 라이브러리를 다운 받습니다.
* AES for microcontrollers (Arduino & Raspberry pi)
- https://github.com/spaniakos/AES
그리고, Arduino libraries 폴더에 심어 놓으면 됩니다.
기본 준비가 되었으니, ESP32 에서 AES-CBC 방식의 암호화/복호화를 실행 해봅니다.
/*------------------------------------------------------------------------------ Program: aesEncDec Description: Basic setup to test AES CBC encryption/decryption using different key lengths. Hardware: Arduino Uno R3 Software: Developed using Arduino 1.8.2 IDE Libraries: - AES Encryption Library for Arduino and Raspberry Pi: https://spaniakos.github.io/AES/index.html References: - Advanced Encryption Standard by Example: http://www.adamberent.com/wp-content/uploads/2019/02/AESbyExample.pdf - AES Class Reference: https://spaniakos.github.io/AES/classAES.html Date: July 9, 2017 Author: G. Gainaru, https://www.arduinolab.net (based on AES library documentation and examples) ------------------------------------------------------------------------------*/ #include "AES.h" AES aes ; unsigned int keyLength [3] = {128, 192, 256}; // key length: 128b, 192b or 256b byte *key = (unsigned char*)"01234567890123456789012345678901"; // encryption key byte plain[] = "https://chocoball.tistory.com/entry/Hardware-ESP32-Cryptographic-hardware-acceleration"; // plaintext to encrypt unsigned long long int myIv = 36753562; // CBC initialization vector; real iv = iv x2 ex: 01234567 = 0123456701234567 void setup () { Serial.begin(115200); } void loop () { for (int i=0; i < 3; i++) { Serial.print("- key length [b]: "); Serial.println(keyLength [i]); aesTest (keyLength[i]); delay(2000); } } void aesTest (int bits) { aes.iv_inc(); byte iv [N_BLOCK]; int plainPaddedLength = sizeof(plain) + (N_BLOCK - ((sizeof(plain)-1) % 16)); // length of padded plaintext [B] byte cipher [plainPaddedLength]; // ciphertext (encrypted plaintext) byte check [plainPaddedLength]; // decrypted plaintext aes.set_IV(myIv); aes.get_IV(iv); Serial.print("- encryption time [us]: "); unsigned long ms = micros (); aes.do_aes_encrypt(plain, sizeof(plain), cipher, key, bits, iv); Serial.println(micros() - ms); aes.set_IV(myIv); aes.get_IV(iv); Serial.print("- decryption time [us]: "); ms = micros (); aes.do_aes_decrypt(cipher,aes.get_size(),check,key,bits,iv); Serial.println(micros() - ms); Serial.print("- plain: "); aes.printArray(plain,(bool)true); //print plain with no padding Serial.print("- cipher: "); aes.printArray(cipher,(bool)false); //print cipher with padding Serial.print("- check: "); aes.printArray(check,(bool)true); //print decrypted plain with no padding Serial.print("- iv: "); aes.printArray(iv,16); //print iv printf("\n-----------------------------------------------------------------------------------\n"); }
암호화를 걸 평문은, 이 포스트의 URL 을 사용했습니다. :-)
암호화/복호화 잘 됩니다. 속도도 좋네요.
4. Software AES - ATmega328
비교 대상으로 궁금하여, ATmega328 을 탑재한 Arduino nano 로 동일한 계산을 시켜보기로 합니다.
다만, "AES.h" 라이브러리를 include 한다고 제대로 실행되진 않는군요.
aes.printArray(plain,(bool)true); //print plain with no padding
이유는, ATmega328 의 라이브러리에는 위의 printArray 에서 사용하는 printf_P 함수가 없기 때문입니다. (AES.cpp 에서 정의됨)
ESP32 의 FreeRTOS 에는 C 라이브러리 기본 탑재로 문제 없이 동작하지만, Arduino nano 에서는 동작하지 않습니다.
그리하여, 이를 대신할 function 을 만들어 봤는데, 징그럽게도 동작하지 않더군요.
수많은 삽질을 통해, 배열을 다른 함수의 인자로 전달하려면 배열의 pointer 와 그 배열의 크기를 명시해야 하는 것을 알게 되었습니다.
결국 아래처럼 변경하여 Arduino nano 에서도 동작을 성공 시켰습니다.
... showArray(iv, array_size_iv, 1); ... void showArray (byte *result, int array_length, int hex_conv) { for (int i=0; i < array_length; i++) { if (hex_conv) { Serial.print(result[i], HEX); } else { Serial.print((char)result[i]); } } Serial.println(); }
최종 소스는 다음과 같습니다.
#include "AES.h" AES aes ; unsigned int keyLength [3] = {128, 192, 256}; // key length: 128b, 192b or 256b byte *key = (unsigned char*)"01234567890123456789012345678901"; // encryption key byte plain[] = "https://chocoball.tistory.com/entry/Hardware-ESP32-Cryptographic-hardware-acceleration"; // plaintext to encrypt unsigned long long int myIv = 36753562; // CBC initialization vector; real iv = iv x2 ex: 01234567 = 0123456701234567 void setup () { Serial.begin(115200); } void loop () { for (int i=0; i < 3; i++) { Serial.print("- key length [b]: "); Serial.println(keyLength [i]); aesTest (keyLength[i]); delay(2000); } } void aesTest (int bits) { aes.iv_inc(); byte iv [N_BLOCK]; int plainPaddedLength = sizeof(plain) + (N_BLOCK - ((sizeof(plain)-1) % 16)); // length of padded plaintext [B] byte cipher [plainPaddedLength]; // ciphertext (encrypted plaintext) byte check [plainPaddedLength]; // decrypted plaintext aes.set_IV(myIv); aes.get_IV(iv); Serial.print("- encryption time [us]: "); unsigned long ms = micros (); aes.do_aes_encrypt(plain, sizeof(plain), cipher, key, bits, iv); Serial.println(micros() - ms); aes.set_IV(myIv); aes.get_IV(iv); Serial.print("- decryption time [us]: "); ms = micros (); aes.do_aes_decrypt(cipher,aes.get_size(),check,key,bits,iv); Serial.println(micros() - ms); Serial.print("- plain: "); //aes.printArray(plain,(bool)true); //print plain with no padding int array_size_p = sizeof(plain); showArray(plain, array_size_p, 0); Serial.print("- cipher: "); //aes.printArray(cipher,(bool)false); //print cipher with padding int array_size_ci = sizeof(cipher); showArray(cipher, array_size_ci, 0); Serial.print("- check: "); //aes.printArray(check,(bool)true); //print decrypted plain with no padding int array_size_ch = sizeof(check); showArray(check, array_size_ch, 0); Serial.print("- iv: "); //aes.printArray(iv,16); //print iv int array_size_iv = sizeof(iv); showArray(iv, array_size_iv, 1); Serial.println("-----------------------------------------------------------------------------------"); } void showArray (byte *result, int array_length, int hex_conv) { for (int i=0; i < array_length; i++) { if (hex_conv) { Serial.print(result[i], HEX); } else { Serial.print((char)result[i]); } } Serial.println(); }
아래는 Arduino nano 에서 실행시킨 결과 입니다.
ESP32 에서 Software 로 돌린 AES 결과와 비교시, 걸린 시간 빼곤 완벽히 동일합니다.
ESP32 vs. ATmega328 의 CPU 차에 의한 software AES 계산은 encryption = 27 배, decryption = 20 배 정도 차이 났습니다.
---------------------------------------------- | bits | ESP32 | ATmega328 | diff. (multiply)| |--------------------------------------------| | 128 | 159 | 4396 | 27.6 | | | 267 | 5388 | 20.1 | |--------------------------------------------| | 192 | 189 | 5156 | 27.2 | | | 321 | 6392 | 19.9 | |--------------------------------------------| | 256 | 220 | 5964 | 27.1 | | | 376 | 7432 | 19.7 | ----------------------------------------------
ESP32 를 찬양하라!
5. Hardware AES - library
마지막으로 ESP32 의 HW AES 를 걸어볼 차례 입니다.
HW accelerator 의 Native library 는 아래 글에서 설명이 잘 되어 있습니다.
* AES-CBC encryption or decryption operation
- https://tls.mbed.org/api/aes_8h.html#a321834eafbf0dacb36dac343bfd6b35d
int mbedtls_aes_crypt_cbc ( mbedtls_aes_context * ctx, int mode, size_t length, unsigned char iv[16], const unsigned char * input, unsigned char * output )
각 변수들의 정의 입니다.
--------------------------------------------------------------------------------------------------- | Parameters | Meaning | --------------------------------------------------------------------------------------------------- | ctx | The AES context to use for encryption or decryption. | | | It must be initialized and bound to a key. | --------------------------------------------------------------------------------------------------- | mode | The AES operation: MBEDTLS_AES_ENCRYPT or MBEDTLS_AES_DECRYPT. | --------------------------------------------------------------------------------------------------- | length | The length of the input data in Bytes. | | | This must be a multiple of the block size (16 Bytes). | --------------------------------------------------------------------------------------------------- | iv | Initialization vector (updated after use). | | | It must be a readable and writeable buffer of 16 Bytes. | --------------------------------------------------------------------------------------------------- | input | The buffer holding the input data. It must be readable and of size length Bytes. | --------------------------------------------------------------------------------------------------- | output | The buffer holding the output data. It must be writeable and of size length Bytes. | ---------------------------------------------------------------------------------------------------
아래는 실재 구현에 도움이 될 만한 사이트들 입니다. 예제들이 설명되어 있어요.
* ESP32 Arduino: Encryption using AES-128 in ECB mode
- https://techtutorialsx.com/2018/04/18/esp32-arduino-encryption-using-aes-128-in-ecb-mode/
* ESP32 Arduino Tutorial: Encryption AES128 in ECB mode
- https://everythingesp.com/esp32-arduino-tutorial-encryption-aes128-in-ecb-mode/
#include "mbedtls/aes.h"
"hwcrypto/aes.h" 라이브러리도 동일한 parameter 와 동작을 보여줍니다. 함수명에 mbedtls 가 붙느냐, esp가 붙느냐의 차이 뿐.
#include "hwcrypto/aes.h"
위의 두 가지 library 는 따로 설치하지 않아도 되는걸 보면, native library 이면서 서로가 copy 버전이 아닐까 하네요.
6. Hardware AES - 확인
* cnlohr/esp32_aes_example.c
- https://gist.github.com/cnlohr/96128ef4126bcc878b1b5a7586c624ef
#include "string.h" #include "stdio.h" #include "hwcrypto/aes.h" /* For Encryption time: 1802.40us (9.09 MB/s) at 16kB blocks. */ static inline int32_t _getCycleCount(void) { int32_t ccount; asm volatile("rsr %0,ccount":"=a" (ccount)); return ccount; } char plaintext[16384]; char encrypted[16384]; void encodetest() { uint8_t key[32]; uint8_t iv[16]; //If you have cryptographically random data in the start of your payload, you do not need //an IV. If you start a plaintext payload, you will need an IV. memset( iv, 0, sizeof( iv ) ); //Right now, I am using a key of all zeroes. This should change. You should fill the key //out with actual data. memset( key, 0, sizeof( key ) ); memset( plaintext, 0, sizeof( plaintext ) ); strcpy( plaintext, "https://chocoball.tistory.com/entry/Hardware-ESP32-Cryptographic-hardware-acceleration" ); //Just FYI - you must be encrypting/decrypting data that is in BLOCKSIZE chunks!!! esp_aes_context ctx; esp_aes_init( &ctx ); esp_aes_setkey( &ctx, key, 256 ); int32_t start = _getCycleCount(); esp_aes_crypt_cbc( &ctx, ESP_AES_ENCRYPT, sizeof(plaintext), iv, (uint8_t*)plaintext, (uint8_t*)encrypted ); int32_t end = _getCycleCount(); float enctime = (end-start)/240.0; Serial.printf( "Encryption time: %.2fus (%f MB/s)\n", enctime, (sizeof(plaintext)*1.0)/enctime ); //See encrypted payload, and wipe out plaintext. memset( plaintext, 0, sizeof( plaintext ) ); int i; for( i = 0; i < 128; i++ ) { Serial.printf( "%02x[%c]%c", encrypted[i], (encrypted[i]>31)?encrypted[i]:' ', ((i&0xf)!=0xf)?' ':'\n' ); } Serial.printf( "\n" ); //Must reset IV. //XXX TODO: Research further: I found out if you don't reset the IV, the first block will fail //but subsequent blocks will pass. Is there some strange cryptoalgebra going on that permits this? Serial.printf( "IV: %02x %02x\n", iv[0], iv[1] ); memset( iv, 0, sizeof( iv ) ); //Use the ESP32 to decrypt the CBC block. Serial.print("- decryption time [us]: "); unsigned long ms = micros (); esp_aes_crypt_cbc( &ctx, ESP_AES_DECRYPT, sizeof(encrypted), iv, (uint8_t*)encrypted, (uint8_t*)plaintext ); Serial.println(micros() - ms); //Verify output for( i = 0; i < 128; i++ ) { Serial.printf( "%02x[%c]%c", plaintext[i], (plaintext[i]>31)?plaintext[i]:' ', ((i&0xf)!=0xf)?' ':'\n' ); } Serial.printf( "\n" ); esp_aes_free( &ctx ); } void setup() { // put your setup code here, to run once: Serial.begin(115200); encodetest(); } void loop() { // put your main code here, to run repeatedly: }
결과는 다음과 같습니다.
위의 Software AES 와 비슷하게 결과를 내도록 소스를 만들면 확연히 비교할 수 있겠으나, 공부를 더 해야 함.
다만, 최종적인 처리 속도는 결코 아래 결과에서 변하지 않는다는 것을 여러 삽질을 통해 발견했으니 이걸로 만족.
Hardware AES 를 서포트하는 전용 명령어 set 이 최적화가 되지 않아, 이런 결과가 나온 것인지 모르겠네요.
지금으로써는 전용 accelerator 를 사용하지 않고, Software AES 를 구현하는 것이 더 속도적인 이득이 있는 듯 보입니다.
단, 여러가지 일을 동시에 처리해야 할 경우, 암호화/복호화 처리 부분만 따로 분리하여 HW accelerator 를 이용한다면, CPU 부하를 분산시켜 효율적인 활용은 가능할 것 같네요.
FIN
혹시, 위의 Hardware AES 결과가 잘못된 방법으로 검증된 것이라면 댓글로 알려주세요.
esp32_technical_reference_manual_en.pdf
Clock 사이클을 바탕으로 이론적인 계산을 해보면 68ns 레벨이라고, 아래 블로그에서 봤는데, 실측과는 많이 다르군요.
* Pwn the ESP32 crypto-core
- https://limitedresults.com/2019/08/pwn-the-esp32-crypto-core/
Doing simple math, the HW AES-128 encryption process is 68.75ns (@160MHz).
그렇다고 합니다.
'Hardware' 카테고리의 다른 글
Hardware | ESP32 NTP Server 이용한 시간 맞추기 (0) | 2020.04.21 |
---|---|
Hardware | ESP32 Deep sleep 알아보기 (0) | 2020.04.18 |
Hardware | EPS32 PWM 기능 확인해 보기 (0) | 2020.04.11 |
Hardware | ESP32 의 internal sensor 확인해 보기 (2) | 2020.04.09 |
Hardware | ESP32 의 Dual core 확인해 보기 (2) | 2020.04.08 |