암호화된 영상을 전체를 받지 않고 복호화 재생하기

웹 · 앱 개발5분 읽기

제로널리지에서 영상 재생은 가장 풀기 어려운 조각입니다. 서버는 평문에 손댈 수 없으니 트랜스코딩도 서버 사이드 스트리밍도 불가능하고 브라우저가 암호문을 받아 스스로 복호화해 재생해야 합니다. 그런데 긴 영상을 전부 내려받아 복호화한 다음에야 재생할 수 있다면 사용자 경험이 무너집니다. Ciphek은 MediaSource API와 mp4box.js를 조합해 이 문제를 스트리밍으로 풀었습니다.

표준 복호화가 스트리밍과 안 맞는 이유

가장 단순한 방법은 브라우저 표준 AES-GCM으로 파일을 복호화하는 것입니다. 하지만 AES-GCM 복호화는 전체 인증 태그를 확인해야 완료되기 때문에, 조각을 받는 족족 복호화해 흘려보내는 스트리밍과 근본적으로 맞지 않습니다. 결국 전체를 버퍼에 쌓아 놓고 한 번에 복호화해야 하는데, 이것은 통짜 다운로드와 다르지 않습니다. 그래서 청크 단위로 독립 복호화가 가능한 XChaCha20-Poly1305를 미디어에 쓰고 재생 쪽은 이 청크들을 받아 순차로 복호화하는 파이프라인을 직접 구성했습니다.

재생 버퍼로는 브라우저의 MediaSource API를 씁니다. MediaSource는 자바스크립트가 미디어 데이터를 조각조각 SourceBuffer에 밀어 넣으면 브라우저가 그것을 이어서 재생하게 해 주는 표준입니다. 우리는 암호문 청크를 받아 복호화한 뒤, mp4box.js로 컨테이너를 해석해 재생 가능한 세그먼트로 만들고 SourceBuffer에 append합니다. 서버는 이 과정에 전혀 관여하지 않고 짧은 수명의 접근 URL만 발급합니다.

moov가 어디 있느냐는 문제

MP4를 재생하려면 코덱과 트랙 정보가 담긴 moov 아톰을 먼저 읽어야 합니다. 문제는 이 moov가 파일 앞에 있을 수도, 끝에 있을 수도 있다는 것입니다. 웹용으로 최적화된 파일은 moov가 앞에 있어 첫 청크만 읽으면 되지만 휴대폰이나 캠코더가 만든 파일은 moov가 끝에 붙습니다. 앞에서부터 순서대로 청크를 받는 소박한 방식으로는 moov가 끝에 있는 파일에서 전체를 받을 때까지 재생이 시작되지 않습니다.

그래서 초기화 단계에서 첫 청크와 마지막 청크를 병렬로 먼저 가져와 mp4box.js에 물려 봅니다. 대부분의 파일은 이 두 청크 안에 moov가 있어 곧바로 재생 준비가 끝납니다. 드물게 moov가 가운데 있는 특이한 파일은 양쪽에서 안쪽으로 좁혀 들어가며 병렬로 탐색합니다. 이 전략 덕분에 파일이 어떤 방식으로 먹싱됐든 전체 다운로드를 기다리지 않고 재생을 시작할 수 있습니다.

앞서 받고 다시 쓰기

재생이 시작되면 현재 위치보다 몇 청크 앞을 미리 가져옵니다. Ciphek은 기본적으로 재생 지점보다 2청크를 앞서 프리페치해 네트워크가 잠깐 흔들려도 재생이 끊기지 않게 여유를 둡니다. 이미 복호화한 청크는 LRU 캐시에 담아 사용자가 뒤로 돌려 다시 볼 때 재복호화 없이 재사용합니다. 복호화는 CPU를 쓰는 작업이라 캐시는 성능과 배터리 모두에 도움이 됩니다.

탐색(seek)도 이 구조 위에서 돌아갑니다. 사용자가 타임라인의 한 지점을 누르면 그 시각에 해당하는 바이트 오프셋을 mp4box.js로 계산해 필요한 청크만 가져와 복호화합니다. 앞에서부터 순서대로 받을 필요가 없으니 긴 영상의 뒷부분으로 바로 건너뛰어도 그 지점 청크만 받으면 됩니다. 재생 버퍼가 한계에 다다르면 지나간 구간을 비워 메모리를 회수하는 처리도 함께 둬서 몇 시간짜리 영상에서도 버퍼가 넘치지 않게 했습니다.

브라우저마다 다른 현실

MediaSource는 표준이지만 브라우저마다 사정이 다릅니다. iPhone 사파리는 오랫동안 일반 MediaSource를 노출하지 않다가 iOS 17.1부터 ManagedMediaSource라는 변형을 제공하기 시작했습니다. 그래서 재생 엔진은 실행 환경에 따라 일반 MediaSource와 ManagedMediaSource 중 맞는 것을 골라 쓰고 ManagedMediaSource가 요구하는 원격 재생 비활성화 같은 조건도 함께 처리합니다. 데스크톱과 안드로이드, 아이패드는 일반 MediaSource 경로로 갑니다.

어떤 브라우저나 컨테이너는 스트리밍 자체가 불가능할 수 있습니다. MediaSource가 없거나 코덱이 지원되지 않으면, 무리하게 스트리밍을 밀어붙이지 않고 전체를 내려받아 복호화한 뒤 재생하는 다운로드 모드로 자연스럽게 폴백합니다. 스트리밍이 되면 좋지만 안 될 때도 영상은 반드시 재생돼야 하기 때문입니다. 이렇게 최선 경로와 안전 경로를 함께 두는 것이 다양한 기기를 지원하는 실서비스의 조건이었습니다.

이 재생이 다루는 청크가 어떻게 암호화되는지는 XChaCha20 청크 AAD 바인딩에서, 청크가 저장되고 접근되는 방식은 R2 zero egress 암호문 저장에서 다룹니다. 전체 구조는 제로널리지 암호화 미디어 클라우드 사례에 있습니다.

자주 묻는 질문

서버가 복호화하지 않는데 어떻게 스트리밍이 되나요?

서버는 암호문 청크와 짧은 수명의 접근 URL만 제공하고 실제 복호화와 재생은 브라우저가 담당합니다. 브라우저가 필요한 청크만 받아 복호화한 뒤 MediaSource의 재생 버퍼에 흘려보내므로, 서버가 평문을 다루지 않아도 앞부분부터 스트리밍 재생이 가능합니다.

moov가 끝에 있는 영상도 바로 재생되나요?

네. 초기화 단계에서 첫 청크와 마지막 청크를 병렬로 먼저 가져와 코덱과 트랙 정보를 담은 moov 아톰을 찾습니다. 대부분의 파일은 이 두 청크 안에 moov가 있어 전체 다운로드를 기다리지 않고 재생을 시작할 수 있고 moov가 가운데 있는 드문 경우에는 안쪽으로 좁혀 가며 탐색합니다.

스트리밍이 안 되는 브라우저에서는 어떻게 되나요?

MediaSource가 없거나 코덱이 지원되지 않는 환경에서는 전체 파일을 내려받아 복호화한 뒤 재생하는 다운로드 모드로 폴백합니다. iPhone 사파리는 iOS 17.1부터 제공되는 ManagedMediaSource 경로로 스트리밍하고 그 이전 버전이나 미지원 환경에서는 안전하게 다운로드 후 재생으로 처리합니다.

#MediaSource#mp4box#스트리밍#청크복호화#브라우저

업무자동화 구축이 필요하신가요?

무료 상담 신청