개발하다가 글쓰는 블로그

시간 측정이 틀릴 수도 있다? - 브라우저가 시간을 속이는 방법 본문

개발

시간 측정이 틀릴 수도 있다? - 브라우저가 시간을 속이는 방법

지존개발자킹 2025. 4. 24. 09:24

웹개발과 시간

웹 개발을 하다 보면 시간 생성은 꼭 필요한 작업이다.
단순히 현재 시각을 표시하고 싶을 때도 있고 로깅이나 성능 측정을 위해서도 필요하다.
하지만 만약 브라우저에서 단순히 Date.now()나 performance.now()를 사용하고 있다면 정확한 시간을 얻지 못하고 있을 수도 있다.

왜냐하면 브라우저는 보안상의 이유로 시간에 의도적 오차를 삽입하기 때문이다

JavaScript에서 사용 가능한 시간 관련 API

시간 측정은 크게 두 가지 방식이 있는데, 원하는게 무엇인지에 따라 사용할 API가 달라진다.

API 용도 단위 비고
Date.now() 절대시각 ms (number) 전통적 방식
performance.now() 상대시각 μs (float) 페이지 로드 후 경과시간
Temporal.now.instant() 절대시각 ns (BigInt) 최신 표준 (TC39 stage-3)

 

간단히 정리하면 다음과 같다.

지금 몇 시 인가? - Date.now() 또는 Temporal.now.instant()

작업이 얼마나 걸렸는가? - performance.now()

모든 시간 API에는 "의도적 오차"가 있다

브라우저에서 사용 가능한 모든 시간 API는 의도적 오차가 삽입된다.

이는 시간을 활용한 보안 공격(타이밍 공격, Spectre, Meltdown)을 방지하고, 개인정보를 보호(Fingerprinting 방지)를 위함이다

그렇기 때문에 시크릿 모드, iframe 환경에서는 더욱 강력하게 적용된다.

이런 의도적 오차는 크게 두 가지 방식으로 추가된다.

Truncation

Truncation은 정밀도를 제한하는 방식이다. 더 상세한 시간 정보를 제공할 수 있음에도 불구하고 특정 단위 이하 정보를 없애버리는 식이다. 예를들면

 

1.2345678ms → 1.2000000ms

 

이렇게 더 정밀한 시간을 측정할 수 있어도 의도적으로 상세 정보를 없애버리는 식이다.

Jitter

Jitter는 무작위로 오차를 추가하는 방식이다. 예를들면

 

1.2000000ms → 1.2132435ms

 

이렇게 측정한 시간에 의도적으로 무작위 오차를 추가하는 식이다.

Node.js는 예외다?

Node.js는 이러한 제한이 적용되지 않는데 이는 사용자의 브라우저 환경에서 임의의 코드가 실행될 수 있는 브라우저와는 다르게 Node.js 환경은 사용자가 직접 실행하는 환경이기도 하고, 그 사용특성상 정밀한 시간 측정이나 연산이 필요하기 때문이다.

또한 Node.js에는 더욱 정밀한 시간 측정 API가 존재하기도 한다.

Node.js에서 process.hrtime.bigint()를 사용하면 마치 Performance API 처럼 상대 시각을 얻을 수 있는데 이는 나노초 단위로 브라우저에서도 사용가능한 API들보다 정밀하며 Node.js상의 다른 API들과 마찬가지로 의도적 시간 오차가 없다.

const start = process.hrtime.bigint(); // 나노초 단위
doSomething();
const end = process.hrtime.bigint();
console.log(`소요 시간: ${(end - start)} ns`);

브라우저에서 정밀한 시간을 사용하는 방법

위 설명처럼 브라우저는 보안상의 이유로 의도적인 오차가 존재하는데, 특정 조건을 만족하는경우 이를 상당히 완화할 수도 있다.

브라우저에서 crossOriginIsolated가 true로 평가되는 경우, 보안오차(truncation, jitter)가 대부분 제거된다. (완벽하지는 않다!)

브라우저의 콘솔에서 아래 코드를 실행하면 결과를 알 수 있다

window.crossOriginIsolated // true or false

 

이 값은 문서가 보안 오차를 완화해도 될 정도로 충분히 격리되었다는 것을 알려주는 역할을 하는데, 이 값이 true인 경우에만

  • perfoemance.now()의 정밀도 향상
  • SharedArrayBuffer 사용
  • WebAssembly의 고정밀 메모리 연산

등이 허용된다.

 

이를 위해서는 아래 Response Header가 필수로 요구된다.

Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
  • Cross-Origin-Opener-Policy: same-origin
    해당 문서의 윈도우와 탭이 다른 origin과 완전히 격리된다
  • Cross-Origin-Embedder-Policy: require-corp
    해당 문서의 모든 리소스 (script, image, iframe 등)에 CORS, CORP를 강제하여 외부 리소스를 포함할 수 없다

개발 시 주의할 점

  • 벤치마킹하는 경우 반복 측정, 평균을 사용한다
    브라우저 환경은 오차때문에 생긴 최소 측정단위 때문에 빠른 실행시간(최소 ~100μs)의 실행시간은 거의 의미가 없을 수 있으므로 이 경우 반복실행의 평균값을 활용하여 노이즈를 줄일 수 있다
  • 만약 성능측정등이 Node.js 에서도 가능한 경우라면 Node를 사용한다
  • 시간에 민감한 서비스라면 시크릿 모드에서도 잘 동작하는지 확인한다