본문으로 건너뛰기
ESP32-C3 Mastering · 11/12

ESP32-C3 보안 분석 — Secure Boot·Flash Encryption·eFuse

· Hawk · 9분 읽기

#한 줄 요약

“Secure Boot는 코드가 진짜 우리 것임을 증명하고, Flash Encryption은 플래시를 읽혀도 의미가 없게 합니다. eFuse는 둘의 키를 영구히 보관하는 OTP 비트입니다.” 한 번 Release 모드로 봉인하면 되돌릴 수 없습니다. 양산 라인에 올리기 전 워크플로를 반드시 손에 익혀야 합니다.

ESP32-C3의 보안 모델은 3중 레이어입니다. Secure Boot V2가 부팅 체인을 검증하고, Flash Encryption이 플래시 내용을 AES-XTS로 암호화하며, eFuse가 두 시스템의 키와 정책 비트를 저장합니다. ESP32-C3는 원본 ESP32와 달리 RSA가 아니라 ECDSA-256(P-256) 서명을 씁니다.

이번 장에서는 eFuse의 구조, Secure Boot V2 키 생성·서명·burn 절차, Flash Encryption Development vs Release 모드, anti-rollback, HMAC peripheral 사용, 그리고 영구 brick을 안 만드는 워크플로까지 다룹니다.

#eFuse — 일회성 비트의 보관소

eFuse는 0 → 1로만 바꿀 수 있는 OTP 비트입니다. 한 번 burn하면 영원히 1입니다. ESP32-C3에는 11개 블록이 있습니다.

블록용도비고
BLOCK0시스템 설정 비트reserved 영역 다수
BLOCK1MAC 주소, 칩 버전공장 출하 시 burn
BLOCK2System data공장 출하 시 burn
BLOCK3User data사용자가 자유 사용
BLOCK4~BLOCK9Key blocks 0~5Secure Boot, Flash Encryption, HMAC, DS 키
BLOCK10시스템 (calibration 등)공장 출하 시 burn

키 블록 6개는 256-bit씩 6개의 서로 다른 키를 보관할 수 있습니다. 각 블록은 용도가 별도로 지정됩니다.

Terminal window
# 칩의 eFuse 상태 확인
espefuse.py --port /dev/ttyUSB0 summary
EFUSE_NAME (Block) Description = [Value] [Readable/Writeable]
=== Generated from "esp_efuse_table.csv" ===
WR_DIS (BLOCK0) Disable programming of individual eFuses = 0 R/W
RD_DIS (BLOCK0) Disable reading from BlOCK4-10 = 0 R/W
SECURE_BOOT_EN (BLOCK0) Enable Secure Boot = False R/W
SECURE_BOOT_AGGRESSIVE_REVOKE (BLOCK0) Enable aggressive Secure Boot key revocation = False R/W
SPI_BOOT_CRYPT_CNT (BLOCK0) Enable encryption of SPI flash boot = Disabled R/W
KEY_PURPOSE_0 (BLOCK0) KEY0 purpose = USER R/W
KEY_PURPOSE_1 (BLOCK0) KEY1 purpose = USER R/W
...

eFuse burn은 되돌릴 수 없으니 출력을 반드시 dry-run해 본 뒤 실행합니다.

Terminal window
espefuse.py --port /dev/ttyUSB0 burn_efuse SECURE_BOOT_EN 1
# 확인 프롬프트가 뜸. y를 누른 뒤에야 실제 burn.

#Secure Boot V2 — ECDSA 체인 검증

Secure Boot V2는 3단계 체인을 ECDSA-256으로 검증합니다.

단계검증
ROM bootloader (마스크롬, 변경 불가)Boot ROM이 BOOTLOADER 영역의 서명을 검증
Secondary bootloader (사용자 빌드, 서명 됨)자체적으로 partition table 서명 검증
Partition table (서명 됨)(다음 단계가 검증)
Application (서명 됨)bootloader가 검증

각 단계의 서명은 공통 ECDSA-256 공개키로 검증됩니다. 공개키의 SHA-256 digest가 eFuse의 SECURE_BOOT_V2_KEY_DIGEST에 burn되어 있습니다. 최대 3개의 digest를 보관할 수 있어, 키 회전이 가능합니다.

#키 생성과 첫 빌드

Terminal window
# ECDSA-256 서명 키 생성
espsecure.py generate_signing_key --version 2 --scheme ecdsa256 \
secure_boot_signing_key.pem
# menuconfig
idf.py menuconfig
# Security features →
# Enable hardware Secure Boot in bootloader [*]
# Secure bootloader mode → One-time flash
# Sign binaries during build [*]
# Secure boot private signing key → secure_boot_signing_key.pem

빌드하면 bootloader, partition-table, app 세 binary가 자동 서명됩니다.

Terminal window
idf.py build
# 결과:
# build/bootloader/bootloader.bin (서명됨, 64-byte trailer)
# build/partition_table/partition-table.bin (서명됨)
# build/my_app.bin (서명됨)

#첫 플래시

처음에는 모든 영역을 보통의 esptool로 씁니다.

Terminal window
idf.py -p /dev/ttyUSB0 flash
# 부팅 시 bootloader가 자동으로:
# 1. 자기 공개키 digest를 eFuse에 burn (없으면)
# 2. partition table·app 서명 검증
# 3. SECURE_BOOT_EN 비트를 burn
# 이후로는 서명된 binary만 받아들임

#펌웨어 업데이트

이후의 모든 새 binary는 동일 키로 서명되어야 합니다.

Terminal window
# 새 빌드는 자동 서명됨
idf.py -p /dev/ttyUSB0 app-flash
# 또는 수동으로 OTA에 사용
espsecure.py sign_data --version 2 --keyfile secure_boot_signing_key.pem \
--output my_app_signed.bin my_app.bin

서명되지 않은 binary는 부팅 시 거부되고 reset 루프에 들어갑니다.

#Flash Encryption — AES-256 XTS

Flash Encryption은 플래시 칩 자체를 AES-XTS로 암호화합니다. 누군가 플래시를 떼서 직접 읽어도 의미 있는 데이터가 나오지 않습니다.

항목
알고리즘AES-XTS, 256-bit
키 위치eFuse BLOCK4~9 중 하나
키 노출외부 read 불가 (한 번 burn 후 RD_DIS=1)
암호화 단위32-byte sector
Tweak플래시 주소 기반

XTS의 tweak가 주소이므로 같은 평문이라도 위치가 다르면 암호문이 다릅니다. ECB 같은 패턴 누출이 없습니다.

#Development 모드

개발 단계에서는 재플래시가 필요합니다. Development 모드는 encryption은 켜되 추가 플래시를 허용합니다.

sdkconfig
CONFIG_SECURE_FLASH_ENC_ENABLED=y
CONFIG_SECURE_FLASH_ENCRYPTION_MODE_DEVELOPMENT=y
CONFIG_SECURE_FLASH_REQUIRE_ALREADY_ENABLED=n
Terminal window
# 첫 플래시
idf.py -p /dev/ttyUSB0 flash
# bootloader가 부팅 시 자동으로:
# 1. 키 생성 (random) 후 eFuse에 burn
# 2. 플래시의 bootloader / partition table / app 영역을 in-place 암호화
# 3. SPI_BOOT_CRYPT_CNT를 1로 burn (Development = 홀수)

이후 idf.py encrypted-flash새 binary를 호스트에서 미리 암호화해서 씁니다. 또는 idf.py flash평문 binary를 보내면 부트로더가 부팅 시 다시 암호화합니다.

Terminal window
# 호스트 측 사전 암호화
espsecure.py encrypt_flash_data --keyfile flash_encryption_key.bin \
--address 0x10000 --output app_encrypted.bin my_app.bin

#Release 모드 — 일방향 봉인

양산용입니다. Development의 재플래시 허용 비트를 모두 끕니다.

sdkconfig
CONFIG_SECURE_FLASH_ENCRYPTION_MODE_RELEASE=y
Terminal window
idf.py -p /dev/ttyUSB0 flash
# bootloader가 자동으로 (Development와 다름):
# 1. SPI_BOOT_CRYPT_CNT를 3 또는 7로 burn (Release = 마지막 비트도 burn)
# 2. EFUSE_DIS_DOWNLOAD_MODE를 burn (UART download 영구 차단)
# 3. EFUSE_HARD_DIS_JTAG를 burn (JTAG 영구 차단)
# 4. 키 블록의 RD_DIS·WR_DIS를 burn (키 읽기·쓰기 영구 차단)

이후로 호스트가 펌웨어를 갱신할 유일한 방법OTA입니다. UART, JTAG, esptool은 모두 차단됩니다.

#Anti-rollback — 구버전 펌웨어 방지

공격자가 알려진 취약점이 있는 옛 버전을 다시 설치하지 못하게 막습니다. secure_version 필드를 app descriptor에 박고, eFuse의 SECURE_VERSION 카운터현재 이상인 펌웨어만 부팅합니다.

sdkconfig
CONFIG_BOOTLOADER_APP_ANTI_ROLLBACK=y
CONFIG_BOOTLOADER_APP_SEC_VER=3

OTA에서 새 펌웨어가 더 높은 secure_version이면 부팅이 성공하고 eFuse 카운터가 burn됩니다. 한 번 burn된 카운터는 되돌릴 수 없으니, secure_version은 신중히 증가시킵니다. 너무 자주 올리면 카운터 공간이 빨리 소진됩니다.

VersioneFuse 비트비고
0~3132-bit 카운터 단방향C3 기준

ESP32-C3는 최대 32까지 anti-rollback이 가능합니다. 제품 수명 동안 보안 업데이트 횟수를 미리 가늠해 증분 정책을 세워야 합니다.

#HMAC와 Digital Signature peripheral

ESP32-C3는 하드웨어 HMAC 가속기Digital Signature(DS) peripheral을 갖습니다.

#include "esp_hmac.h"
uint8_t hmac_out[32];
const char *msg = "challenge_data";
ESP_ERROR_CHECK(esp_hmac_calculate(HMAC_KEY0, msg, strlen(msg), hmac_out));

HMAC 키도 eFuse의 키 블록에 저장됩니다. KEY_PURPOSEHMAC_UP 또는 HMAC_DOWN으로 burn해야 사용할 수 있습니다. 키는 외부에서 읽을 수 없습니다. 호스트가 키를 알지 못해도, 디바이스가 알맞은 HMAC 응답을 생성할 수 있어 디바이스 인증에 쓰입니다.

Digital Signature(DS) peripheral은 RSA 서명을 하드웨어 가속합니다. 클라우드 TLS 클라이언트 인증서의 private key를 평문이 아닌 암호화된 형태로 플래시에 두고, DS peripheral이 부팅 시 복호화해 사용합니다. AWS IoT, Azure IoT, Google Cloud IoT 같은 mTLS 환경에 핵심입니다.

#Development → Release 전환 워크플로

가장 흔한 영구 brick 시나리오Development에서 충분히 확인하지 않고 바로 Release로 봉인하는 것입니다. 권장 워크플로입니다.

단계활동
1. Development 모드 1차 검증sdkconfig SECURE_FLASH_ENCRYPTION_MODE_DEVELOPMENT · Secure Boot은 켜고 키 burn은 안 함 · 펌웨어가 잘 부팅·OTA되는지 확인
2. 키 생성과 백업secure_boot_signing_key.pem 생성 · HSM 또는 안전한 키 저장소에 백업 · 키 분실 = 향후 OTA 불가 = 모든 디바이스 영구 동결
3. Development 모드에서 Secure Boot 활성SECURE_BOOT_EN burn · 서명된 binary로 재플래시 · bootup·OTA 모두 검증
4. Release 모드로 전환 (한 디바이스에만 먼저)sdkconfig SECURE_FLASH_ENCRYPTION_MODE_RELEASE · 한 디바이스에서 24~48시간 검증 · OTA, BLE provisioning, 정상 운영 시나리오 전부
5. 양산 라인 적용JTAG·UART 차단됨 · 디버그 불가, 모든 갱신은 OTA만

#영구 brick 시나리오

  • 키 분실 — secure_boot_signing_key.pem을 잃으면 새 펌웨어를 서명할 수 없습니다. 모든 디바이스가 현재 버전에 고정됩니다.
  • eFuse 오버 burnWR_DIS 비트를 잘못 burn해 원하는 영역의 추가 수정이 차단됩니다.
  • Release 모드의 OTA 버그 — OTA 코드에 버그가 있는 상태로 Release로 봉인하면, 영원히 그 버그와 함께 살아야 합니다. UART·JTAG가 차단되어 복구 불가.
  • secure_version 오 burn — 너무 높은 값을 burn하면 모든 이전 버전 펌웨어가 부팅 거부됩니다.

이런 시나리오를 피하는 핵심은 Release 봉인 전에 Development 모드에서 충분히 검증입니다. 특히 OTA 경로반드시 한 번은 통과시킨 뒤 Release로 갑니다.

#자주 하는 실수와 troubleshooting

증상원인해결
서명된 binary가 부팅 거부공개키 digest mismatchkeyfile 동일성 확인, 처음부터 다시
”Flash encryption is in DEVELOPMENT”release로 안 봉인됨의도적이면 정상, 양산이면 RELEASE로
JTAG 안 붙음Release 모드라 차단됨Release 봉인 후엔 JTAG 영구 불가
OTA가 “image header magic” 에러서명·암호화 누락espsecure.py sign + encrypt
secure_version mismatcheFuse counter가 현재보다 큼해당 디바이스 영구 폐기
새 binary가 너무 큼 → 파티션 오버encrypted binary는 약간 더 큼OTA 슬롯 크기를 여유 있게
키 분실백업 안 함해당 lot 전부 OTA 동결
부팅 직후 무한 resetpartition table 서명 누락CONFIG_SECURE_BOOT_BUILD_SIGNED

가장 비싼 실수는 키 분실입니다. 서명 키는 hardware security module(HSM) 또는 air-gapped 시스템에 보관하는 것이 표준입니다. 여러 사람이 서로 다른 사본을 보관해 single point of failure를 없앱니다.

#정리

  • ESP32-C3의 보안은 3중 레이어입니다. Secure Boot V2(ECDSA-256), Flash Encryption(AES-256 XTS), eFuse(OTP 키 저장).
  • eFuse는 0 → 1로만 burn되는 OTP입니다. 한 번 burn하면 영원히 1이고, 신중하지 않으면 영구 brick입니다.
  • Secure Boot V2는 bootloader → partition table → app 3단계 체인을 ECDSA-256으로 검증합니다.
  • Flash Encryption은 AES-XTS로 플래시 자체를 암호화합니다. tweak가 주소라 같은 평문도 위치마다 암호문이 다릅니다.
  • Flash Encryption은 Development(재플래시 가능)와 Release(JTAG·UART 영구 차단) 두 모드입니다. 한 번 Release로 가면 되돌릴 수 없습니다.
  • Anti-rollback은 secure_version 카운터로 옛 펌웨어 재설치를 차단합니다. C3는 최대 32 단계까지 가능합니다.
  • HMAC peripheral과 DS peripheral은 키를 외부에 노출하지 않고 인증·서명에 쓰입니다. AWS IoT mTLS 같은 시나리오의 핵심입니다.
  • Development → Release 워크플로를 미리 손에 익혀야 영구 brick을 피합니다. 키 백업은 복수 사본이 표준입니다.

#다음 편

Ch 12: 전력 관리 — Modem/Light/Deep Sleep와 Wake 소스는 이 시리즈의 마지막 장입니다. 보안된 펌웨어를 배터리로 1년 이상 굴리는 전력 모델, deep sleep과 RTC 도메인, wake 소스를 정리합니다.

#관련 항목