#DRM
#암호화
Written by Paul
1. DRM의 개요
디지털 저작권 관리(DRM, Digital Rights Management)는 디지털 콘텐츠의 불법 복제 및 무단 사용을 방지하는 기술입니다. 주로 영상 스트리밍, 전자책, 게임 등에서 사용됩니다.
1.1 DRM의 핵심 기능
- 콘텐츠 암호화 – AES-CTR을 이용하여 콘텐츠 보호
- 라이선스 제어 – 인증된 사용자만 콘텐츠 접근 가능
- 기기 인증 및 보안 정책 적용 – 승인된 기기에서만 재생 가능
- 멀티 DRM 지원 – Widevine(Google), PlayReady(Microsoft), FairPlay(Apple) 등
2. DRM에서 사용되는 암호화 방식
2.1 AES-CTR (Counter Mode)
- 방법:
- 각 블록마다 증가하는 카운터 값을 암호화하여 키 스트림을 생성 후 평문과 XOR 연산
- 랜덤 액세스가 가능하여 스트리밍 서비스에 적합
- 장점:
- 빠른 암호화/복호화 속도
- 병렬 처리 가능
- 랜덤 액세스 지원
- 단점:
- Nonce(일회성 값) 재사용 시 보안 취약
- 사용 예:
- 스트리밍 서비스
🔹 AES-CTR 암호화 과정
(Nonce + 1 → 🔒) ⊕ P1 → C1
(Nonce + 2 → 🔒) ⊕ P2 → C2
(Nonce + 3 → 🔒) ⊕ P3 → C3
3. DRM 라이선스 서버와 키 관리
3.1 DRM 라이선스 서버의 역할
- 암호화된 콘텐츠를 재생하기 위해서는 DRM 라이선스 서버에서 키를 받아야 함
- 라이선스 서버는 사용자의 기기, 계정, 정책을 검증한 후 대칭키(AES 키)를 제공
- 대칭키는 공개키(RSA, ECC)로 암호화되어 전달됨
3.2 키 교환 과정
- 사용자가 콘텐츠 재생 요청
- 클라이언트가 DRM 라이선스 서버에 요청 전송
- 서버에서 사용자의 기기 및 계정 검증
- 허가된 경우, 암호화된 대칭키 제공 (공개키 암호화 사용)
- 클라이언트는 받은 키를 복호화하여 콘텐츠 해독
📌 정리
✔ AES(대칭키) → 콘텐츠 암호화
✔ RSA(공개키) → 대칭키 보호 및 전송
✔ 최종적으로 콘텐츠 복호화에 사용되는 키는 "대칭키(AES)"
4. DRM 암호화 구현 예제 (Python)
from Crypto.Cipher import AES from Crypto.Util import Counter import base64 def encrypt_content(content, key, nonce): counter = Counter.new(64, prefix=nonce) cipher = AES.new(key, AES.MODE_CTR, counter=counter) encrypted_content = cipher.encrypt(content.encode()) return base64.b64encode(encrypted_content).decode() key = b'16byteslongkey!' nonce = b'unique_iv' original_content = "DRM 보호된 콘텐츠입니다." encrypted_content = encrypt_content(original_content, key, nonce) print("🔹 암호화된 콘텐츠:", encrypted_content)
def decrypt_content(encrypted_data, key, nonce): encrypted_data = base64.b64decode(encrypted_data) counter = Counter.new(64, prefix=nonce) cipher = AES.new(key, AES.MODE_CTR, counter=counter) return cipher.decrypt(encrypted_data).decode() decrypted_content = decrypt_content(encrypted_content, key, nonce) print("🔹 복호화된 콘텐츠:", decrypted_content)
5. FairPlay DRM 구현 예시 (웹)
5.1 웹에서 FairPlay DRM 구현 (JavaScript)
웹 브라우저에서 FairPlay DRM을 구현하는 방법은 Encrypted Media Extensions (EME) API를 사용하여 FairPlay 키를 처리하는 방식입니다. FairPlay는
com.apple.fps.1_0
키 시스템을 사용합니다.<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>FairPlay DRM Example</title> </head> <body> <video id="video" controls></video> <script> const videoElement = document.getElementById("video"); // FairPlay 라이선스 URL const licenseUrl = "https://license.example.com/fairplay"; // Encrypted Media Extensions (EME) API 초기화 if (navigator.requestMediaKeySystemAccess) { const config = [{ initDataTypes: ['cenc'], videoCapabilities: [{ contentType: 'video/mp4; codecs="avc1.4d401e"' }] }]; // FairPlay 키 시스템을 사용하여 요청 navigator.requestMediaKeySystemAccess('com.apple.fps.1_0', config) .then(keySystemAccess => { return keySystemAccess.createMediaKeys(); }) .then(mediaKeys => { videoElement.setMediaKeys(mediaKeys); // Play DRM 콘텐츠 const mediaSource = new MediaSource(); videoElement.src = mediaSource; mediaSource.addEventListener('sourceopen', () => { // 라이선스 서버에서 키를 요청 mediaSource.sourceBuffer.addEventListener('encrypted', event => { const session = mediaKeys.createSession(); session.onmessage = (e) => { // 라이선스 서버에서 받은 응답을 통해 DRM 키 전달 const licenseRequest = new XMLHttpRequest(); licenseRequest.open('POST', licenseUrl); licenseRequest.setRequestHeader('Content-Type', 'application/octet-stream'); licenseRequest.send(e.message); }; session.generateRequest(event.initDataType, event.initData); }); }); mediaSource.addSourceBuffer('video/mp4; codecs="avc1.4d401e"'); mediaSource.addSourceBuffer('audio/mp4; codecs="mp4a.40.2"'); }) .catch(error => { console.error("FairPlay DRM 지원 오류:", error); }); } else { console.error("Encrypted Media Extensions (EME) 미지원"); } </script> </body> </html>
6. ExoPlayer + Widevine DRM 구현 예시 (Android)
6.1 ExoPlayer + Widevine DRM 구현 (Android)
ExoPlayer
는 Android에서 DRM 콘텐츠를 재생할 때 가장 많이 사용되는 라이브러리입니다. Widevine DRM을 사용하여 DRM 보호된 비디오 콘텐츠를 재생할 수 있습니다. 아래는 ExoPlayer와 Widevine DRM을 활용한 예제입니다.import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import com.google.android.exoplayer2.ExoPlayer import com.google.android.exoplayer2.MediaItem import com.google.android.exoplayer2.source.dash.DashMediaSource import com.google.android.exoplayer2.upstream.DefaultHttpDataSource import com.google.android.exoplayer2.upstream.HttpDataSource import com.google.android.exoplayer2.drm.DrmSessionManager import com.google.android.exoplayer2.drm.DefaultDrmSessionManager import com.google.android.exoplayer2.drm.DefaultDrmProvider import com.google.android.exoplayer2.drm.WidevineUtil import com.google.android.exoplayer2.util.MimeTypes import com.google.android.exoplayer2.upstream.DataSource import android.net.Uri class DRMPlayerActivity : AppCompatActivity() { private lateinit var exoPlayer: ExoPlayer override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // DRM 콘텐츠 URL val contentUrl = "https://example.com/drm/protected_video.mpd" // Widevine 라이선스 URL val licenseUrl = "https://license.example.com/widevine" // 기본 HTTP 데이터 소스 val dataSourceFactory: HttpDataSource.Factory = DefaultHttpDataSource.Factory() // Widevine DRM 세션 관리 설정 val drmCallback: DrmSessionManager.DrmSessionCallback = DefaultDrmProvider(licenseUrl) val drmSessionManager = DefaultDrmSessionManager(drmCallback) // Dash MediaSource 설정 val mediaItem = MediaItem.fromUri(Uri.parse(contentUrl)) val mediaSource = DashMediaSource.Factory(dataSourceFactory).createMediaSource(mediaItem) // ExoPlayer 초기화 exoPlayer = ExoPlayer.Builder(this).build() exoPlayer.setMediaSource(mediaSource) exoPlayer.prepare() exoPlayer.play() } override fun onDestroy() { super.onDestroy() exoPlayer.release() } }
7. iOS FairPlay DRM 콘텐츠 복호화 예시
7.1 FairPlay DRM을 활용한 콘텐츠 복호화
import AVFoundation import UIKit class DRMPlayerViewController: UIViewController { var player: AVPlayer? override func viewDidLoad() { super.viewDidLoad() // DRM이 적용된 콘텐츠 URL let contentURL = URL(string: "https://example.com/encrypted_video.m3u8")! // DRM 라이선스 서버 URL let licenseServerURL = URL(string: "https://license.example.com/fairplay")! let asset = AVURLAsset(url: contentURL) asset.resourceLoader.setDelegate(self, queue: DispatchQueue.main) let playerItem = AVPlayerItem(asset: asset) player = AVPlayer(playerItem: playerItem) let playerLayer = AVPlayerLayer(player: player) playerLayer.frame = self.view.bounds self.view.layer.addSublayer(playerLayer) player?.play() } } // AVAssetResourceLoaderDelegate 구현 (라이선스 요청 처리) extension DRMPlayerViewController: AVAssetResourceLoaderDelegate { func resourceLoader(_ resourceLoader: AVAssetResourceLoader, shouldWaitForLoadingOfRequestedResource loadingRequest: AVAssetResourceLoadingRequest) -> Bool { guard let url = loadingRequest.request.url else { return false } // DRM 키 요청 처리 (FairPlay 라이선스 서버와 통신) let licenseRequest = URLRequest(url: URL(string: "https://license.example.com/fairplay")!) let task = URLSession.shared.dataTask(with: licenseRequest) { data, response, error in guard let data = data, error == nil else { loadingRequest.finishLoading(with: error) return } // DRM 라이선스 서버에서 받은 데이터로 복호화된 콘텐츠를 응답 loadingRequest.dataRequest?.respond(with: data) loadingRequest.finishLoading() } task.resume() return true } }
7.2 복호화 과정 설명
- 컨텐츠 요청:
AVPlayer
는AVURLAsset
을 사용하여 DRM이 적용된 콘텐츠를 요청합니다.
- 라이선스 서버와의 통신: 콘텐츠가 암호화되어 있으면,
AVAssetResourceLoaderDelegate
의shouldWaitForLoadingOfRequestedResource
메소드가 호출되고, 여기서 라이선스 요청을 FairPlay DRM 서버에 전송합니다.
- 복호화 키 수신: 서버는 복호화에 필요한 키를 응답으로 보내며, 이 키를 사용하여 콘텐츠를 복호화합니다.
- 콘텐츠 복호화 및 응답: 서버로부터 받은 데이터는
loadingRequest.dataRequest?.respond(with:)
를 통해 응답되어 복호화된 콘텐츠가AVPlayer
로 전달됩니다.
7.3 FairPlay DRM 키 처리 및 콘텐츠 복호화
이 코드에서는 FairPlay DRM 라이선스 서버와의 통신을 통해 키를 받아 콘텐츠를 복호화하는 과정을 보여주고 있습니다. 복호화된 콘텐츠는
AVPlayer
에서 재생할 수 있도록 처리됩니다.이 코드는 iOS 앱에서 FairPlay DRM을 적용한 콘텐츠를 안전하게 복호화하고 재생하는 방법을 다루며, 라이선스 요청 및 처리 과정을 간단히 구현한 예제입니다.
8. Web Browser에서 Widevine DRM 콘텐츠 복호화 예시 (JavaScript)
8.1 Widevine DRM을 활용한 웹 브라우저에서 콘텐츠 복호화
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Widevine DRM Video</title> </head> <body> <video id="video" controls width="640" height="360"></video> <script> // DRM이 적용된 비디오 URL const videoUrl = 'https://example.com/path/to/your/encrypted/video.mpd'; // Widevine 라이선스 서버 URL const licenseServerUrl = 'https://license.example.com/widevine'; // video 엘리먼트 가져오기 const video = document.getElementById('video'); // Encrypted Media Extensions (EME) API 사용 if ('requestMediaKeySystemAccess' in navigator) { navigator.requestMediaKeySystemAccess('com.widevine.alpha', [{ initDataTypes: ['cenc'], videoCapabilities: [{ contentType: 'video/mp4; codecs="avc1.42E01E"' }] }]) .then(keySystemAccess => { // 미디어 키 세션 생성 return keySystemAccess.createMediaKeys(); }) .then(mediaKeys => { // 비디오 요소에 MediaKeys 설정 video.setMediaKeys(mediaKeys); // 요청할 라이선스 서버 URL const licenseRequest = { type: 'license', url: licenseServerUrl, initData: new Uint8Array() }; // 라이선스 서버와 통신하여 키를 받아옴 return fetch(licenseRequest.url, { method: 'POST', body: JSON.stringify(licenseRequest), headers: { 'Content-Type': 'application/json' } }).then(response => response.arrayBuffer()) .then(licenseData => { // 받은 라이선스 데이터로 키를 설정 mediaKeys.setServerCertificate(new Uint8Array(licenseData)); return mediaKeys; }); }) .then(mediaKeys => { // 복호화된 콘텐츠 재생 const mediaSource = new MediaSource(); video.src = URL.createObjectURL(mediaSource); // 추가적인 로직을 통해 DRM을 통해 암호화된 콘텐츠를 재생하는 과정 // (예: MediaSource API와 연결된 MediaPlayer 객체를 이용해 실제 비디오 데이터 처리) }) .catch(error => { console.error('Error setting up Widevine DRM:', error); }); } else { console.error('EME not supported on this browser.'); } </script> </body> </html>
8.2 복호화 과정 설명
- 미디어 키 시스템 접근 요청:
navigator.requestMediaKeySystemAccess
메소드를 사용하여 브라우저에 Widevine 키 시스템을 요청합니다. 이때 필요한 코덱 및 미디어 데이터 타입을 정의합니다.
- MediaKeys 생성:
- Widevine DRM 키를 관리하는 객체인
MediaKeys
를 생성하고, 이를<video>
엘리먼트에 설정하여 복호화 작업을 준비합니다.
- 라이선스 서버와 통신:
- 라이선스 서버로 HTTP 요청을 보내어 콘텐츠 복호화에 필요한 키를 받습니다. 이때, Widevine 라이선스 서버는 POST 요청을 통해 콘텐츠를 해독하는 데 필요한 라이선스를 반환합니다.
- 복호화 및 콘텐츠 재생:
- 받은 라이선스를 통해 복호화된 콘텐츠가 HTML5
<video>
엘리먼트에서 재생됩니다. DRM 보호가 적용된 콘텐츠는 이 과정을 통해 복호화되고 재생 가능합니다.
8.3 Widevine DRM 복호화 프로세스
- Widevine은 Encrypted Media Extensions (EME) API와 함께 작동하여 브라우저에서 DRM 보호된 콘텐츠를 복호화합니다.
- 웹에서 DRM 콘텐츠를 재생하려면, MediaKeySystemAccess, MediaKeys, MediaKeySession 등의 API를 활용하여 라이선스 요청, 복호화 및 콘텐츠 재생을 처리할 수 있습니다.
- 이 예제는 Widevine DRM을 사용하여 콘텐츠를 보호하고 복호화하는 과정입니다.
9. 결론
🔹 DRM 솔루션은 AES-CTR을 활용하여 콘텐츠 보호
🔹 라이선스 서버를 통해 대칭키를 안전하게 전달하여 보안 강화
🔹 FairPlay, Widevine, PlayReady DRM을 통해 다양한 플랫폼에서 안전한 콘텐츠 보호 가능
🔹 스트리밍 서비스에서 빠르고 효율적인 DRM 구현 가능
스트리밍 서비스 등은 AES-CTR 기반 DRM 구조를 사용하며, 각 플랫폼에 맞는 FairPlay, Widevine, PlayReady DRM 솔루션을 통해 콘텐츠 보호를 강화하고 있습니다. 🚀
10. DRM 구현 예제 종합
위에서 다룬 FairPlay와 Widevine DRM을 사용하는 실제 코드 예제들을 통해, 다양한 플랫폼에서 DRM 콘텐츠 보호를 구현할 수 있음을 알 수 있습니다.
10.1. Widevine DRM 구현 예시 (Android)
import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import com.google.android.exoplayer2.ExoPlayer import com.google.android.exoplayer2.MediaItem import com.google.android.exoplayer2.source.dash.DashMediaSource import com.google.android.exoplayer2.upstream.DefaultHttpDataSource import com.google.android.exoplayer2.drm.DefaultDrmSessionManager import com.google.android.exoplayer2.drm.DrmSessionManager import com.google.android.exoplayer2.drm.WidevineUtil import android.net.Uri class DRMPlayerActivity : AppCompatActivity() { private lateinit var exoPlayer: ExoPlayer override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // DRM 콘텐츠 URL val contentUrl = "https://example.com/drm/protected_video.mpd" // Widevine 라이선스 URL val licenseUrl = "https://license.example.com/widevine" // 기본 HTTP 데이터 소스 val dataSourceFactory: HttpDataSource.Factory = DefaultHttpDataSource.Factory() // Widevine DRM 세션 관리 설정 val drmCallback: DrmSessionManager.DrmSessionCallback = DefaultDrmProvider(licenseUrl) val drmSessionManager = DefaultDrmSessionManager(drmCallback) // Dash MediaSource 설정 val mediaItem = MediaItem.fromUri(Uri.parse(contentUrl)) val mediaSource = DashMediaSource.Factory(dataSourceFactory).createMediaSource(mediaItem) // ExoPlayer 초기화 exoPlayer = ExoPlayer.Builder(this).build() exoPlayer.setMediaSource(mediaSource) exoPlayer.prepare() exoPlayer.play() } override fun onDestroy() { super.onDestroy() exoPlayer.release() } }
10.2. iOS에서 FairPlay DRM 구현 예시 (Swift)
import AVFoundation import UIKit class DRMPlayerViewController: UIViewController { var player: AVPlayer? override func viewDidLoad() { super.viewDidLoad() // DRM이 적용된 콘텐츠 URL let contentURL = URL(string: "https://example.com/encrypted_video.m3u8")! // DRM 라이선스 서버 URL let licenseServerURL = URL(string: "https://license.example.com/fairplay")! let asset = AVURLAsset(url: contentURL) asset.resourceLoader.setDelegate(self, queue: DispatchQueue.main) let playerItem = AVPlayerItem(asset: asset) player = AVPlayer(playerItem: playerItem) let playerLayer = AVPlayerLayer(player: player) playerLayer.frame = self.view.bounds self.view.layer.addSublayer(playerLayer) player?.play() } } // AVAssetResourceLoaderDelegate 구현 (라이선스 요청 처리) extension DRMPlayerViewController: AVAssetResourceLoaderDelegate { func resourceLoader(_ resourceLoader: AVAssetResourceLoader, shouldWaitForLoadingOfRequestedResource loadingRequest: AVAssetResourceLoadingRequest) -> Bool { guard let url = loadingRequest.request.url else { return false } // DRM 키 요청 처리 (FairPlay 라이선스 서버와 통신) let licenseRequest = URLRequest(url: URL(string: "https://license.example.com/fairplay")!) let task = URLSession.shared.dataTask(with: licenseRequest) { data, response, error in guard let data = data, error == nil else { loadingRequest.finishLoading(with: error) return } loadingRequest.dataRequest?.respond(with: data) loadingRequest.finishLoading() } task.resume() return true } }
10.3. 웹 브라우저에서 Widevine DRM 구현 예시 (JavaScript)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Widevine DRM Video</title> </head> <body> <video id="video" controls width="640" height="360"></video> <script> // DRM이 적용된 비디오 URL const videoUrl = 'https://example.com/path/to/your/encrypted/video.mpd'; // Widevine 라이선스 서버 URL const licenseServerUrl = 'https://license.example.com/widevine'; // video 엘리먼트 가져오기 const video = document.getElementById('video'); // Encrypted Media Extensions (EME) API 사용 if ('requestMediaKeySystemAccess' in navigator) { navigator.requestMediaKeySystemAccess('com.widevine.alpha', [{ initDataTypes: ['cenc'], videoCapabilities: [{ contentType: 'video/mp4; codecs="avc1.42E01E"' }] }]) .then(keySystemAccess => { // 미디어 키 세션 생성 return keySystemAccess.createMediaKeys(); }) .then(mediaKeys => { // 비디오 요소에 MediaKeys 설정 video.setMediaKeys(mediaKeys); // 요청할 라이선스 서버 URL const licenseRequest = { type: 'license', url: licenseServerUrl, initData: new Uint8Array() }; // 라이선스 서버와 통신하여 키를 받아옴 return fetch(licenseRequest.url, { method: 'POST', body: JSON.stringify(licenseRequest), headers: { 'Content-Type': 'application/json' } }).then(response => response.arrayBuffer()) .then(licenseData => { // 받은 라이선스 데이터로 키를 설정 mediaKeys.setServerCertificate(new Uint8Array(licenseData)); return mediaKeys; }); }) .then(mediaKeys => { // 복호화된 콘텐츠 재생 const mediaSource = new MediaSource(); video.src = URL.createObjectURL(mediaSource); // 추가적인 로직을 통해 DRM을 통해 암호화된 콘텐츠를 재생하는 과정 }) .catch(error => { console.error('Error setting up Widevine DRM:', error); }); } else { console.error('EME not supported on this browser.'); } </script> </body> </html>