Skip to content
오늘을살자
Go back

DoIP에서 NRC 0x35가 뜬다: Invalid Key를 알고리즘 오류로만 보면 오래 헤맨다

Edit page

DoIP로 Security Access를 붙이다 보면 가장 신경 쓰이는 응답이 있다.

27 01
-> 67 01 <seed>

27 02 <key>
-> 7F 27 35

NRC 0x35 Invalid Key다.

이름만 보면 바로 seed/key 알고리즘이 틀린 것처럼 보인다.
물론 실제로 key가 틀렸을 수도 있다.

하지만 현장에서 보면 계산식 자체보다 주변 상태가 어긋나서 0x35가 반복되는 경우도 많다.
seed와 key의 짝이 바뀌었거나, security level이 섞였거나, 재연결 뒤 오래된 seed를 다시 쓰는 식이다.

오늘 메모는 DoIP에서 NRC 0x35가 반복될 때 key 알고리즘 이전에 확인할 지점이다.

결론부터

0x35는 먼저 이렇게 나눠 보는 편이 빠르다.

요청 순서가 틀림
-> 7F 27 24

보안 상태에서 서비스가 허용되지 않음
-> 7F xx 33

key 값이 ECU 기준과 맞지 않음
-> 7F 27 35

시도 횟수나 대기 시간에 걸림
-> 7F 27 36 또는 7F 27 37 계열

7F 27 35가 왔다면 요청은 대체로 ECU 애플리케이션까지 도착했다.
ECU도 SecurityAccess 서비스와 key 전송 단계를 이해했다.

다만 ECU가 계산한 기대 key와 테스터가 보낸 key가 맞지 않는다고 판단한 것이다.

그래서 이때는 TCP, Routing Activation, DoIP header보다
seed/key pair와 security context가 같은 시간축에 있는지를 먼저 봐야 한다.

흔한 패턴 1: seed와 key의 짝이 바뀐다

테스터가 동시에 여러 요청을 다루거나,
재시도 로직이 느슨하면 seed와 key가 쉽게 어긋난다.

예를 들어 이런 흐름이다.

requestSeed level 1
-> seed A

timeout 판단
requestSeed level 1 retry
-> seed B

sendKey computed from seed A
-> 7F 27 35

로그만 대충 보면 seed도 받았고 key도 보냈다.
하지만 ECU 입장에서는 이미 seed B에 대한 key를 기다리는 상태일 수 있다.

그래서 seed를 저장할 때 단순히 last_seed만 두면 부족하다.
최소한 아래 정보를 같이 묶어야 한다.

source_address
target_address
session
security_level
seed_value
seed_generation
connection_generation

key를 보낼 때도 같은 묶음에서 나온 seed인지 확인해야 한다.

흔한 패턴 2: level 1 seed로 level 3 key를 보낸다

Security Access sub-function은 보통 level마다 짝이 있다.

예를 들면 이런 식이다.

27 01
-> requestSeed for level 1

27 02
-> sendKey for level 1

27 03
-> requestSeed for level 3

27 04
-> sendKey for level 3

프로젝트마다 level 의미는 다를 수 있다.
하지만 중요한 점은 seed 요청 sub-function과 key 전송 sub-function이 같은 level 흐름이어야 한다는 것이다.

테스터 코드가 아래처럼 추상화되어 있으면 실수하기 쉽다.

security_level = extended
seed = request_seed(security_level)
key = calc_key(seed)
send_key(current_level, key)

중간에 current_level이 바뀌거나,
UI에서 다른 unlock 버튼을 누르면 seed와 key level이 섞인다.

로그에는 반드시 요청 sub-function을 그대로 남기는 편이 좋다.

seed_req=0x01 seed_rsp=0x01 key_req=0x02 result=7F 27 35

이 정도만 있어도 level mismatch를 빨리 의심할 수 있다.

흔한 패턴 3: byte order와 입력 데이터 범위가 다르다

seed/key 알고리즘은 프로젝트 정책이다.
여기서는 계산식을 말할 수 없고, 일반화해서도 안 된다.

다만 디버깅할 때 자주 흔들리는 지점은 비슷하다.

seed byte order
-> ECU가 준 byte 배열 그대로 쓰는가, 정수로 바꾼 뒤 endian을 바꾸는가

algorithm input
-> seed만 넣는가, level이나 variant 값도 같이 넣는가

key output length
-> ECU가 기대하는 길이와 실제 전송 길이가 같은가

특히 seed를 uint32_t 같은 정수로 바꾸는 순간 byte order 문제가 숨어든다.

캡처에 보이는 seed가 아래라고 하자.

67 01 12 34 56 78

테스터 내부 로그가 이렇게만 남으면 부족하다.

seed=0x12345678

실제 알고리즘에 들어간 byte 배열도 같이 남겨야 한다.

seed_bytes=12 34 56 78
key_bytes=9A BC DE F0

그래야 “계산식이 틀렸나?”와 “입력 byte 해석이 틀렸나?”를 나눌 수 있다.

흔한 패턴 4: 재연결 뒤 오래된 seed를 다시 쓴다

DoIP에서는 아래 이벤트 뒤에 보안 문맥이 바뀔 수 있다.

이런 이벤트 뒤에도 앱이 이전 seed를 들고 있으면 위험하다.

requestSeed
-> seed A

TCP reconnect
Routing Activation success

sendKey computed from seed A
-> 7F 27 35

테스터 입장에서는 “방금 받은 seed인데 왜 틀렸지?”처럼 보인다.
하지만 ECU 입장에서는 이미 다른 연결 세대나 다른 세션일 수 있다.

그래서 seed는 오래 보관하는 값이 아니다.
연결 세대와 세션 상태가 바뀌면 폐기하는 편이 안전하다.

세션 전환 자체는 DoIP에서 Session Control 먼저 이해해야 하는 이유와 같이 보면 좋다.

흔한 패턴 5: 0x35 이후 같은 key를 계속 재시도한다

0x35가 왔을 때 같은 key를 빠르게 반복 전송하면 상황이 나빠질 수 있다.

ECU는 잘못된 key 시도 횟수를 셀 수 있다.
그 다음에는 0x36 Exceed Number Of Attempts나 대기 시간 관련 응답으로 넘어갈 수 있다.

sendKey wrong
-> 7F 27 35

sendKey same value retry
-> 7F 27 35

sendKey same value retry
-> 7F 27 36

따라서 0x35는 네트워크 retry처럼 다루면 안 된다.

같은 key를 다시 보내기 전에
새 seed를 받아야 하는지,
대기 시간이 필요한지,
세션을 다시 잡아야 하는지부터 확인해야 한다.

요청 순서 문제는 DoIP에서 Security Access 뒤 NRC 0x24가 뜬다: seed/key 이후 요청 순서와 세션 문맥을 같이 봐야 한다와 비교하면 구분이 쉽다.

로그를 이렇게 남기면 빨라진다

0x35는 key 값을 숨겨야 하는 환경도 많다.
그렇다고 로그를 너무 지우면 원인을 못 좁힌다.

민감한 값은 마스킹하더라도 아래 문맥은 남기는 편이 좋다.

  1. SA와 TA
  2. diagnostic session
  3. requestSeed sub-function
  4. sendKey sub-function
  5. seed generation 또는 seed hash
  6. key 길이
  7. 연결 세대와 마지막 Routing Activation 시각
  8. 0x35 전후의 retry 횟수

예를 들면 이런 식이다.

sec seed_req=0x01 seed_gen=14 session=0x03 sa=0x0E00 ta=0x1001 conn=7
sec key_req=0x02 key_len=4 seed_gen=14
rx 7F 27 35 retry_count=1 action=discard_seed

key byte 전체를 남기지 않아도
seed와 key가 같은 세대에서 나온 것인지는 확인할 수 있다.

빠른 체크리스트

  1. 0x35를 TCP timeout이나 DoIP framing 문제로 보지 않는지 확인
  2. key가 가장 최근 seed에서 계산됐는지 확인
  3. requestSeed와 sendKey sub-function이 같은 security level 쌍인지 확인
  4. seed byte order와 알고리즘 입력 byte 배열을 캡처 기준으로 비교
  5. key 길이가 ECU가 기대하는 길이와 같은지 확인
  6. 재연결, RA 재수행, 세션 변경 뒤 오래된 seed를 폐기하는지 확인
  7. 0x35 이후 같은 key를 무한 retry하지 않는지 확인

함께 보면 좋은 글

한 줄 요약

DoIP에서 NRC 0x35가 반복되면 key 알고리즘만 붙잡기 전에 seed/key 세대, security level 짝, byte order, 세션과 재연결 상태가 같은 문맥인지 먼저 확인하는 편이 빠르다.

추천 키워드

DoIP, UDS, NRC 0x35, Invalid Key, Security Access, Session Control


DevBJ | 오늘을살자, Log Today


Edit page
Share this post on:

Previous Post
lwIP에서 UDP broadcast가 안 나간다: SO_BROADCAST와 netif flags를 같이 보자
Next Post
lwIP에서 tcp_write()가 ERR_MEM을 돌려준다: free RAM보다 snd_buf와 snd_queuelen을 먼저 보자