BLE 보안 연결과 페어링

BLEIoT

BLE 연결은 기본적으로 암호화되지 않는다. 주변의 누구나 GATT 데이터를 스니핑할 수 있다. 페어링은 두 디바이스가 암호화 키를 교환하는 과정이고, 본딩은 그 키를 저장해서 재연결 시 다시 페어링하지 않는 것이다.

쉽게 말하면: BLE는 기본적으로 엽서와 같다. 누구나 내용을 읽을 수 있다. 페어링은 봉투에 넣고 봉인하는 것이고, 본딩은 다음에 또 보낼 때 같은 봉인을 재사용하는 것이다.


보안 없이 연결하면 어떻게 되는가

BLE 스니퍼(nRF Sniffer, Ubertooth 등)를 켜면 주변 BLE 디바이스의 advertising과 connection 데이터를 모두 볼 수 있다.

# Wireshark + nRF Sniffer로 캡처되는 것들:
- GATT Read/Write 데이터 (센서 값, 명령어)
- Notify/Indicate 데이터
- 디바이스 이름, 서비스 UUID
- 연결 파라미터

암호화 없이 BLE로 도어락 비밀번호, 의료 데이터, 결제 정보를 전송하면 평문으로 노출된다. 보안이 필요한 모든 BLE 어플리케이션은 페어링이 필수다.

카페에서 큰 소리로 통화하는 것과 같다. 옆 테이블에서 다 들린다. BLE 스니퍼는 10만원짜리 USB 동글이면 충분하다.


페어링 과정

페어링은 3단계로 진행된다. 처음 만난 두 사람이 암호 통신을 시작하는 과정이라고 생각하면 된다.

Phase 1: Feature Exchange (자기소개)
  양쪽이 I/O 능력(키보드, 디스플레이 유무)과 보안 요구사항을 교환
  → "나는 화면이 있어" / "나는 버튼만 있어"

Phase 2: Key Generation (비밀 키 만들기)
  Legacy Pairing: TK → STK 생성 (취약)
  LE Secure Connections: ECDH → LTK 생성 (권장)
  → 양쪽이 수학적으로 같은 암호화 키를 만들어낸다

Phase 3: Key Distribution (키 저장)
  LTK, IRK, CSRK 등 장기 키를 교환
  본딩이 활성화되면 이 키들을 Flash에 저장
  → 다음에 만나면 이 키로 바로 암호화

Phase 1: I/O Capability

양쪽의 I/O 능력에 따라 페어링 방식이 자동 결정된다:

NoInput NoOutputDisplayOnlyDisplayYesNoKeyboardOnlyKeyboardDisplay
NoInput NoOutputJust WorksJust WorksJust WorksJust WorksJust Works
DisplayOnlyJust WorksJust WorksJust WorksPasskeyPasskey
DisplayYesNoJust WorksJust WorksNumeric Comp.PasskeyNumeric Comp.
KeyboardOnlyJust WorksPasskeyPasskeyPasskeyPasskey
KeyboardDisplayJust WorksPasskeyNumeric Comp.PasskeyNumeric Comp.

임베디드 디바이스는 대부분 NoInput/NoOutput → Just Works가 선택된다. MITM 방어가 안 된다.

표가 복잡해 보이지만 규칙은 단순하다: 양쪽 다 화면/키보드가 없으면 확인할 방법이 없으니 Just Works(무조건 연결)가 된다. 한쪽이라도 입력/확인 수단이 있으면 Passkey나 Numeric Comparison이 가능해진다.

페어링 방식별 보안

방식동작MITM 방어사용 상황
Just Works확인 없이 키 교환X디스플레이/키보드 없는 센서, 비콘
Passkey Entry6자리 숫자 입력O키패드가 있는 디바이스
Numeric Comparison양쪽 6자리 확인O디스플레이가 있는 양쪽 디바이스 (BLE 4.2+)
OOBNFC 등 다른 채널로 키 교환ONFC 터치로 페어링

비유로 정리하면:

  • Just Works = 모르는 사람이랑 악수만 하고 “우리 친구”. 중간에 누가 끼어도 모른다
  • Passkey = 비밀번호를 아는 사람만 들어올 수 있는 문
  • Numeric Comparison = 양쪽이 같은 숫자를 보고 “이거 맞지?” 확인. 중간자가 끼면 숫자가 달라진다
  • OOB (NFC) = 물리적으로 터치해야 연결. 원격 공격이 불가능

Legacy Pairing vs LE Secure Connections

Legacy Pairing (BLE 4.0~4.1)

1. TK (Temporary Key) 생성
   - Just Works: TK = 0 (고정!)
   - Passkey: TK = 입력된 6자리 숫자 (최대 999,999)

2. TK로 STK (Short-Term Key) 생성
   STK = s1(TK, Srand, Mrand)

3. STK로 연결 암호화

4. 암호화된 채널에서 LTK 배포

문제점:

  • Just Works의 TK = 0. 페어링 과정을 캡처하면 STK를 역산할 수 있다
  • Passkey의 TK도 20비트(0~999,999). 브루트포스로 수 초 내 해독 가능
  • 페어링 과정을 스니핑하면 이후 모든 통신을 복호화할 수 있다 (passive eavesdropping)

Legacy의 근본적 문제: 비밀 키(TK)가 너무 약하다. Just Works는 TK가 0(= 비밀번호 없음)이고, Passkey도 최대 6자리 숫자(100만 가지)라서 컴퓨터로 수 초면 다 시도할 수 있다. 페어링 과정을 녹화해뒀다가 나중에 해독하는 것도 가능하다.

LE Secure Connections (LESC, BLE 4.2+)

1. ECDH 키 교환
   양쪽이 P-256 공개키를 교환 → 공유 시크릿 DHKey 생성

2. DHKey로 LTK 직접 생성
   LTK = f5(DHKey, N_master, N_slave, BD_ADDR_master, BD_ADDR_slave)

3. 인증
   - Just Works: 확인 없음
   - Passkey: 6자리 숫자로 DHKey 검증 (비트별 검증, 20라운드)
   - Numeric Comparison: 양쪽 6자리 확인

4. LTK로 연결 암호화 (AES-CCM)

LESC가 나은 이유:

  • ECDH라서 패시브 스니핑으로 키를 역산할 수 없다 (forward secrecy)
  • STK 단계가 없다. LTK를 직접 생성
  • Passkey도 비트별 20라운드 검증이라 브루트포스 불가

ECDH를 쉽게 설명하면: 두 사람이 각각 비밀 숫자를 가지고, 공개 숫자만 교환한다. 수학적 성질 덕분에 양쪽 모두 같은 공유 키를 계산할 수 있지만, 옆에서 공개 숫자만 봐서는 비밀 숫자를 역산할 수 없다. Legacy는 약한 비밀번호(TK)를 직접 기반으로 키를 만들지만, LESC는 ECDH로 강력한 키를 만들어서 도청만으로는 해독이 불가능하다.

LegacyLESC
키 교환TK → STK → LTKECDH → LTK
패시브 스니핑취약 (TK 브루트포스)안전 (ECDH)
Just Works MITM취약취약 (여전히)
Passkey 강도20비트20라운드 검증
최소 BLE 버전4.04.2

Just Works는 LESC에서도 MITM에 취약하다. 액티브 MITM(중간자가 양쪽과 각각 페어링)은 어떤 방법으로도 I/O 확인 없이는 막을 수 없다.

한 줄 요약: LESC는 도청은 막지만, 중간자 공격은 못 막는다. 중간자를 막으려면 사람이 직접 “이 상대가 맞는지” 확인하는 단계(Passkey, Numeric Comparison)가 반드시 필요하다.


본딩

페어링 후 교환된 키를 Flash에 저장하는 것이 본딩이다. 재연결 시 저장된 LTK로 즉시 암호화한다.

블루투스 이어폰을 한번 페어링하면 다음부터 자동 연결되는 것이 본딩이다. 처음 만든 암호화 키를 양쪽 Flash에 저장해두고, 재연결 시 그 키를 꺼내서 바로 암호화한다.

저장되는 키

용도
LTK연결 암호화. 가장 중요
IRKRPA 주소 해석. 프라이버시
CSRK서명 검증 (거의 사용 안 함)
Identity Address상대방의 실제 주소 (public 또는 static random)

본딩 정보 관리

// STM32WB: 본딩 테이블 크기 설정
#define CFG_BLE_NUM_LINK    2    /* 동시 연결 수 */
#define CFG_BONDING_NUMBER  10   /* 최대 본딩 디바이스 수 */

// 본딩 삭제 (팩토리 리셋)
aci_gap_clear_security_db();

재연결 시퀀스:

1. Peripheral이 RPA로 advertising
2. Central이 IRK로 RPA resolve → 알려진 디바이스 확인
3. 연결
4. Central이 Encryption Request (저장된 LTK의 EDIV, Rand 전송)
5. Peripheral이 EDIV/Rand로 LTK 찾음
6. AES-CCM 암호화 활성화
7. 페어링 없이 암호화된 연결 완료

보안 레벨

BLE는 Security Mode 1에 4개 레벨을 정의한다:

레벨암호화인증의미
Level 1XX암호화 없음
Level 2OX암호화, 인증 없음 (Just Works)
Level 3OO암호화 + MITM 방어 (Passkey/Numeric/OOB)
Level 4OOLevel 3 + LESC 필수 (BLE 4.2+)

집에 비유하면:

  • Level 1 = 문이 없는 집
  • Level 2 = 자물쇠는 있는데, 누가 잠갔는지 확인 안 함 (도청 방어만)
  • Level 3 = 자물쇠 + 신분증 확인 (도청 + 중간자 방어)
  • Level 4 = 가장 튼튼한 자물쇠(LESC) + 신분증 확인

Characteristic에 보안 요구 설정

// STM32WB: Characteristic에 보안 레벨 지정
aci_gatt_add_char(
    service_handle,
    UUID_TYPE_128, &char_uuid,
    20,                              /* max length */
    CHAR_PROP_READ | CHAR_PROP_WRITE,
    ATTR_PERMISSION_ENCRY_READ |     /* 읽기: 암호화 필수 */
    ATTR_PERMISSION_AUTHEN_WRITE,    /* 쓰기: 인증(MITM 방어) 필수 */
    GATT_NOTIFY_ATTRIBUTE_WRITE,
    10,
    CHAR_VALUE_LEN_VARIABLE,
    &char_handle
);
퍼미션의미
ATTR_PERMISSION_NONELevel 1: 누구나 접근
ATTR_PERMISSION_ENCRY_READLevel 2+: 암호화된 연결 필요
ATTR_PERMISSION_AUTHEN_READLevel 3+: 인증된 페어링 필요

암호화 없는 연결에서 ENCRY_READ characteristic을 읽으려 하면 스택이 자동으로 페어링을 시작한다 (Security Request → Pairing).


STM32WB 페어링 설정

초기화

// I/O Capability 설정
aci_gap_set_io_capability(IO_CAP_DISPLAY_YES_NO);

// 인증 요구사항
aci_gap_set_authentication_requirement(
    1,           /* bonding_mode: 1 = bonding */
    1,           /* mitm_mode: 1 = MITM 보호 요구 */
    1,           /* sc_support: 1 = LE Secure Connections */
    0,           /* keypress_notification */
    16,          /* enc_key_size_min: 최소 암호화 키 크기 */
    16,          /* enc_key_size_max */
    1,           /* use_fixed_pin: 1 = 고정 핀 사용 */
    123456,      /* fixed_pin */
    0x00         /* identity_address_type: public */
);

페어링 이벤트 처리

void aci_gap_pairing_complete_event(uint8_t conn_handle,
                                     uint8_t status,
                                     uint8_t reason)
{
    if (status == 0x00) {
        /* 페어링 성공 — 본딩 키가 Flash에 저장됨 */
    } else if (status == 0x01) {
        /* 페어링 타임아웃 */
    } else if (status == 0x02) {
        /* 페어링 실패 */
        switch (reason) {
        case 0x01: /* Passkey entry failed */ break;
        case 0x02: /* OOB not available */ break;
        case 0x03: /* Authentication requirements not met */ break;
        case 0x04: /* Confirm value failed */ break;
        case 0x05: /* Pairing not supported */ break;
        case 0x06: /* Encryption key size too short */ break;
        }
    }
}

void aci_gap_numeric_comparison_value_event(uint8_t conn_handle,
                                             uint32_t numeric_value)
{
    /* 디스플레이에 numeric_value (6자리) 표시 */
    /* 사용자가 확인하면: */
    aci_gap_numeric_comparison_value_confirm_yesno(conn_handle, 0x01);
}

void aci_gap_pass_key_req_event(uint8_t conn_handle)
{
    /* Passkey 입력 요청 — 사용자가 입력한 값을 전달 */
    aci_gap_pass_key_resp(conn_handle, 123456);
}

보안 요청 (Peripheral → Central)

Peripheral이 먼저 암호화를 요청할 수 있다:

// 연결 후 보안 요청 전송
aci_gap_slave_security_req(conn_handle);
// → Central이 이미 본딩되어 있으면 LTK로 암호화
// → 본딩 안 되어 있으면 페어링 시작

MITM 공격 시나리오

두 가지 공격 유형을 구분해야 한다. 수비 전략이 완전히 다르다.

패시브 스니핑 (도청)

공격자가 중간에서 듣기만 한다. 통신에 개입하지 않는다.

[디바이스 A] ←── BLE ──→ [디바이스 B]

              [스니퍼]
              (패킷 캡처만)
  • Legacy Just Works: TK=0이므로 STK/LTK 역산 가능. 이후 모든 데이터 복호화
  • Legacy Passkey: TK가 20비트. 오프라인 브루트포스로 해독
  • LESC: ECDH라서 패시브 스니핑으로는 불가능

방어: LESC를 켜면 도청은 막을 수 있다. ECDH 덕분에 옆에서 아무리 들어도 키를 알아낼 수 없다.

액티브 MITM (중간자 공격)

공격자가 양쪽 사이에 끼어들어, 각각과 별도로 페어링한다. 양쪽 모두 정상 연결이라고 착각한다.

[디바이스 A] ←── BLE ──→ [공격자] ←── BLE ──→ [디바이스 B]
                         (양쪽과 각각 페어링)
  • Just Works (Legacy/LESC 모두): 사용자 확인이 없으므로 MITM 성공
  • Passkey: 공격자가 passkey를 모르면 실패
  • Numeric Comparison: 양쪽에 다른 숫자가 표시되므로 사용자가 거부 가능
  • OOB (NFC): 물리적 근접이 필요하므로 MITM 매우 어려움

방어: 사람이 직접 확인하는 수밖에 없다. LESC든 뭐든 암호화 알고리즘만으로는 “내가 진짜 상대와 연결된 건지” 확인할 수 없다. Passkey나 Numeric Comparison처럼 사람이 숫자를 보고 확인하는 단계가 있어야 중간자가 끼었을 때 알아챌 수 있다.

결론: Just Works는 편리하지만 MITM에 무방비. 보안이 중요하면 최소 Passkey, 이상적으로는 Numeric Comparison 또는 OOB를 사용한다.


실전 보안 설계

제품 유형권장 설정
센서/비콘 (데이터 민감하지 않음)Just Works + LESC, Level 2
웨어러블 (폰과 1:1 연결)Passkey + LESC + 본딩, Level 4
도어락/결제OOB(NFC) 또는 Numeric Comparison + LESC, Level 4
의료기기Numeric Comparison + LESC + 본딩, Level 4

공통: LESC는 항상 켠다. Legacy Pairing을 허용할 이유가 없다 (BLE 4.2 이전 디바이스 호환이 필요한 경우 제외).

제품 설계 시 핵심 질문: “내 디바이스에 화면이나 버튼이 있는가?” 없으면 Just Works밖에 선택지가 없고, MITM은 포기하는 대신 LESC로 도청만 막는다. 보안이 정말 중요하면 하드웨어에 버튼/디스플레이를 추가하거나 NFC를 붙여야 한다.


메모

  • “Just Works도 암호화는 된다”
    • Just Works로 페어링해도 AES-CCM 암호화는 활성화된다. 패시브 스니핑은 LESC 사용 시 방어된다
    • 문제는 MITM. 공격자가 중간에서 relay하면 양쪽 모두 정상 연결로 착각
    • 보안 요구가 낮은 디바이스(온도 센서 등)에서는 Just Works + LESC로 충분할 수 있다
  • 본딩 테이블 가득 찼을 때
    • STM32WB 기본: 가장 오래된 본딩을 삭제하고 새 디바이스를 추가
    • 일부 스택은 페어링을 거부한다. CFG_BONDING_NUMBER를 적절히 설정
  • iOS/Android 페어링 동작 차이
    • iOS: Just Works 페어링 시 사용자에게 팝업을 띄우지 않는다. 자동으로 진행
    • Android: Just Works도 “페어링 하시겠습니까?” 팝업을 띄운다
    • iOS에서 Passkey를 사용하려면 Peripheral이 DisplayOnly 이상의 I/O capability를 설정해야 한다
  • sc_support = 1로 설정해도 상대가 LESC를 지원하지 않으면 Legacy로 폴백
    • sc_support = 2 (SC only)로 설정하면 Legacy 폴백을 차단한다. 오래된 폰에서 연결 실패할 수 있음
  • 고정 Passkey의 위험
    • use_fixed_pin = 1, fixed_pin = 123456은 개발용이다
    • 프로덕션에서 고정 passkey를 쓰면 한 번 알려지면 모든 디바이스에 적용 가능
    • 디바이스마다 다른 passkey를 생성하거나 Numeric Comparison을 사용
  • RPA와 보안의 관계
    • 본딩 시 교환한 IRK로 RPA를 해석하여 재연결 상대를 식별한다
    • RPA 없이 고정 주소를 쓰면 본딩과 무관하게 디바이스 추적이 가능하다