1P by GN⁺ | ★ favorite | 댓글 1개
  • Safari 17은 비공개 모드에서 Audio API 샘플마다 무작위 노이즈를 더해 오디오 지문을 흔들지만, FingerprintJS는 이를 줄이는 새 지문 알고리듬으로 대응함
  • 기존 방식은 500개 오디오 샘플의 합을 식별자로 쓰기 때문에 Safari의 노이즈 범위가 브라우저 간 차이보다 커져 안정성을 잃음
  • 새 방식은 같은 오디오 샘플의 노이즈 사본을 대량으로 만들고 (min+max)/2유효숫자 반올림으로 값의 흔들림을 줄임
  • square OscillatorNode, DynamicsCompressorNode, BiquadFilterNode를 연결해 브라우저 간 차이를 키웠고, 선택한 브라우저들의 3396번째 샘플 최소 차이를 0.0014%까지 늘림
  • 새 알고리듬은 FingerprintJS 4.2.0부터 기존 오디오 지문을 대체했으며, 계산 시간은 1.5~2배 늘지만 저사양 기기에서도 짧은 시간 안에 끝남

Safari 17이 오디오 지문을 흔드는 방식

  • 오디오 지문 인식은 브라우저의 Audio APIOfflineAudioContext로 오디오 신호를 렌더링한 뒤, 샘플을 합산해 하나의 식별자 숫자로 만드는 방식임
  • 이 식별자는 쿠키 삭제나 시크릿 모드 전환에도 바뀌지 않는 안정성을 가지지만, 많은 사용자가 같은 값을 공유할 수 있어 고유성은 높지 않음
  • Safari 17의 고급 지문 보호는 기본적으로 비공개 모드에서 켜지고 일반 모드에서는 꺼지며, 데스크톱과 모바일 모두에 적용됨
  • 보호 기능은 Screen API와 Canvas API에도 영향을 주지만, 여기서는 Audio API만 다룸
  • 보호 기능이 켜지면 Safari는 각 오디오 샘플에 개별 무작위 노이즈를 곱함
    • 노이즈가 적용된 샘플은 sample*(1-magnitude)sample*(1+magnitude) 사이에 있음
    • 분포는 균등분포임
    • Safari 개발이 계속 진행 중이므로 구현 세부사항은 시간이 지나면 달라질 수 있음

노이즈가 적용되는 Audio API 지점

  • Safari는 오디오 신호를 읽을 수 있는 여러 인터페이스에서 노이즈를 적용함
  • 노이즈는 적용될 때마다 달라지므로 Safari 17 비공개 모드에서는 오디오 지문이 계산할 때마다 바뀜
  • M1 MacBook Air의 Safari 17에서 지문은 124.03516~124.04545 사이에서 변동하며, 차이는 약 0.008%
  • 브라우저별 기존 오디오 지문 차이 중 가장 작은 값은 0.0000023%로, Safari의 노이즈 범위보다 훨씬 작음
  • 노이즈를 없애려면 소수점 한 자리 수준으로 반올림해야 하지만, 6자리보다 적게 남기면 일부 브라우저를 구분하기 어려워 고유성이 낮아짐

새 알고리듬의 3단계

  • FingerprintJS의 새 오디오 지문 알고리듬은 Safari가 추가한 노이즈를 줄이기 위해 세 단계를 거침
    • 노이즈의 분산을 줄임
    • 브라우저 식별자 숫자 간 거리를 늘림
    • 남은 노이즈를 반올림으로 제거함
  • 기존 오디오 지문은 500개 오디오 샘플의 합이므로, 각 샘플에 균등분포 노이즈가 더해지면 전체 지문 노이즈는 정규분포에 가까워짐
  • 정규분포 평균은 많은 샘플의 평균으로 추정해야 하지만, 균등분포 평균은 minmax를 사용한 (min+max)/2로 더 적은 샘플에서 정밀하게 추정할 수 있음
  • 실험 코드에서는 같은 정밀도 조건에서 정규분포는 524,288개 샘플이 필요했고, 균등분포는 4,096개 샘플이면 충분했음
  • 새 방식은 합산 지문 대신 단일 오디오 샘플만 수집해 균등분포 노이즈를 다루도록 바꿈
  • 이 변경 때문에 새 지문은 기존 지문과 호환되지 않으며, 방문자 식별자를 잃지 않고 전환하려면 별도 접근이 필요함

같은 오디오 샘플의 노이즈 사본 만들기

  • AudioBuffer 인스턴스에서 getChannelData를 여러 번 호출하는 방식은 작동하지 않음
    • 노이즈는 각 AudioBuffer 인스턴스마다 한 번 적용됨
    • 같은 인스턴스의 getChannelData는 같은 신호를 반환함
  • 전체 오디오 신호 생성 과정을 여러 번 실행하면 많은 AudioBuffer 인스턴스를 만들 수 있지만, 지문 계산용으로는 너무 느림
    • 6,000개 노이즈 샘플은 M1 MacBook에서 가장 빠른 시간이 7초였음
    • 60,000개에서는 Safari가 작업을 끝내지 못했음
  • 더 나은 방식은 같은 오디오 신호를 반복하는 AudioBuffer 인스턴스를 만드는 것임
    • 첫 번째 오디오 신호를 렌더링하되 getChannelData는 호출하지 않음
    • 더 긴 두 번째 OfflineAudioContext를 만들고 원본 신호를 AudioBufferSourceNode로 사용함
    • loop, loopStart, loopEnd로 원본 신호 일부를 반복함
    • 반복 후 Safari가 노이즈를 추가하므로 같은 오디오 샘플에 서로 다른 노이즈가 적용된 사본을 얻음
  • 이 방식은 오디오 렌더링 2번만으로 필요한 수의 노이즈 사본을 만들 수 있음
  • 노이즈가 완전히 사라지지는 않지만, 원본 오디오 샘플보다 분산이 작아짐
    • 8,192개 사본: 100회 실행 결과 범위 0.000387%, M1 MacBook 기준 2.6ms
    • 65,536개 사본: 0.0000123%, 4.1ms
    • 262,144개 사본: 0%, 7.5ms

브라우저 간 오디오 샘플 차이 키우기

  • 사본 수를 줄이면 성능은 좋아지지만 결과 분산이 커지므로, 브라우저 간 오디오 샘플 차이를 키우기 위해 기본 신호를 바꿈
  • 여러 내장 오디오 노드를 실험한 결과, 브라우저 간 샘플 차이가 커지는 신호 생성 체인은 다음과 같음
  • 오디오 신호의 3396번째 샘플이 브라우저 간 차이가 가장 컸으며, 여러 브라우저의 모든 샘플을 비교해 찾은 값임
  • 선택한 브라우저 표본에서 이 새 샘플의 가장 작은 차이는 0.0014%였음
    • 기존 지문의 가장 작은 차이 0.0000023%보다 큼
    • 덕분에 더 거친 노이즈 제거와 반올림을 적용할 수 있음

반올림으로 지문 안정화

  • 단일 샘플의 노이즈 범위가 작아져도 값은 여전히 흔들리므로, 최종 지문으로 쓰려면 반올림이 필요함
  • 노이즈는 절대값이 아니라 오디오 샘플 값에 상대적으로 적용되기 때문에, 소수점 자리수가 아니라 유효숫자를 기준으로 반올림함
  • 선택한 브라우저들을 구분하는 데는 유효숫자 5개가 충분했지만, 전체 브라우저와 향후 변화를 모두 확인할 수 없으므로 더 많은 자릿수를 사용함
  • Safari 17 비공개 모드에서 반올림 정밀도별 안정화에 필요한 사본 수는 다음과 같음
    • 유효숫자 6개: 15,000개, Safari 17 on M1 MacBook warm 기준 3ms
    • 유효숫자 7개이되 마지막 자리를 5의 배수로 반올림: 30,000개, 4ms
    • 유효숫자 7개이되 마지막 자리를 가장 가까운 짝수로 반올림: 70,000개, 6ms
    • 유효숫자 7개 이상: 400,000개, 12ms
  • 최종 선택은 유효숫자 7개이되 마지막 자리를 0 또는 5로 만드는 방식이며, 안정성을 높이기 위해 사본 수를 40,000개로 늘림
  • 이렇게 반올림된 숫자는 Safari 17 고급 지문 보호가 켜져 있어도 바뀌지 않는 새 오디오 지문이 됨
  • 새 지문은 기존 오디오 지문과 같은 고유성을 가진 것으로 평가됨

성능과 실행 제약

  • 빈 페이지의 warm 브라우저 기준 새 알고리듬은 기존보다 대체로 느림
    • MacBook Air 2020 Safari 17.3: 기존 2ms, 새 방식 4ms
    • MacBook Air 2020 Chrome 120: 기존 5ms, 새 방식 8ms
    • iPhone 13 mini Safari 17.3: 기존 8ms, 새 방식 12ms
    • Galaxy J7 Prime Chrome 120: 기존 33ms, 새 방식 45ms
    • BrowserStack Windows 11 Firefox 121: 기존 10ms, 새 방식 18ms
  • 새 알고리듬의 성능은 기존 대비 1.5~2배 나빠지지만, 저사양 기기에서도 계산 시간은 짧음
  • 브라우저가 일부 작업을 OfflineAudioRender 스레드에 위임하므로, 오디오 지문 계산 대부분 동안 페이지는 반응성을 유지함
  • Web Audio API는 web workers에서 사용할 수 없어 오디오 지문을 워커에서 계산할 수 없음
  • 성능을 개선하려면 사용자 에이전트 문자열로 Safari 17 이상 여부를 확인하고, Safari 17 이상에서만 새 알고리듬을 쓰며 다른 브라우저에서는 기존 알고리듬을 유지할 수 있음

Tor와 Brave의 차이

  • Tor는 Web Audio API를 완전히 비활성화하므로 오디오 지문 인식이 불가능함
  • Brave는 Safari 17처럼 오디오 신호에 노이즈를 추가하지만 방식이 다름
  • Safari는 각 오디오 샘플마다 별도 무작위 값을 곱함
  • Brave는 fudge factor라는 무작위 배수를 한 번 만들고 모든 오디오 샘플에 같은 값을 곱함
    • 이 값은 페이지 안에서 유지됨
    • 새 일반 세션이나 시크릿 세션에서만 바뀜
  • Brave에서는 아무리 많은 오디오 샘플 사본을 만들어도 모든 사본에 같은 노이즈가 적용되므로, Safari용 수학적 노이즈 제거 방식이 작동하지 않음
  • 다만 이전 Brave 노이즈 제거 방식은 계속 작동하며, 브라우저 간 지문 차이를 키우는 새 신호 생성 방법은 오차 허용 범위를 늘릴 수 있음

FingerprintJS 적용

  • 새 오디오 지문 알고리듬은 FingerprintJS에서 기존 방식을 대체했으며, 4.2.0에서 처음 공개됨
  • 전체 구현 코드는 FingerprintJS의 GitHub 저장소에 있음
  • 오디오 지문은 오픈소스 라이브러리가 브라우저 지문을 만들 때 사용하는 여러 신호 중 하나임
  • FingerprintJS는 브라우저에서 얻을 수 있는 모든 신호를 무조건 포함하지 않고, 각 신호의 안정성과 고유성을 따로 분석해 지문 정확도에 미치는 영향을 판단함
  • 오디오 지문은 고유성에는 조금만 기여하지만 안정성이 높아 전체 지문 정확도를 약간 높이는 신호로 평가됨

댓글과 토론

Hacker News 의견들
  • 온라인에서 사용자를 식별하는 또 다른 흥미로운 기법으로 GPU 지문 채취가 있고, 2022년에 "DrawnApart"라는 코드명으로 소개됐음
    WebGL로 GPU 실행 유닛의 수와 속도를 세고, 정점 렌더링 완료 시간과 stall 함수 처리 등을 측정하는 방식임

    1. https://www.bleepingcomputer.com/news/security/researchers-u...
    • 브라우저는 기본적으로 소프트웨어 렌더러를 써야 하고, 하드웨어 GPU 렌더 경로를 열 때는 마이크나 카메라처럼 사이트가 사용자 권한을 요청해야 함
  • 요즘은 특히 부채널 공격에 대한 관심을 보면, 데이터가 새는 값에 균일 잡음을 더하는 방식은 거의 당연히 안 통한다고 봄
    샘플을 더 많이 모으면 잡음을 제거할 수 있기 때문임. Safari가 왜 이걸 넣었는지 모르겠음. 지문 채취를 더 귀찮게 만들 수는 있겠지만, 이 글처럼 결국 어떤 형태로든 대체로 돌파 가능해 보임

    • Apple의 요즘 프라이버시 기능 상당수는 마케팅에 가깝다고 봄
      기술적으로 효과적인지보다, 대중에게 그럴듯한 이야기를 할 수 있는지가 더 중요해진 프라이버시 연극처럼 느껴짐
  • 애초에 결과가 왜 달라지는지 설명해 줄 수 있나? 예를 들어 오디오 지문 채취가 왜 가능한 건지 궁금함

    • 핵심은 Web Audio API에 많은 수학 연산을 하는 알고리즘이 있고, 브라우저마다 구현이 조금씩 다르며, 정확한 결과가 운영체제와 CPU에도 의존한다는 데 있어 보임
      Web Audio API로 작은 신호를 만들면 모든 브라우저가 거의 같은 결과를 내지만, 아주 작은 차이를 이용해 서로 구분할 수 있음
    • WebGL에서 쓰이는 비슷한 기법처럼, PC의 그래픽 카드 드라이버와 하드웨어 자체에서 많은 엔트로피가 나오는 것과 비슷하다고 봄
      브라우저 개발자들이 이를 막으려고 오디오 버퍼 처리에 잡음을 추가해야 한다는 게 안타까움
    • 나도 처음 든 생각이 그거였고, 여기서 더 자세히 다룸: https://fingerprint.com/blog/audio-fingerprinting/#why-the-a...
      요약하면 같은 코드베이스 안에서도 서로 다른 코드 경로, 예컨대 SIMD 변형이 미묘하게 다른 부동소수점 결과를 만들 수 있음. 부동소수점 연산은 연산 순서 등에 예상보다 민감하다는 점과 관련 있어 보임
    • 구현 세부사항과 컴파일러 최적화 때문일 가능성이 큼. 예를 들어 부동소수점 덧셈은 교환법칙이 성립하지 않음
      같은 알고리즘과 같은 공식을 올바르게 구현해도 결과가 조금씩 달라질 수 있음
  • 내가 틀렸다면 고쳐 달라. 여기서 지문 채취 우회가 성공하는 이유는 Web Audio API 명세에서 OscillatorNode의 앤티앨리어싱 처리 방식을 이렇게 열어 둔 선택으로 귀결되는 것 같음
    "구현체가 이 앨리어싱을 피하기 위해 취할 수 있는 실용적 접근은 여러 가지다. 접근 방식과 관계없이 이상적인 이산 시간 디지털 오디오 신호는 수학적으로 잘 정의되어 있다. 구현의 절충점은 CPU 사용량 측면의 구현 비용과 이 이상에 얼마나 충실하게 도달하느냐에 있다. 구현체가 이 이상을 달성하기 위해 어느 정도 주의를 기울일 것으로 기대되지만, 저사양 하드웨어에서는 더 낮은 품질과 더 낮은 비용의 접근을 고려하는 것이 합리적이다."
    내가 보기엔 이 말은 여기서 악용하는 OscillatorNode 출력이 브라우저 간, 심지어 같은 브라우저라도 다른 하드웨어에서는 거의 확실히 결정적이지 않다는 뜻임. 비결정성은 브라우저가 선택한 앤티앨리어싱 방식, 또는 하드웨어에 따라 같은 브라우저 안에서 선택되는 여러 경로에 기반함. 같은 앤티앨리어싱 알고리즘의 변경이나 수정도 포함됨
    왜 앤티앨리어싱을 브라우저에 맡겼는지는 잘 모르겠음. 고품질 오디오 앱이나 라이브러리는 자신이 생성하는 신호의 앨리어싱 회피 방식을 완전히 제어하려 할 테고 기본 오실레이터를 쓰지 않을 것임. 반대로 임의의 앤티앨리어싱 알고리즘과 그에 따른 브라우저별 차이를 받아들일 웹 앱이라면, 알고리즘이 하드코딩된 SIMD 명령이든 20MB짜리 JavaScript Web Audio 헬퍼 프레임워크든 크게 신경 쓰지 않을 가능성이 큼
    1: https://webaudio.github.io/web-audio-api/#OscillatorNode
    HTML5 파서를 표준화할 때 Hixie가 썼던 방식과 같은 해법을 여기에 적용할 수 있을지도 궁금함. 특정 분야 전문가가 충분히 괜찮게 동작하는 정확하고 결정적인 앤티앨리어싱 알고리즘을 지정하고, 이후 모든 브라우저가 그걸 쓰게 하는 식임. 측정 가능한 성능 손실은 기본 앤티앨리어싱 오실레이터로 신호를 생성하는 Web Audio API 튜토리얼 정도에서나 보일 것 같음

    • 고품질 앤티앨리어싱은 비용이 큼
      그래서 사용 가능한 연산 자원, 배터리 등에 따라 구현체가 얼마를 쓸지 결정할 수 있게 하고 싶은 것임
  • 브라우저에 노드 그래프 오디오 API를 넣은 건 어리석었음. 그냥 AudioWorklet만 있었어야 함

  • 역겹다

    • 내 생각도 정확히 그럼. 흥미롭지만 역겨움
      애초에 왜 오디오 API가 웹사이트 권한 없이 사용 가능한지 모르겠음. "이 사이트가 사운드 장치를 사용하려고 합니다" 같은 간단한 대화상자로 쉽게 고칠 수 있어 보임
    • 지금의 네트워크 스택을 앞으로 100년 동안 써도 되는지 묻게 됨
      현재 형태의 인터넷은 개인용 컴퓨팅의 꿈을 많이 망쳤음. 기업과 국가가 개인에 비해 너무 비대칭적으로 강하기 때문임. 내 기술이 명시적 승인 없이 서버로 데이터를 보내는 게 가능해야 하나?
    • 맞음. 이 사람들이 이걸 자랑스러워한다는 게 믿기지 않음
      한편으로는 브라우저 캐시를 지우고 VPN을 켰더니 나를 새 방문자로 잘못 식별하긴 했음. 그래도 비즈니스 모델은 비열함
    • fingerprint.com이라서 어느 정도 아이러니가 있다고 생각했음. 세금 부담을 피하는 허점을 대중화하는 웹사이트가 나타나서, 세상이 역겨워하며 그 허점을 닫게 만드는 것과 비슷함
      설령 희망적 해석이라도, 이런 연구를 공개하고 밖으로 드러내는 데는 큰 가치가 있음. 특정 브랜드의 초록색 배낭이 절도에 도움이 된다는 글이 나왔다고 모두가 더 많이 훔칠까 걱정하기보다는, 상점들이 그 수법을 더 잘 알아차릴 가능성 쪽에 무게를 두겠음
  • 각 샘플에 무작위 값을 더하는 대신, Safari는 매시간 바뀌는 키에 기반한 결정적 잡음을 더할 수도 있어 보임
    오디오 샘플과 키의 함수로 만들면 같은 세션에서는 잡음이 같지만, 한 시간 뒤 추적에는 쓸모없어짐

    • 그런 샘플 10개를 평균 내면, 결국 장치의 실제 값에 가까워짐. 샘플이 많을수록 더 가까워질 것임
      이걸 고치려면 정보 누출 자체를 제거해야지, 무작위 편차 층으로 가리는 것만으로는 부족함
    • 추가되는 잡음이 출처(origin) 를 기준으로 결정적이면 도움이 되지 않을까? 그러면 과도한 샘플링으로 평균을 내도 제거할 수 없음
      예를 들면 RNG_SEED = HMAC_SHA256(PERSISTENT_SECRET,Location.origin) 같은 방식임
  • 이제 정말 JavaScript를 끄고 웹을 보는 "그 사람"이 될 준비가 됐음

    • 문제는 그렇게 "그 사람"이 되는 것만으로도 식별에 10비트 이상을 넘겨줄 가능성이 있다는 점임
      다른 곳에서 몇 비트만 더 긁어오면 고유하게 식별될 수 있음. 그래도 내 기준으로는 이 사람들은 나머지 광고기술 업계와 함께 Golgafrinchan Ark B에 태워 보내도 됨
    • 행운을 빔. 요즘 웹에 제대로 된 오래된 HTML이 얼마나 적은지 놀라울 정도임
      얼마 전 방문한 웹사이트는 마크업을 쓰긴 했는데, 그걸 HTML로 컴파일해 정적으로 제공한 게 아니라 클라이언트 쪽 JavaScript로 렌더링했음. WTF
    • 같이 하자, 실제로 해보면 됨. uMatrix라는 훌륭한 Firefox 확장 기능이 있어서 사이트별뿐 아니라 하위 도메인별로도 JavaScript를 쉽게 끌 수 있고, JavaScript 없이는 깨지는 사이트에서는 다시 켜기도 쉬움
    • 행운을 빔. 최근에 이 싸움을 포기했음. 방문하는 거의 모든 웹사이트에서 콘텐츠를 보려면 JavaScript를 다시 켜야 했기 때문임
      Cloudflare 같은 DDoS 검사뿐 아니라, 이제는 페이지 HTML 안에 있어야 할 것들조차 JavaScript로 로드됨
    • 바로 이런 이유 때문에 Tor Browser는 JavaScript를 꺼야 하는 것임
      인터넷이 점점 더 적대적으로 변할수록, 이 선택은 점점 더 맞는 방향이 됨
  • 이 방식이 몇천 개 이상의 고유 조합을 만들 수 있다는 게 잘 이해되지 않음
    브라우저 종류 × 브라우저 버전 × 운영체제 버전 × 가속기 버전 × … 또 뭐가 있지? 원격으로 고유하다고 할 만큼의 변이가 충분해 보이지 않음

    • 조합론은 가혹한 여주인임
  • 이 기법은 오디오 처리에서 하드웨어, 드라이버, 운영체제 차이를 기준으로 지문을 찍는 건가, 아니면 그냥 브라우저 소프트웨어만 보는 건가?
    하위 그래픽 장치의 차이를 노출하는 비슷한 기법이 있었거나 아직 있을 거라고 봄

    • 비슷한 방식임. 오디오 알고리즘은 종종 운영체제 함수를 호출하고 CPU 최적화를 활용함
      글에서 든 예 중 하나가 고속 푸리에 변환(FFT)임. 모든 운영체제에는 이 함수의 버전이 있지만 시간이 지나며 최적화되는 경향이 있고, 사용 가능한 SIMD 명령에 따라 CPU마다 다르게 동작하는 경우가 많음