{"id":66339,"date":"2025-03-25T16:02:50","date_gmt":"2025-03-25T10:32:50","guid":{"rendered":"https:\/\/www.tothenew.com\/blog\/?p=66339"},"modified":"2025-05-01T12:52:24","modified_gmt":"2025-05-01T07:22:24","slug":"cracking-the-code-apples-drm-technology-part-2","status":"publish","type":"post","link":"https:\/\/www.tothenew.com\/blog\/cracking-the-code-apples-drm-technology-part-2\/","title":{"rendered":"Cracking the code: Apple\u2019s DRM Technology (Part-2)"},"content":{"rendered":"<h2>How to Implement FairPlay DRM with &#8220;AVAssetResourceLoader&#8221; Delegate<\/h2>\n<h3>1. Prepare DRM-protected HLS Content:<\/h3>\n<ul>\n<li>The HLS media needs to be encoded and encrypted using <strong>FairPlay DRM<\/strong>. This process takes place on the license server, where media files are encrypted and content keys are securely stored.<\/li>\n<li>Content keys are generated and securely stored on the license server, while encrypted .ts segments and .m3u8 playlists are created for streaming.<\/li>\n<li>The encryption process creates <strong>&#8220;content keys&#8221;<\/strong> for the HLS stream, but these keys are not part of the .m3u8 playlist. Instead, the player retrieves them during playback.<\/li>\n<\/ul>\n<h3><strong>2. License Server Set Up:<\/strong><\/h3>\n<p>The player will request the license server to obtain the decryption key, which is needed to unlock and decode the encrypted media chunks (audio\/video).<\/p>\n<ul>\n<li>In this process, the license server authenticates the user (using session tokens, credentials, etc.) to confirm their access rights and then provides the decryption key.<\/li>\n<\/ul>\n<h3><strong>3. Implement AVAssetResourceLoader in Client Application (tvOS \/ iOS):<\/strong><\/h3>\n<ul>\n<li>The &#8220;AVAssetResourceLoader&#8221; delegate enables custom handling of media data (audio\/video) loading. It allows us to intercept content key requests and facilitate the retrieval of decryption keys from the license server.<\/li>\n<\/ul>\n<ul>Here are the steps to integrate this into the client application (iOS, tvOS application) for playing FairPlay-protected content:<\/ul>\n<h4>Create an AVURLAsset and assign a resourceLoader delegate to manage key requests:<\/h4>\n<blockquote><p><span style=\"color: #3366ff;\">let contentUrl = URL(string: &#8220;www.hls_stream_sample.m3u8&#8221;)<\/span><br \/>\n<span style=\"color: #3366ff;\">let avAsset = AVURLAsset(url: contentUrl!)<\/span><br \/>\n<span style=\"color: #3366ff;\">asset.resourceLoader.setDelegate(self, queue: DispatchQueue.main)<\/span><br \/>\n<span style=\"color: #3366ff;\">let playerItem = AVPlayerItem(asset: avAsset)<\/span><br \/>\n<span style=\"color: #3366ff;\">let player = AVPlayer(playerItem: playerItem)<\/span><\/p><\/blockquote>\n<h3><strong>4. Implement AVAssetResourceLoader in Client Application (tvOS \/ iOS):<\/strong><\/h3>\n<h4>Implement the AVAssetResourceLoaderDelegate protocol to capture key requests. This delegate will be used to manage content key requests as follows.<\/h4>\n<blockquote><p><span style=\"color: #3366ff;\">extension YourViewController: AVAssetResourceLoaderDelegate {<\/span><br \/>\n<span style=\"color: #3366ff;\">func resourceLoader(_ resourceLoader: AVAssetResourceLoader,<\/span><br \/>\n<span style=\"color: #3366ff;\">shouldWaitForLoadingOfRequestedResource loadingRequest: AVAssetResourceLoadingRequest) -&gt; Bool {<\/span><\/p>\n<p><span style=\"color: #3366ff;\">\/\/ Handle the request to load a FairPlay license<\/span><\/p>\n<p><span style=\"color: #3366ff;\">guard let requestUrl = loadingRequest.request.url else { return false }<\/span><\/p>\n<p><span style=\"color: #3366ff;\">\/\/ Process the Fairplay License Request<\/span><br \/>\n<span style=\"color: #3366ff;\">handleFairPlayRequest(loadingRequest: loadingRequest, url: requestUrl)<\/span><br \/>\n<span style=\"color: #3366ff;\">return true<\/span><br \/>\n<span style=\"color: #3366ff;\">}<\/span><br \/>\n<span style=\"color: #3366ff;\">}<\/span><\/p><\/blockquote>\n<h3><strong>5. Handle FairPlay Content Key Request (SPC and CKC exchange):<\/strong><\/h3>\n<blockquote><p><span style=\"color: #3366ff;\">func handleFairPlayRequest(loadingRequest: AVAssetResourceLoadingRequest, url: URL) {<\/span><br \/>\n<span style=\"color: #3366ff;\">\u00a0\u00a0\u00a0\u00a0let contentId = url.absoluteString \/\/ Extract Content ID from URL<\/span><br \/>\n<span style=\"color: #3366ff;\">\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0do {<\/span><br \/>\n<span style=\"color: #3366ff;\">\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\/\/ Get the App DRM Certificate<\/span><br \/>\n<span style=\"color: #3366ff;\">\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0let applicationDrmCertificate = fetchApplicationDrmCertificate()<\/span><br \/>\n<span style=\"color: #3366ff;\">\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\/\/ Generate SPC (Server Playback Context)<\/span><br \/>\n<span style=\"color: #3366ff;\">\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0let spcData = try loadingRequest.streamingContentKeyRequestData(<\/span><br \/>\n<span style=\"color: #3366ff;\">\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 for app: applicationDrmCertificate,<\/span><br \/>\n<span style=\"color: #3366ff;\">\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0contentIdentifier: contentId.data(using: .utf8)!,<\/span><br \/>\n<span style=\"color: #3366ff;\">\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0options: nil<\/span><br \/>\n<span style=\"color: #3366ff;\">\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0)<\/span><br \/>\n<span style=\"color: #3366ff;\">\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\/\/ Send SPC (Server playback Context) to your License Server and receive CKC(Content Key Context)<\/span><br \/>\n<span style=\"color: #3366ff;\">\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0requestContentKey(fromServer: spcData, for: contentId) { ckcData in<\/span><br \/>\n<span style=\"color: #3366ff;\">\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0guard let ckc = ckcData else {<\/span><br \/>\n<span style=\"color: #3366ff;\">\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0loadingRequest.finishLoading(with: NSError(domain: &#8220;FairPlay&#8221;, code: -1))<\/span><br \/>\n<span style=\"color: #3366ff;\">\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0return<\/span><br \/>\n<span style=\"color: #3366ff;\">\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0}<\/span><br \/>\n<span style=\"color: #3366ff;\">\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\/\/ Provide CKC to AVFoundation to decrypt the content<\/span><br \/>\n<span style=\"color: #3366ff;\">\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0loadingRequest.dataRequest?.respond(with: ckc)<\/span><br \/>\n<span style=\"color: #3366ff;\">\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0loadingRequest.finishLoading()<\/span><br \/>\n<span style=\"color: #3366ff;\">\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0}<\/span><br \/>\n<span style=\"color: #3366ff;\">\u00a0\u00a0\u00a0\u00a0} catch {<\/span><br \/>\n<span style=\"color: #3366ff;\">\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0loadingRequest.finishLoading(with: error)<\/span><br \/>\n<span style=\"color: #3366ff;\">\u00a0\u00a0\u00a0\u00a0}<\/span><br \/>\n<span style=\"color: #3366ff;\">}<\/span><\/p><\/blockquote>\n<h3><strong>6. Send ServerPlayback Content (SPC) to License Server and Retrieve (Content Key Context) CKC:<\/strong><\/h3>\n<blockquote><p><span style=\"color: #3366ff;\">func requestContentKey(fromServer spcData: Data, for contentId: String, completion: @escaping (Data?) -&gt; Void) {<\/span><\/p>\n<p><span style=\"color: #3366ff;\">\u00a0\u00a0\u00a0\u00a0var request = URLRequest(url: URL(string: &#8220;https:\/\/your-license-server.com\/getckc&#8221;)!)<\/span><\/p>\n<p><span style=\"color: #3366ff;\">\u00a0\u00a0\u00a0\u00a0request.httpMethod = &#8220;POST&#8221;<\/span><\/p>\n<p><span style=\"color: #3366ff;\">\u00a0\u00a0\u00a0\u00a0request.httpBody = spcData<\/span><\/p>\n<p><span style=\"color: #3366ff;\">\u00a0\u00a0\u00a0\u00a0request.setValue(&#8220;application\/octet-stream&#8221;, forHTTPHeaderField: &#8220;Content-Type&#8221;)<\/span><\/p>\n<p><span style=\"color: #3366ff;\">\u00a0\u00a0\u00a0\u00a0\/\/ Optionally add authentication headers<\/span><\/p>\n<p><span style=\"color: #3366ff;\">\u00a0\u00a0\u00a0\u00a0\/\/ request.addValue(&#8220;Bearer \\(authToken)&#8221;, forHTTPHeaderField: &#8220;Authorization&#8221;)<\/span><\/p>\n<p><span style=\"color: #3366ff;\">\u00a0\u00a0\u00a0\u00a0URLSession.shared.dataTask(with: request) { data, response, error in<\/span><\/p>\n<p><span style=\"color: #3366ff;\">\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0if let error = error {<\/span><\/p>\n<p><span style=\"color: #3366ff;\">\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0print(&#8220;CKC request error: \\(error.localizedDescription)&#8221;)<\/span><\/p>\n<p><span style=\"color: #3366ff;\">\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0completion(nil)<\/span><\/p>\n<p><span style=\"color: #3366ff;\">\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0} else {<\/span><\/p>\n<p><span style=\"color: #3366ff;\">\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0completion(data)<\/span><\/p>\n<p><span style=\"color: #3366ff;\">\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0}<\/span><\/p>\n<p><span style=\"color: #3366ff;\">\u00a0\u00a0\u00a0\u00a0}.resume()<\/span><\/p>\n<p><span style=\"color: #3366ff;\">}<\/span><\/p><\/blockquote>\n<h3>7. Fetch FairPlay DRM Certificate:<\/h3>\n<blockquote><p><span style=\"color: #3366ff;\">func fetchApplicationDrmCertificate() -&gt; Data {<\/span><\/p>\n<p><span style=\"color: #3366ff;\">\u00a0 \u00a0 \/\/ Load locally (or fetch securely from your backend if required)<\/span><\/p>\n<p><span style=\"color: #3366ff;\">\u00a0 \u00a0 guard let certURL = Bundle.main.url(forResource: &#8220;fairplay&#8221;, withExtension: &#8220;cer&#8221;),<\/span><\/p>\n<p><span style=\"color: #3366ff;\">\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 let certData = try? Data(contentsOf: certURL) else {<\/span><\/p>\n<p><span style=\"color: #3366ff;\">\u00a0 \u00a0 \u00a0 \u00a0 fatalError(&#8220;Application certificate not found!&#8221;)<\/span><\/p>\n<p><span style=\"color: #3366ff;\">\u00a0 \u00a0 }<\/span><\/p>\n<p><span style=\"color: #3366ff;\">\u00a0 \u00a0 return certData<\/span><\/p>\n<p><span style=\"color: #3366ff;\">}<\/span><\/p><\/blockquote>\n<h3><strong>8. Server Validation Process Recap:<\/strong><\/h3>\n<ul style=\"list-style-type: disc;\">\n<li>The app sends the Server Playback Context (SPC) to the license server.<\/li>\n<li>The license server forwards the SPC (Server Playback Context) to Apple\u2019s KSM (Key Server Module).<\/li>\n<li>The KSM (Key Server Module) verifies and returns a CKC (Content Key Context).<\/li>\n<li>The CKC is passed back to AVFoundation in the app.<\/li>\n<\/ul>\n<h3>9. AVFoundation Decrypts the Content<\/h3>\n<ul style=\"list-style-type: disc;\">\n<li>AVFoundation utilizes the CKC to decrypt the content securely, with hardware enforcing protection..<\/li>\n<\/ul>\n<h3><strong>10. Playback Starts<\/strong><\/h3>\n<ul style=\"list-style-type: disc;\">\n<li>Content is played via AVPlayer as normal.<\/li>\n<\/ul>\n<h2><strong>Conclusion:\u00a0<\/strong><\/h2>\n<p>Implementing FairPlay DRM ensures your premium video content is securely streamed on Apple devices, maintaining both performance and protection. The code snippet above demonstrates the essential steps for setting up FairPlay in your Apple application\u2014from handling SPC\/CKC exchanges to initialising the AVPlayer with a protected stream.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>How to Implement FairPlay DRM with &#8220;AVAssetResourceLoader&#8221; Delegate 1. Prepare DRM-protected HLS Content: The HLS media needs to be encoded and encrypted using FairPlay DRM. This process takes place on the license server, where media files are encrypted and content keys are securely stored. Content keys are generated and securely stored on the license server, [&hellip;]<\/p>\n","protected":false},"author":1411,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"iawp_total_views":172},"categories":[3477],"tags":[5482,5480,6553,6548,3116],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/posts\/66339"}],"collection":[{"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/users\/1411"}],"replies":[{"embeddable":true,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/comments?post=66339"}],"version-history":[{"count":30,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/posts\/66339\/revisions"}],"predecessor-version":[{"id":71712,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/posts\/66339\/revisions\/71712"}],"wp:attachment":[{"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/media?parent=66339"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/categories?post=66339"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/tags?post=66339"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}