Implementation of DRM Protection for Digital Content
DRM (Digital Rights Management) restricts unauthorized copying and distribution of digital content: video courses, e-books, audio, software. Protection level and implementation depend on content type and product price category.
Protection Levels
Basic (Software DRM):
- Tokenized URLs with limited expiration
- User account binding
- Concurrent session limits
Medium:
- Watermarking (invisible watermarks with user ID)
- File encryption with decryption only via authorized player
Professional (HLS Encryption / Widevine / FairPlay):
- Video stream encryption at HLS/DASH level
- DRM licenses via Widevine (Chrome, Android), FairPlay (Safari/iOS), PlayReady (Windows/Edge)
Protected URLs for Files
class SecureFileService
{
public function generateSecureUrl(int $fileId, int $userId): string
{
$token = $this->generateToken($fileId, $userId);
$expiresAt = now()->addMinutes(30)->timestamp;
return URL::temporarySignedRoute(
'files.download',
now()->addMinutes(30),
['file' => $fileId, 'user' => $userId, 'token' => $token]
);
}
private function generateToken(int $fileId, int $userId): string
{
return hash_hmac('sha256', "{$fileId}:{$userId}", config('app.key'));
}
}
// Download handler
Route::get('/files/download/{file}', function (Request $request, ProtectedFile $file) {
if (!$request->hasValidSignature()) abort(403);
// Check user access rights to file
$purchase = Purchase::where([
'user_id' => auth()->id(),
'file_id' => $file->id,
])->firstOrFail();
// Limit: no more than 5 downloads
if ($purchase->download_count >= 5) abort(429, 'Download limit exceeded');
$purchase->increment('download_count');
return Storage::disk('private')->download($file->path, $file->original_name);
})->name('files.download');
PDF Watermarking
class PdfWatermarker
{
public function addWatermark(string $pdfPath, int $userId, string $userName): string
{
$pdf = new \setasign\Fpdi\Fpdi();
$pageCount = $pdf->setSourceFile($pdfPath);
for ($i = 1; $i <= $pageCount; $i++) {
$pdf->AddPage();
$templateId = $pdf->importPage($i);
$pdf->useTemplate($templateId, 0, 0, null, null, true);
// Add invisible text (white color, transparent)
$pdf->SetFont('Arial', '', 8);
$pdf->SetTextColor(200, 200, 200);
$pdf->SetXY(10, 5);
$pdf->Write(0, "ID: {$userId} | {$userName}");
}
$outputPath = tempnam(sys_get_temp_dir(), 'wm_');
$pdf->Output($outputPath, 'F');
return $outputPath;
}
}
HLS Encryption for Video
# FFmpeg: convert video to encrypted HLS
ffmpeg -i input.mp4 \
-codec: copy \
-hls_time 10 \
-hls_key_info_file enc.keyinfo \
-hls_playlist_type vod \
-hls_segment_filename 'segments/seg%03d.ts' \
playlist.m3u8
# enc.keyinfo contains:
https://example.com/keys/{key_id} # URL to get key
/tmp/enc.key # local path to key
Route::get('/keys/{keyId}', function (string $keyId) {
// Check user has video access
if (!auth()->user()->hasAccessToVideo($keyId)) abort(403);
// Return encryption key
return response(
file_get_contents(storage_path("app/keys/{$keyId}")),
200,
['Content-Type' => 'application/octet-stream']
);
})->middleware('auth');
Concurrent Session Control
class ConcurrentSessionGuard
{
public function checkLimit(int $userId, int $contentId, string $sessionId): bool
{
$key = "active_sessions:{$userId}:{$contentId}";
$sessions = Redis::smembers($key);
if (count($sessions) >= 2 && !in_array($sessionId, $sessions)) {
return false; // Too many concurrent views
}
Redis::sadd($key, $sessionId);
Redis::expire($key, 300); // Session active 5 minutes without heartbeat
return true;
}
}
Timeline
Basic DRM (tokenized URLs, watermarking): 5–7 days. HLS encryption + Widevine: 14–20 days.







