시작하며
안녕하세요. 저는 WebRTC셀에서 서버개발을 담당하고 있는 hans(한현섭)입니다.
여러분들은 코로나 시대에 잘 적응하고 계신가요? 작년에 이어 올해까지 계속되고 있는 코로나 시대를 맞이하여 뉴노멀(New Normal)이 노멀(Normal)한 시대가 되면서, 업무 환경은 “대면”에서 “비대면”이 대세가 되었습니다. 특히 재택근무와 화상회의 등의 비대면 업무 진행에 대한 요구가 폭발적으로 증가했는데요. 화상회의 사용자 수는 2020년 1분기부터 급속도로 증가하기 시작했고, 수요는 지속적인 상승 곡선을 그리고 있습니다. 더욱이 사용성이나 보안 등의 이유로 화상회의 도입을 미뤄왔던 회사나 단체들도 최근 시대적 흐름에 따라 화상회의를 적극적으로 도입하려는 움직임이 포착되고 있습니다.
하지만 이렇게 갑작스럽게 찾아온 COVID-19 만큼 화상회의 서비스 업체들도 큰 혼란을 겪어야 했습니다. 우선 급격히 증가한 트래픽을 감당해야 했으며, 갑작스러운 사용자 증가나 보안 문제에 대응하는데 많은 시간을 쏟아야 했습니다. 코로나 시대의 업무 환경에서 화상회의가 더 이상 선택이 아닌 필수로 자리매김하면서 사용자들은 화상회의뿐만 아니라 보안에도 큰 관심을 갖기 시작했는데요. 작년에는 화상회의의 보안 취약점을 찾아내 비대면 수업을 방해하는 사건이 뉴스에 보도되기도 했습니다. 급격한 트래픽과 사용자 증가, 보안 이슈 등으로 2020년은 화상회의 서비스 업체들에게는 잊지 못할 한 해로 기억될 것입니다.
사실 메시징 업계에서 보안 기술은 코로나가 유행하기 훨씬 오래전부터 논의되고 있었습니다. 2010년대 중반 세계 정보기관들이 메신저 등을 감청하고 있다는 이야기가 퍼지면서, 당시 통신비밀 보장이 어려웠던 사회에서는 종단간암호화(End to End Encryption, 이하 E2EE)를 적용한 메신저들이 출시되어 인기를 끌기도 했습니다. E2EE는 사용자 단말(End)에서 사용자 단말(End) 말고는 아무도 메시지를 확인할 수 없도록 하는 기술로서, 비밀대화, 비밀채팅, 또는 레터실링 등의 명칭으로 제공되고 있습니다.
화상회의의 경우, 이런 메시지보다 개인 정보나 기밀 정보가 더 많을 수 있기 때문에 보안이 더욱 중요합니다. 이런 이유에서 대다수의 화상회의 서비스 업체들은 E2EE를 제공하기 위해 꾸준히 노력해왔는데요. 대표적으로 Google이 개발하여 Chrome 86부터 지원하고 있는 WebRTC Insertable Stream이라는 API가 있습니다. Insertable Stream(인서터블 스트림)은 보안뿐만 아니라 AR(Augmented Reality, 증강 현실), STT(Speech to Text) 등 다양한 AI 기술에도 도움이 되는 기능이기도 한데요. 그러면 Insertable Stream은 무엇이고, 어떻게 사용해야 하는지 한번 알아보도록 하겠습니다.
WebRTC Insertable Stream과 E2EE
WebRTC MediaServer의 보안 문제
WebRTC(Web Real-Time Communication)는 TLS(Transport Layer Security)의 UDP버전인 DTLS(Datagram Transport Layer Security)를 통해 암호화하여 패킷을 송수신합니다.
1:1 영상통화의 경우, 두 브라우저 간 패킷들은 HTTPS처럼 확실하게 암호화가 되어 전달됩니다.
반면 화상회의와 같이 다수의 사람들이 동시에 영상을 주고받는 경우에는 클라이언트의 퍼포먼스 한계 때문에 미디어서버의 도움을 받게 됩니다. 이때 각 브라우저는 미디어 서버와 PeerConnection을 맺어서 영상을 전송하거나 전송받게 됩니다. 즉 1:1 영상통화가 브라우저끼리 P2P로 연결되었다면, 화상회의는 서버와 P2P로 연결되는 것입니다.
이와 같이 미디어서버와 브라우저 간 영상을 주고받는 경우, PeerConnection사이에서만 암호화(Secure RTP Stream)가 될 뿐, 일단 서버가 영상을 받고 나면 복호화(RTP Stream)가 되어 영상의 내용을 확인할 수 있게 됩니다. 이 덕분에 우리는 MCU(Multipoint Control Unit) 미디어서버처럼 여러 사람들의 영상을 녹화하여 하나의 영상으로 합성하기도 하고, RTP 패킷을 인코딩하여 녹화된 동영상 파일을 만들기도 합니다.
이로인해 모든 WebRTC 서비스 제공자들은 사용자들의 영상을 활용하여 서버사이드 녹화, AR, 이미지 필터 등의 편의 서비스를 제공할 수도 있지만, 해킹으로 인한 정보 유출 가능성도 감수해야 합니다. 또한 통신비밀 보장 측면에서도, 회의 내용이 외부로 노출되면 회사 차원에서 큰 손해를 입을 가능성도 배제할 수는 없습니다.
태생적으로 모든 영상을 하나로 합성하여 각각의 참여자에게 송신하는 MCU 미디어 서버는 근본적으로 이 문제를 해결할 수 어렵습니다.
그렇다면 SFU(Selective Forwarding Unit) 미디어 서버는 어떨까요? 가장 기본적인 사용환경에서 단지 패킷을 받아서 복제해 나눠주기만 하는(Routing) SFU 미디어 서버는 패킷에 어떤 내용이 있는지에 대해 전혀 신경 쓸 이유가 없습니다. 그러므로 MCU보다는 보안을 강화 할 수 있는 여지가 많죠. 그러나 단지 처리 과정 중에 확인하지 않을 뿐 여전히 수신한 패킷을 열어 볼 수 있는 상황은 바뀌지 않았습니다. 왜냐하면 브라우저에서 보내는 프레임 데이터를 서버가 확인할 수 없도록, 클라이언트가 패킷을 조작하는 API가 없었기 때문입니다.
E2EE의 동작방식
HTTPS가 보편화되면서 웹서비스 대부분의 패킷들은 네트워크를 통과하면서 암호화됩니다. (WebRTC도 DTLS를 통해 지원합니다). 그렇다면 서버에서도 암호화가 되어 있을까요? 서버에 저장되는 내용들은 패스워드 같은 극히 민감한 정보를 제외하고는 서비스 제공자가 내용을 확인할 수 있는 형태로 저장되는 것이 일반적입니다. 이 때문에 서버가 해킹당했을 때는 개인정보 유출이 발생합니다. 프라이버시 보호를 최우선으로 할 때 선택할 수 있는 것이 바로 End-to-End Encryption(종단 간 암호화)인데요. 이를 적용한 서비스 중에 우리가 쉽게 쓸 수 있는 서비스가 '비밀채팅'기능입니다. 메신저에 비밀채팅을 사용하면 사용자가 보낸 데이터는 사용자 단말에 저장된 키로 암호화되고 오직 상대방만 그 데이터를 복호화 할 수 있게 됩니다. TLS의 전송영역이 아닌 애플리케이션 레벨에서도 서비스제공자가 사용자의 데이터를 확인하지 못하게 됩니다.
WebRTC는 브라우저에서 구현된 기능이고 이전까지는 개발자가 데이터를 조작하는 것이 불가능했습니다. 하지만 위에서 설명하였듯 이제 Insertable Stream API가 추가되면서 프레임데이터를 조작하는 다양한 시도를 해볼 수 있게 되었는데요. 그중 가장 손쉬우면서도 보안적으로 강력함을 더할 수 있는 E2EE 영상 전송을 살펴보겠습니다.
심플하게 E2EE 영상전송 해보기
그렇다면 WebRTC의 E2EE는 어떻게 구현하는 것일까요? 간단하게 WebRTC를 통해 프레임을 보내기 전에 특정키로 암호화하고, 받는 쪽에선 WebRTC를 통해 받은 프레임을 특정키로 복호화를 한 뒤에 플레이합니다. 그리고 아래 이미지에서 Insertable Stream이라고 하는 단계에서 처리를 하게 될 것입니다. [그림 3]
일단 Insertable Stream을 사용하기 위해서는 Chrome 86버전 이상의 버전이 필요한데요. 자신의 Chrome이 Insertable Stream을 지원하는지 알아보는 가장 간단한 방법은 개발자 콘솔에 다음 항목을 입력해보는 것입니다.
RTCRtpSender에 createEncodedStreams가 존재한다면 Insertable Stream을 사용할 수 있습니다.
Insertable Stream을 사용하기 위해서는 RTCPeerConnection을 생성할 때 해당 옵션을 추가해야 합니다.
위와 같이 옵션을 입력하셨다면, 아래와 같이 sender에서 createEncodedStreams를 호출하여 Insertable Stream을 얻을 수 있습니다. Insertable Stream은 Duplex Stream으로서 readable과 writable stream을 가지고 있으며, TransformStream을 통해 데이터를 처리할 수 있습니다.
그렇다면 어떤 데이터들이 enDecoder 함수로 들어오는지 살펴보겠습니다.
별다른 설정이 없다면 Chrome의 WebRTC는 영상코덱으로는 VP8을 사용하고 음성 코덱으로는 opus 을 사용합니다. transform 함수는 첫 번째 인자로 encodedFrame 데이터를 받습니다. encodedFrame의 type 프로퍼티를 확인하면 이 프레임이 어떤 데이터 타입을 갖는지 알 수 있습니다. type이 key 또는 delta라면 영상프레임이고, undefined라면 음성프레임입니다. Codec마다 달라질 수 있지만 VP8을 기준으로 키프레임은 10바이트, 인터프레임(델타프레임)은 3바이트의 프레임헤더를 갖습니다. Opus에서는 음성프레임에서는 1 바이트의 TOC 헤더를 가지고 있습니다.
RTP Payload Format for VP8 Video (https://tools.ietf.org/html/rfc7741#section-4.3) |
프레임의 헤더데이터까지 암호화를 하면 복호화하기 전까지 디코더가 이를 해석할 수 없습니다. SFU를 경유하는 경우에 문제가 발생할 수도 있으니, 각 SFU별 동작여부를 확인하기 전에는 우선 헤더를 제외한 페이로드만 암호화하도록 해봅시다. 덤으로 디코딩을 할 때 복호화를 하지 않고도 암호화된 프레임을 재생시켜볼 수도 있습니다.
offset을 확인하였으니, 이제 데이터에 대해서 암호화를 해봅시다. E2EE를 위해서 어떠한 암호화 방식을 사용해도 무방하지만 여러 예제에서 자주 사용되는 XOR 암호화를 사용하겠습니다.
XOR 암호화는 XOR 연산의 특징을 이용합니다. XOR 연산은 한 번 연산한 결과에 다시 한번 같은 키를 이용해 XOR 연산을 하면 원래 데이터를 복구할 수 있습니다. 어떤 방법으로든 상호 간에 키를 교환할 수 있다면 쉽게 암/복호화를 가능하게 합니다. 이를 구현한 내용은 아래와 같습니다.
encodedFrame의 data는 ArrayBuffer이기 때문에 직접 수정을 할 수는 없고, DataView 객체를 통해 다룰 수 있습니다. 기존 data를 새로운 ArrayBuffer에 XOR 연산을 하여 controller.enqueue로 다음 스트림에 넘기면 이후 PeerConnection 스트림을 통해 상대에게 전달되게 됩니다.
마치며
지금까지 Insertable Stream으로 E2EE를 구현하는 방법에 대해서 간단히 리뷰하였습니다. E2EE는 단순해 보일 수 있지만, 단지 스트림을 처리하는 것 이상의 많은 미디어 지식이 필요한 기능이라 생각합니다. 물론 데이터를 암호화 및 복호화를 하는 것 외에도 안전한 키교환, 클라이언트 부하 증가 등의 문제는 여전히 해결해야 할 숙제로 남아있습니다.
이번 포스팅에서는 WebRTC를 간단하게 소개해 드렸다면, 다음 포스팅에서는 재미있고 신기한 WebRTC 이야기로 찾아뵐 것을 약속드리면서 글을 마치도록 하겠습니다. 감사합니다.
댓글