비교에 걸린 시간까지 비밀로 다뤄, 값이 한 바이트씩 복원되는 통로를 막습니다.
암호가 보통 보장하는 것은 정답을 모르면 열리지 않는다는 것입니다. 그런데 공격자가 얻는 정보는 결과값만이 아닙니다. 처리 시간, 전력, 캐시 상태 같은 부수 채널도 관찰 대상이고, 시간은 네트워크 너머에서도 잴 수 있습니다. 다른 바이트를 만나는 순간 멈추는 비교는 앞에서 몇 바이트가 일치했는지를 시간으로 흘리고, 그 정보 하나만으로 비밀값 전체가 복원됩니다.
결과값이 아니라 처리 과정의 부산물로 정보가 새는 경로입니다.
다른 바이트를 만나자마자 멈추는 비교는 앞에서 일치한 길이를 드러냅니다.
자리마다 후보를 넣어 보는 방식으로, 전수 탐색 비용을 길이에 비례하는 수준까지 낮춥니다.
32바이트 MAC을 전수 탐색하려면 10^77번쯤 시도해야 합니다. 그런데 비교가 중간에 멈추면 공격자는 전체를 한 번에 맞힐 필요가 없습니다. 첫 바이트에 256가지를 넣어 보고 가장 오래 걸린 후보를 고른 뒤, 그 자리를 확정하고 다음 바이트로 넘어갑니다. 비용이 곱셈에서 덧셈으로 바뀌어 8,192번이면 끝납니다. 이 모듈의 공격 증명이 실제로 이 복원을 해 보입니다.
타이밍을 실제로 재면 노이즈 때문에 결과가 흔들려 CI가 가끔 실패합니다. 증명이 이따금 빨갛게 뜨면 그 증명을 믿을 근거부터 사라집니다. 그래서 비교가 들여다본 바이트 수를 관찰값으로 씁니다. 이 횟수는 실제 소요 시간에 비례하므로 공격의 구조는 그대로입니다. 관찰 채널만 시간에서 횟수로 바꾼 것입니다.
Node의 timingSafeEqual은 길이가 다르면 예외를 던집니다. 그 예외를 그냥 삼키면 길이가 다르다는 사실이 다시 시간 차이로 새어 나갑니다. 이 모듈은 길이를 먼저 확인해 바로 false를 돌려주되, 비교 대상이 MAC이나 digest처럼 길이가 고정된 값이라는 전제를 명시합니다. 길이 자체가 비밀인 값에는 이 계약을 쓰면 안 됩니다.
아래 증명은 글로 된 설명이 아닙니다. 버튼을 누르면 이 서버가 지금 직접 계산합니다.
비교 결과는 '같다'와 '다르다' 두 가지뿐이어야 합니다. 어느 위치에서 달라졌는지, 앞에서 몇 바이트가 맞았는지는 결과가 아니라 지켜야 할 비밀입니다. 조기 종료 비교는 이 정보를 처리 시간으로 흘립니다.
표준 문서에 실린 테스트 벡터를 그대로 넣어 같은 값이 나오는지 확인합니다.
공격자는 비밀을 한 번도 보지 못하고, 비교가 몇 바이트를 확인했는지만 관찰합니다. 그런데도 8바이트 MAC이 2,048번 시도 만에 완전히 복원됩니다. 전수 탐색이었다면 10^19번이 필요했습니다. 상수 시간 비교로 바꾸면 이 경로가 막힙니다.
막아야 할 입력을 실제로 넣고 거부되는지 확인합니다.
규격 원문을 먼저 실었습니다. 사례는 이 분야에서 실제로 보안이 뚫린 일을 다룬 자료입니다.
이 결함 유형의 표준 분류와 완화 방법을 정리합니다.
네트워크 너머에서 타이밍만으로 OpenSSL 개인키를 복원한 연구입니다.
해시 비교의 타이밍 하나로 부팅 서명 검증이 뚫린 사례입니다.
다음 단계
검증 경로를 모두 훑어 남아 있는 일반 비교를 찾고, 정적 검사로 다시 생기지 않게 막습니다.