Classic 보안: 레거시 PIN에서 Secure Connections까지
BR/EDR 보안은 세 번 바뀌었다. 처음에는 PIN과 SAFER+ 블록 암호로 키를 만들고 E0 스트림 암호로 암호화했다. 2.1의 Secure Simple Pairing(SSP)은 타원곡선 Diffie-Hellman과 HMAC-SHA-256으로 키 합의를 바꿨다. 4.1의 Secure Connections는 곡선을 P-256으로 올리고 장치 인증과 암호화를 AES로 바꿨다. 이 장은 세 세대의 함수를 모두 구현해 Part G의 예제 데이터와 맞춘다. 공격 절차가 아니라 각 방식이 무엇을 지키고 왜 바뀌었는지가 목표다.
- 레거시·SSP·Secure Connections가 각각 어떤 키 합의·인증·암호화를 쓰는지 표로 구분할 수 있다.
- SAFER+ 기반 E22·E21·E1·E3의 입력과 출력(K_init, 결합 키, SRES·ACO, K_enc)을 설명할 수 있다.
- SSP의 f1(약속)·g(숫자 비교)·f2(링크 키)·f3(확인)과 IOcap 인코딩을 따라갈 수 있다.
- Secure Connections의 h4·h5·h3과 BR/EDR AES-CCM의 nonce·B1 형식을 적용할 수 있다.
세 세대 한눈에
| 레거시 (≤ 2.0) | SSP (2.1) | Secure Connections (4.1) | |
|---|---|---|---|
| 키 합의 | PIN + E22 → K_init, E21 결합 키 | P-192 ECDH + f1·g·f3, f2 → 링크 키 | P-256 ECDH, 같은 f 함수 |
| 장치 인증 | E1 (SAFER+) → SRES, ACO | E1 | h4 → h5 (HMAC-SHA-256) → SRES, ACO |
| 암호화 키 | E3 → K_enc | E3 | h3 → AES 키 |
| 암호화 | E0 스트림 암호 | E0 | AES-CCM (MIC 4바이트) |
바뀐 이유는 한 줄로 말할 수 있다. 레거시는 비밀이 PIN뿐이라 짧은 PIN이면 엿들은 교환으로 PIN을 추측해 볼 수 있었다. SSP는 비밀을 ECDH로 만들어 엿듣는 것만으로는 키를 얻을 수 없게 했고, 사람이 숫자를 비교하거나 패스키를 넣어 중간자를 막는다. Secure Connections는 곡선과 대칭 암호를 현대 표준으로 맞췄다. 그래서 GAP의 Secure Connections Only 모드는 BR/EDR의 서비스에 보안 모드 4 레벨 4(인증된 P-256 링크 키와 AES-CCM)를 요구한다.
레거시는 둘이 미리 정한 짧은 암호(PIN)를 섞어 열쇠를 깎는 것이다. 암호가 짧으면 엿들은 사람이 하나씩 대 보며 같은 열쇠를 깎아 볼 수 있다. SSP는 각자 비밀을 하나씩 갖고 공개된 재료만 주고받아 같은 열쇠를 만드는 방식(ECDH)이라, 엿들어도 열쇠를 못 깎는다. 다만 중간에서 둘 다인 척하는 사람을 막으려고 마지막에 화면의 여섯 자리 숫자를 서로 비교한다.
보안 개요와 세대: [Vol 2] Part H §1, §1.1 · 보안 모드 4와 Secure Connections Only: [Vol 3] Part C §5.2.2
레거시: SAFER+로 만든 E 함수들
레거시의 모든 키 함수는 128비트 블록 암호 Ar(= SAFER+)와 그 변형 A′r(1라운드 입력을 3라운드 입력에 다시 더해 역함수를 없앤 것)로 만든다. 한 라운드는 바이트마다 라운드 키를 XOR 또는 더하기 → e(45i mod 257) 또는 그 역 l → 다음 라운드 키를 더하기 또는 XOR → 2바이트 PHT(a, b → 2a+b, a+b) 4층과 그 사이 “아르메니아 셔플” 3번이다. 8라운드 뒤 17번째 키를 더한다. 라운드 키는 17바이트 레지스터를 매번 3비트 돌리고 바이어스 Bp[i] = 454517p+i+1를 더해 만든다.
- E22(K_init, 임시 키): PIN′ = PIN 뒤에 BD_ADDR을 붙여 최대 16바이트로, A′r(PIN′ 반복, RAND ⊕ L′·2120).
- E21(결합 키 재료): A′r(RAND ⊕ 6·2120, 주소 반복).
- E1(장치 인증): Hash(K, RAND, 주소, 6) = A′r(K̃, E(주소) +₁₆ (Ar(K, RAND) ⊕ RAND)). 앞 4바이트가 SRES(응답), 나머지 12바이트가 ACO.
- E3(암호화 키): Hash(K, EN_RAND, COF, 12) → K_enc. 그 뒤 협상한 키 길이로 줄여 E0에 넣는다.
레거시 E 함수 ([Vol 2] Part G §10)
E1과 Hash: [Vol 2] Part H §6.1 · Ar·A′r, 라운드, e·l, 키 스케줄: §6.2, §6.2.1~§6.2.3 · E21·E22: §6.3 · E3: §6.4 · K_init의 PIN 덧붙이기: §3.2.1 · 예제: [Vol 2] Part G §10.1~§10.5
Secure Simple Pairing: ECDH와 f1·g·f2·f3
SSP는 다섯 단계다. ① 공개 키 교환(PKa, PKb) ② 인증 1단계: 숫자 비교·Just Works·패스키·OOB 중 하나 ③ 인증 2단계: 확인 값 Ea·Eb 교환 ④ 링크 키 계산 ⑤ LMP 인증과 암호화. 함수는 모두 SHA-256 기반이다.
| 함수 | 정의 | 쓰임 |
|---|---|---|
| f1 | HMAC-SHA-256X(U ‖ V ‖ Z)의 앞 128비트 | 약속 Ca = f1(PKax, PKbx, Na, Z). Z = 0(숫자 비교·OOB), 0x80·0x81(패스키 비트 0·1) |
| g | SHA-256(U ‖ V ‖ X ‖ Y) mod 232 | 숫자 비교 값 = g(PKax, PKbx, Na, Nb) mod 106 |
| f2 | HMAC-SHA-256W(N1 ‖ N2 ‖ "btlk" ‖ A1 ‖ A2)의 앞 128비트 | 링크 키 = f2(DHKey, Nc, Np, "btlk", BD_ADDR_C, BD_ADDR_P) |
| f3 | HMAC-SHA-256W(N1 ‖ N2 ‖ R ‖ IOcap ‖ A1 ‖ A2)의 앞 128비트 | Ea = f3(DHKey, Na, Nb, r, IOcapA, A, B), Eb는 순서를 바꿔서 |
IOcap은 3바이트로, 위에서부터 Authentication_Requirements, OOB 데이터 유무, IO 능력이다. 확인 값에 IOcap이 들어가므로 중간자가 “나는 화면이 없다”고 속여 Just Works로 떨어뜨려도 Ea·Eb가 맞지 않는다.
SSP 계산 체인 (P-192 데이터 세트 1, [Vol 2] Part G §7)
Part G §7.2.2.2의 g() P-256 예는 적힌 입력(U = 20b003d2…, V = 55188b3d…, X = d5cb8454…, Y = a6e8e7cc…)으로 다시 계산하면 SHA-256의 하위 32비트가 0x5fda62e0이지만, 예에는 0x000240E0(147680)이 적혀 있다. 같은 방법으로 P-192 예(0x52146a1e)는 정확히 나오고 OpenSSL의 SHA-256도 같은 값을 내므로, 예의 입력이나 출력 중 하나가 잘못 옮겨진 것으로 보인다. 이 책의 검증 스크립트는 이 불일치를 그대로 기록해 둔다.
g의 출력이 0x52146a1e였다. 화면에 뜨는 여섯 자리는?
10진수로 바꾼 뒤 아래 여섯 자리.
SSP 단계: [Vol 2] Part H §7, §7.1~§7.3 · f1·g·f2·f3: §7.7.1~§7.7.4 · IO 능력과 인증 방식 대응: [Vol 3] Part C §5.2.2.4~§5.2.2.6 · 예제: [Vol 2] Part G §7.1, §7.2
Secure Connections: h4·h5·h3과 AES-CCM
P-256으로 링크 키를 만든 뒤 장치 인증은 E1 대신 h4(장치 인증 키 = HMACLK("btdk" ‖ A1 ‖ A2)) → h5(HMACS(AU_RAND_C ‖ AU_RAND_P))를 쓴다. h5 출력의 앞 32비트가 SRES_C, 다음 32비트가 SRES_P, 나머지 64비트가 ACO다. 암호화 키는 h3 = HMACLK("btak" ‖ A1 ‖ A2 ‖ ACO)의 앞 128비트다.
암호화는 AES-CCM(M = 4, L = 2)인데 B1이 표준과 다르다. 연관 데이터 길이를 넣지 않고 바로 패킷 헤더 2바이트(ARQN·SEQN·FLOW를 0으로 가림)와 페이로드 헤더 1바이트(FLOW를 가림)를 넣는다. nonce 13바이트는 ACL이면 PayloadCounter 형식(39비트 카운터 + 길이 0 이어짐 비트 + 방향 비트 + IV), eSCO면 클록 형식(CLK₂₇₋₁ + dayCounter + IV)이다. IV는 암호화 시작 때 마지막 h5의 ACO, 재개 뒤에는 EN_RAND다. eSCO는 MIC 없이 암호화만 한다.
키 길이를 줄이기로 협상했으면 키의 하위 바이트를 0으로 바꾼다. 협상 범위는 7~16바이트이고, 호스트는 HCI로 받아들일 최소 길이를 정할 수 있다. 짧은 키는 그만큼 추측 공간이 작으므로 16바이트를 요구하는 것이 안전한 설정이다.
BR/EDR AES-CCM ([Vol 2] Part G §1.2)
h3·h4·h5: [Vol 2] Part H §7.7.6~§7.7.8 · AES-CCM과 nonce: §9, §9.1 · B0·B1: §9.2 · A 블록: §9.3 · 키 길이 줄이기: §9.4 · 키 길이 협상(최소 7바이트): [Vol 2] Part C §4.2.5.3 · 최소 키 길이 설정: [Vol 4] Part E §7.3.102 · 예제: [Vol 2] Part G §1.2, §7.2.6~§7.2.8
핵심 정리
- 레거시: SAFER+(Ar, A′r)로 E22(K_init), E21, E1(SRES·ACO), E3(K_enc), 암호화는 E0. 비밀은 PIN뿐.
- SSP: ECDH(P-192) + HMAC-SHA-256의 f1(약속)·g(여섯 자리)·f2(링크 키)·f3(IOcap을 묶은 확인).
- Secure Connections: P-256, h4 → h5(SRES_C·SRES_P·ACO) → h3(AES 키), AES-CCM의 B1은 길이 없이 헤더만.
- nonce: ACL은 PayloadCounter + 방향 비트, eSCO는 CLK + dayCounter. IV는 ACO 또는 EN_RAND.
- 안전한 설정: Secure Connections Only(보안 모드 4 레벨 4), 키 길이 16바이트.
확인 퀴즈
E1 출력 중 ACO는 어디에 쓰이나?
PIN이 4바이트일 때 E22의 L′은?
f3에 IOcap을 넣는 이유는?
BR/EDR AES-CCM의 B1이 표준 CCM과 다른 점은?