Bonjour

Voici mon projet : j’ai quelques films, entre 200Mo et 1Go, que j’aimerais stocker sur un cloud. Comme la plupart des services de cloud utilisent des bots pour scanner les contenus, et que la vie privée est importante, j’ai opté pour la solution suivante : crypter les .mp4 avant de les envoyer sur le cloud, les charger sur le cloud, et depuis l’interface que je développe, lire les .mp4 décryptés.

J’ai fait une tentative du côté de Shaka player, qui avait l’air prometteur, mais je me suis trouvé confronté à des problèmes de compatibilité browser, option écartée donc.

Nouvelle option : php / libsodium.

Comme mes fichiers sont lourds, file_get_content sature la mémoire de php, je dois donc faire cela par chunks.

Voici donc le code pour crypter mon fichier :

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 
function encrypt($msg) 
{
    $key = "blablabla";
    $nonce = "blablable";
    return sodium_crypto_secretbox($msg, $nonce, $key);
}
 
$chunkSize = 1024;
$src = fopen('vid.mp4', 'rb');
$dst = fopen('file', 'wb');
while (!feof($src)) {
    $str = fread($src, $chunkSize); // on lit 1024 octets    
    $str = encrypt($str); // on les crypte : +16 octets soit 1040 octets
    fwrite($dst, $str); // on les écrit dans file
}
fclose($src);
fclose($dst);
Puis je lis le fichier en stream :

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
 
 
from url : https://github.com/micilini/video-stream/blob/master/src/Providers/VideoStreamProvider.php
 
class VideoStream // fol
{
    private $path = "";
    private $stream = "";
    private $start  = -1;
    private $end    = -1;
    private $size   = 0;
 
    function __construct($filePath) 
    {
        $this->path = $filePath;
    }
 
    private function decrypt($msg) {
 
        $key = "blablabla";
        $nonce = "blablabla";
        return sodium_crypto_secretbox_open($msg, $nonce, $key); // decr
    }
 
    /**
     * Open stream
     */
    private function open()
    {
        if (!($this->stream = fopen($this->path, 'rb'))) {
            die('Could not open stream for reading');
        }
    }
 
    /**
     * Set proper header to serve the video content
     */
    private function setHeader()
    {
        ob_get_clean();
        header("Content-Type: video/mp4");
        header("Cache-Control: max-age=2592000, public");
        header("Expires: ".gmdate('D, d M Y H:i:s', time()+2592000) . ' GMT');
        header("Last-Modified: ".gmdate('D, d M Y H:i:s', @filemtime($this->path)) . ' GMT' );
        $this->start = 0;
        $this->size  = filesize($this->path);
        $this->end   = $this->size - 1;
        header("Accept-Ranges: 0-".$this->end);
 
        if (isset($_SERVER['HTTP_RANGE'])) {
 
            $c_start = $this->start;
            $c_end = $this->end;
 
            list(, $range) = explode('=', $_SERVER['HTTP_RANGE'], 2);
            if (strpos($range, ',') !== false) {
                header('HTTPS/1.1 416 Requested Range Not Satisfiable');
                header("Content-Range: bytes $this->start-$this->end/$this->size");
                exit;
            }
            if ($range == '-') {
                $c_start = $this->size - substr($range, 1);
            }else{
                $range = explode('-', $range);
                $c_start = $range[0];
 
                $c_end = (isset($range[1]) && is_numeric($range[1])) ? $range[1] : $c_end;
            }
            $c_end = ($c_end > $this->end) ? $this->end : $c_end;
            if ($c_start > $c_end || $c_start > $this->size - 1 || $c_end >= $this->size) {
                header('HTTPS/1.1 416 Requested Range Not Satisfiable');
                header("Content-Range: bytes $this->start-$this->end/$this->size");
                exit;
            }
            $this->start = $c_start;
            $this->end = $c_end;
            $length = $this->end - $this->start + 1;
            fseek($this->stream, $this->start);
            header('HTTPS/1.1 206 Partial Content');
            header("Content-Length: ".$length);
            header("Content-Range: bytes $this->start-$this->end/".$this->size);
            $this->log("Content-Range: bytes $this->start-$this->end/".$this->size);
        }
        else
        {
            header("Content-Length: ".$this->size);
        }  
    }
 
    /**
     * close curretly opened stream
     */
    private function end()
    {
        fclose($this->stream);
        exit;
    }
 
    /**
     * perform the streaming of calculated range
     */
    private function stream()
    {
        $i = $this->start;
 
        $i = 0;
 
        set_time_limit(0);
 
        while(!feof($this->stream) && $i <= $this->end) {
 
            // je lis 1040 octets...
            $bytesToRead = 1040;
 
            if(($i+$bytesToRead) > $this->end) 
                $bytesToRead = $this->end - $i + 1;
 
            $data = fread($this->stream, $bytesToRead);
            // ... pour en écrire 1024 en clear 
            $data = $this->decrypt($data);
 
            echo $data;
            flush();
            $i += $bytesToRead;
        }
    }
 
    /**
     * Start streaming video content
     */
    function start()
    {
        $this->open();
        $this->setHeader();
        $this->stream();
        $this->end();
    }
}
 
$filePath = "file";
$stream = new VideoStream($filePath); 
$stream->start();
Problème… Je peux parfaitement lire le stream sur Firefox mais pas sur Brave/Chrome ou Safari A noter que si je stream le .mp4 directement (la version clear, donc pas de décryptage au milieu), aucun problème sur ces deux autres navigateurs….

Dans les deux cas, je reçois une erreur 500, internal serveur error.

A noter également que j’avais fait des test en convertissant en base64 avant de crypter (oui, c’est mal), et donc en décodant le base64 dans le streamer. Si je supprime tout le bloc du "if (isset($_SERVER['HTTP_RANGE'])) {", je ne peux pas avancer dans le player mais au moins, ça lit…

J’en déduis que mes problèmes viennent des réponses HTTP mais ici j’arrive en limite de connaissance…. Je me souviens juste avoir fait des tests pour lire la valeur de $_SERVER['HTTP_RANGE’] depuis php, et avait été surpris de constater une valeur du genre "byte=546566-", qui si je comprends bien, indique que le stream ne débute pas au tout début de la vidéo. Et ce alors que je ne cliquais nulle part, c’était juste au chargement de la page.

Voilà… Pas évident, et c’est aussi peut être encore des problèmes de browser, mais si ça fonctionne en clear ça devrait aussi fonctionner en crypté non ? Peut être que le fait d’avoir crypté modifie les size, rendant tout le bloc "if (isset($_SERVER['HTTP_RANGE'])) {" faux ?

Merci pour vos réponses