6 min read
Content Protection and DRM with Azure Media Services
Content protection ensures your premium video content is secured against unauthorized access and piracy. Azure Media Services supports multiple DRM systems including PlayReady, Widevine, and FairPlay, as well as AES-128 encryption.
DRM Options
| DRM System | Platforms | License Server |
|---|---|---|
| PlayReady | Windows, Xbox, Smart TVs | Microsoft |
| Widevine | Chrome, Android, Chromecast | |
| FairPlay | Safari, iOS, Apple TV | Apple |
| AES-128 | All (clear key) | Azure |
Creating Content Key Policies
from azure.mgmt.media import AzureMediaServices
from azure.mgmt.media.models import (
ContentKeyPolicy,
ContentKeyPolicyOption,
ContentKeyPolicyTokenRestriction,
ContentKeyPolicySymmetricTokenKey,
ContentKeyPolicyRestrictionTokenType,
ContentKeyPolicyPlayReadyConfiguration,
ContentKeyPolicyPlayReadyLicense,
ContentKeyPolicyPlayReadyContentEncryptionKeyFromHeader,
ContentKeyPolicyPlayReadyContentType,
ContentKeyPolicyWidevineConfiguration,
ContentKeyPolicyFairPlayConfiguration,
ContentKeyPolicyClearKeyConfiguration
)
from azure.identity import DefaultAzureCredential
import base64
import secrets
class ContentProtectionManager:
def __init__(self, subscription_id: str, resource_group: str, account_name: str):
self.credential = DefaultAzureCredential()
self.client = AzureMediaServices(self.credential, subscription_id)
self.resource_group = resource_group
self.account_name = account_name
def create_multi_drm_policy(self, policy_name: str,
token_key: str,
issuer: str,
audience: str) -> ContentKeyPolicy:
"""Create a content key policy with multiple DRM options."""
# Token verification key
primary_key = ContentKeyPolicySymmetricTokenKey(
key_value=base64.b64decode(token_key)
)
# Token restriction
token_restriction = ContentKeyPolicyTokenRestriction(
issuer=issuer,
audience=audience,
primary_verification_key=primary_key,
restriction_token_type=ContentKeyPolicyRestrictionTokenType.JWT
)
options = []
# PlayReady option
playready_config = ContentKeyPolicyPlayReadyConfiguration(
licenses=[
ContentKeyPolicyPlayReadyLicense(
allow_test_devices=True,
content_key_location=ContentKeyPolicyPlayReadyContentEncryptionKeyFromHeader(),
content_type=ContentKeyPolicyPlayReadyContentType.UNSPECIFIED,
license_type="NonPersistent"
)
]
)
options.append(ContentKeyPolicyOption(
name="PlayReady",
configuration=playready_config,
restriction=token_restriction
))
# Widevine option
widevine_config = ContentKeyPolicyWidevineConfiguration(
widevine_template="""{
"allowed_track_types": "SD_HD",
"content_key_specs": [{
"track_type": "SD",
"security_level": 1,
"required_output_protection": {"hdcp": "HDCP_NONE"}
}],
"policy_overrides": {
"can_play": true,
"can_persist": false,
"can_renew": false
}
}"""
)
options.append(ContentKeyPolicyOption(
name="Widevine",
configuration=widevine_config,
restriction=token_restriction
))
# FairPlay option (requires Apple certificate)
# fairplay_config = ContentKeyPolicyFairPlayConfiguration(
# ask=fairplay_ask,
# fairplay_pfx=fairplay_pfx,
# fairplay_pfx_password=fairplay_password,
# rental_and_lease_key_type="PersistentUnlimited",
# rental_duration=0
# )
# Clear key option (AES-128)
clear_key_config = ContentKeyPolicyClearKeyConfiguration()
options.append(ContentKeyPolicyOption(
name="ClearKey",
configuration=clear_key_config,
restriction=token_restriction
))
policy = ContentKeyPolicy(options=options)
return self.client.content_key_policies.create_or_update(
self.resource_group,
self.account_name,
policy_name,
policy
)
Creating Streaming Policies
from azure.mgmt.media.models import (
StreamingPolicy,
CommonEncryptionCenc,
CommonEncryptionCbcs,
EnabledProtocols,
StreamingPolicyContentKeys,
DefaultKey,
TrackSelection,
TrackPropertyCondition,
StreamingPolicyContentKey
)
def create_drm_streaming_policy(manager, policy_name: str,
content_key_policy_name: str) -> StreamingPolicy:
"""Create a streaming policy with DRM protection."""
# CENC (Common Encryption) for PlayReady and Widevine
cenc = CommonEncryptionCenc(
enabled_protocols=EnabledProtocols(
dash=True,
hls=False, # HLS uses CBCS
smooth_streaming=True,
download=False
),
content_keys=StreamingPolicyContentKeys(
default_key=DefaultKey(
label="cenc_default",
policy_name=content_key_policy_name
)
),
drm=CommonEncryptionCencDrm(
play_ready=StreamingPolicyPlayReadyConfiguration(
custom_license_acquisition_url_template=None
),
widevine=StreamingPolicyWidevineConfiguration(
custom_license_acquisition_url_template=None
)
)
)
# CBCS (Common Encryption CBCS) for FairPlay
cbcs = CommonEncryptionCbcs(
enabled_protocols=EnabledProtocols(
dash=False,
hls=True,
smooth_streaming=False,
download=False
),
content_keys=StreamingPolicyContentKeys(
default_key=DefaultKey(
label="cbcs_default",
policy_name=content_key_policy_name
)
)
)
policy = StreamingPolicy(
common_encryption_cenc=cenc,
common_encryption_cbcs=cbcs
)
return manager.client.streaming_policies.create(
manager.resource_group,
manager.account_name,
policy_name,
policy
)
Generating JWT Tokens
import jwt
from datetime import datetime, timedelta
import uuid
class TokenService:
def __init__(self, token_key: str, issuer: str, audience: str):
self.token_key = token_key
self.issuer = issuer
self.audience = audience
def generate_token(self, content_key_id: str = None,
expiry_hours: int = 24) -> str:
"""Generate a JWT token for content access."""
now = datetime.utcnow()
expiry = now + timedelta(hours=expiry_hours)
claims = {
"iss": self.issuer,
"aud": self.audience,
"iat": now,
"exp": expiry,
"nbf": now,
"jti": str(uuid.uuid4())
}
# Add content key identifier if specified
if content_key_id:
claims["urn:microsoft:azure:mediaservices:contentkeyidentifier"] = content_key_id
token = jwt.encode(
claims,
base64.b64decode(self.token_key),
algorithm="HS256"
)
return token
def generate_persistent_token(self, user_id: str,
content_id: str) -> str:
"""Generate a token for offline playback."""
claims = {
"iss": self.issuer,
"aud": self.audience,
"iat": datetime.utcnow(),
"exp": datetime.utcnow() + timedelta(days=30),
"sub": user_id,
"content_id": content_id,
"urn:microsoft:azure:mediaservices:maxuses": "unlimited"
}
return jwt.encode(
claims,
base64.b64decode(self.token_key),
algorithm="HS256"
)
# Usage
token_service = TokenService(
token_key="your-base64-encoded-key",
issuer="https://yourapp.com",
audience="urn:youraudience"
)
# Generate playback token
token = token_service.generate_token()
print(f"Bearer {token}")
Creating Protected Streaming Locators
from azure.mgmt.media.models import StreamingLocator
def create_protected_locator(manager, asset_name: str,
locator_name: str,
streaming_policy_name: str,
content_key_policy_name: str) -> dict:
"""Create a streaming locator with DRM protection."""
locator = StreamingLocator(
asset_name=asset_name,
streaming_policy_name=streaming_policy_name,
default_content_key_policy_name=content_key_policy_name
)
created_locator = manager.client.streaming_locators.create(
manager.resource_group,
manager.account_name,
locator_name,
locator
)
# Get streaming paths
paths = manager.client.streaming_locators.list_paths(
manager.resource_group,
manager.account_name,
locator_name
)
# Get content keys
keys = manager.client.streaming_locators.list_content_keys(
manager.resource_group,
manager.account_name,
locator_name
)
return {
"locator_id": created_locator.streaming_locator_id,
"streaming_paths": paths.streaming_paths,
"content_keys": [
{"id": k.id, "type": k.type, "label": k.label_reference_in_streaming_policy}
for k in keys.content_keys
]
}
Building a License Delivery Service
from flask import Flask, request, jsonify
import jwt
app = Flask(__name__)
# Configuration
TOKEN_KEY = "your-base64-encoded-key"
ISSUER = "https://yourapp.com"
AUDIENCE = "urn:youraudience"
def verify_token(token: str) -> dict:
"""Verify JWT token and return claims."""
try:
claims = jwt.decode(
token,
base64.b64decode(TOKEN_KEY),
algorithms=["HS256"],
audience=AUDIENCE,
issuer=ISSUER
)
return claims
except jwt.InvalidTokenError as e:
raise ValueError(f"Invalid token: {e}")
@app.route('/api/token', methods=['POST'])
def get_playback_token():
"""Issue playback token for authenticated users."""
# Verify user authentication
auth_header = request.headers.get('Authorization')
if not auth_header:
return jsonify({"error": "Unauthorized"}), 401
# Generate playback token
user_id = request.json.get('user_id')
content_id = request.json.get('content_id')
# Check user entitlement (simplified)
if not check_user_entitlement(user_id, content_id):
return jsonify({"error": "Not entitled"}), 403
token_service = TokenService(TOKEN_KEY, ISSUER, AUDIENCE)
token = token_service.generate_token()
return jsonify({
"token": token,
"expires_in": 86400 # 24 hours
})
def check_user_entitlement(user_id: str, content_id: str) -> bool:
"""Check if user is entitled to access content."""
# Implement your entitlement logic
return True
@app.route('/api/license/playready', methods=['POST'])
def playready_license():
"""Custom PlayReady license acquisition (if using custom server)."""
# Azure Media Services handles this by default
pass
if __name__ == '__main__':
app.run(port=5000)
Video Player Integration
// Azure Media Player with DRM
const playerOptions = {
autoplay: true,
controls: true,
width: "640",
height: "400",
logo: { enabled: false }
};
const player = amp("azuremediaplayer", playerOptions);
// Get token from your backend
async function getPlaybackToken(contentId) {
const response = await fetch('/api/token', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${userToken}`
},
body: JSON.stringify({ content_id: contentId })
});
return response.json();
}
// Set protected source
async function playProtectedContent(manifestUrl, contentId) {
const { token } = await getPlaybackToken(contentId);
player.src([{
src: manifestUrl,
type: "application/vnd.ms-sstr+xml",
protectionInfo: [
{
type: "PlayReady",
authenticationToken: `Bearer=${token}`
},
{
type: "Widevine",
authenticationToken: `Bearer=${token}`
},
{
type: "FairPlay",
authenticationToken: `Bearer=${token}`,
certificateUrl: "https://yourapp.com/fairplay.cer"
}
]
}]);
}
playProtectedContent(
"https://streaming.mediaservices.windows.net/.../manifest",
"video-123"
);
Best Practices
- Multi-DRM: Support PlayReady, Widevine, and FairPlay for maximum reach
- Token Expiry: Use short-lived tokens (hours, not days)
- HTTPS Only: Always use HTTPS for license acquisition
- Secure Keys: Store token signing keys securely
- Monitor Usage: Track license requests for abuse detection
- Offline Support: Consider persistent licenses for offline viewing
Content protection with DRM ensures your premium content remains secure while providing a seamless viewing experience across all platforms.