{
    "items": [
        {
            "type": [
                "h-entry"
            ],
            "properties": {
                "name": [
                    "How DCI Movie Delivery and Encryption works"
                ],
                "category": [
                    "digital cinema",
                    "projects",
                    "reverse engineering",
                    "security"
                ],
                "summary": [
                    "The Cinema Industry is using its own standards for creating and distributing movies in a secure way. The DCI (Digital Cinema Initiatives) specification defines everything from file formats and encryption to the projection systems itself."
                ],
                "url": [
                    "https://serverless.industries/2024/05/31/digital-cinema.en.html"
                ],
                "published": [
                    "2024-05-31"
                ],
                "content": [
                    {
                        "html": "<p>The Cinema Industry is using its own standards for creating and distributing movies in a secure way. The DCI (Digital Cinema Initiatives) specification defines everything from file formats and encryption to the projection systems itself.</p>\n\n<p>The specification itself is <a href=\"https://www.dcimovies.com/\" target=\"_blank\">publicly available</a> but relies on various IEEE (Institute of Electrical and Electronics Engineers) and SMPTE (Society of Motion Picture and Television Engineers) standards, which have to be purchased.</p>\n\n<h2 id=\"scope\">Scope</h2>\n\n<p>This document will show a rough overview of the DCI workflow and describe in detail how the encryption of a DCI movie works. It will <strong>not</strong> show how to break any encryption and no encryption has been broken while writing this document. <strong>From our perspective, the DCI standard is safe</strong>.</p>\n\n<p>The author of this document has been operating a cinema since 2021, without any insight to the distribution or production side of the industry. Some information may be incomplete.</p>\n\n<h2 id=\"how-its-started\">How it\u2019s started</h2>\n\n<p>End of 2023 the movie <a href=\"https://www.imdb.com/title/tt6166392/\" target=\"_blank\">WONKA</a> was released. Some cinemas reported, that they are unable to start this movie on their projector.</p>\n\n<p>The reason was, that a certificate used by the distributor was expired. This certificate was used to sign the DCP files.</p>\n\n<p>The distributor published new files, starting the movie worked again.</p>\n\n<p>Out of curiosity the author started to check how the validation process on the projector is working.</p>\n\n<h2 id=\"glossar\">Glossar</h2>\n\n<ul>\n  <li>\n<strong>DCP</strong>: Digital Cinema Package - A folder which contains all components of a movie. Metadata, Subtitles, Audio and Picture in seperate files</li>\n  <li>\n<strong>CPL</strong>: Composition Playlist - A DCP can contain multiple audio and video streams which will combined in a CPL</li>\n  <li>\n<strong>KDM</strong>: Key Delivery Message - A XML file which contains the cryptographical information to allow a movie playback on a specific DCI certified projection system</li>\n  <li>\n<strong>DKDM</strong>: Distribution Key Delivery Message - Similar to a KDM, but for a remastering or distribution system, not for a projection system</li>\n</ul>\n\n<h2 id=\"distribution-process\">Distribution Process</h2>\n\n<pre><code class=\"language-mermaid\">graph TD\n    classDef red fill:#a91900\n    classDef green fill:#126500\n    classDef purple fill:#650072\n    legendprod[Producer]:::red\n    legenddist[Distributor]:::green\n    legendcine[Cinema]:::purple\n    legendvendors[Projector Manufacturer]\n</code></pre>\n\n<pre><code class=\"language-mermaid\">graph LR\n    classDef red fill:#a91900\n    classDef green fill:#126500\n    classDef purple fill:#650072\n\n    dcpcreated([\"DCP\\ncreated\"]):::red --\"AES Key\"--&gt; dkdmcreated\n    dkdmcreated([\"DKDM\\ncreated\"]):::red --\"encrypted\\nAES Key\"--&gt; dkdmp\n    \n    dkdmp[DKDM]:::green --&gt; verify([\"Projector\\nverified\"]):::green\n    dcpcreated --\"to Distributor\"--&gt; dcp([\"DCP by\\nDownload or\\nHarddrive\"]):::green\n    distcert[\"Distributor\\nCertificate\"]:::green --\"to Producer\"--&gt; dkdmcreated\n    \n    verify --\"to Cinema\"--&gt; kdm[KDM]:::purple\n    dcp --\"to Cinema\"--&gt; dcp2[\"DCP\"]:::purple\n    cinecert[\"Projector\\nCertificate\"]:::purple --\"to Distributor\"--&gt; verify\n    trusted[\"Trusted Device List\"] --&gt; verify\n    dcp2 --&gt; projector[Projector]:::purple\n    kdm --&gt; projector\n</code></pre>\n\n<h2 id=\"projection-systems\">Projection Systems</h2>\n\n<p>Most of the Projection Systems consists of Server, Audio Processor and Projector.</p>\n\n<pre><code class=\"language-mermaid\">graph LR\n  server[Server] --&gt; mediablock[Media Block]\n  hdmi[HDMI Source] --&gt; splitter[Audio Splitter]\n  splitter --\"Video\"--&gt; projector\n  splitter --\"Audio\"--&gt; sound\n  subgraph Projector\n   mediablock --\"Video\"--&gt; projector[Projector]\n  end\n  \n  projector --&gt; screen[Theatre Screen]\n  mediablock --\"Audio\"--&gt; sound[Audio Processor]\n  sound --&gt; speakers[Speakers]\n</code></pre>\n\n<p>The Server stores the DCPs and KDMs, manages Playlists and controls the Projector and Audio Processor hardware.</p>\n\n<p>Many Cinemas also connect their Theatre Automation (light, curtains, etc.) to the projector. By setting time-based commands into the playlists, lights and screen curtain can be controlled automatically. The Projector Server provides 12V/24V relays for this.</p>\n\n<p>These time-based commands are also used to set the correct Aspect Ratio and Volume on the different DCPs in a Playlist.</p>\n\n<p>The Server can be controlled remotely by a PC or in larger cinemas by a Theatre Management System.</p>\n\n<p>The DCPs are imported from USB/CRU hard drives or by internet download to the server. They are stored encrypted at all times.</p>\n\n<p>The projector includes a so called \u201cMedia Block\u201d which handles DRM and decryption. It receives the DCP data and the KDM to decrypt each frame in real time.</p>\n\n<p>On DCP playback the Projector will send the decrypted PCM audio to the Audio Processor which will then send each processed Audio Channel to the respective Speakers.</p>\n\n<p><img src=\"https://serverless.industries/assets/digital-cinema-disks.jpg\" alt=\"DCP Disks\" class=\"img-fluid\"></p>\n\n<h2 id=\"dcp-format\">DCP Format</h2>\n\n<p>A DCP is a folder which contains XML metadata files and multiple MXF files for the actual movie.</p>\n\n<h3 id=\"folder-naming-pattern\">Folder Naming Pattern</h3>\n\n<pre><code class=\"language-txt\">AwesomeMovie_FTR-2_S_DE-XX_DE-16_51_4K_20240119_SMPTE_OV\n</code></pre>\n\n<ul>\n  <li>\n<code class=\"language-plaintext highlighter-rouge\">AwesomeMovie</code>: A short version of the movie title</li>\n  <li>\n<code class=\"language-plaintext highlighter-rouge\">FTR</code>: Media Type, in this case \u201cFeature\u201d</li>\n  <li>\n<code class=\"language-plaintext highlighter-rouge\">2</code>: Version Number</li>\n  <li>\n<code class=\"language-plaintext highlighter-rouge\">S</code>: Aspect ratio, in this case 2.35:1 aka \u201cSope\u201d</li>\n  <li>\n<code class=\"language-plaintext highlighter-rouge\">DE</code>: Audio Language</li>\n  <li>\n<code class=\"language-plaintext highlighter-rouge\">XX</code>: Subtitle Language, in this case is no subtitle available</li>\n  <li>\n<code class=\"language-plaintext highlighter-rouge\">DE</code>: Territory</li>\n  <li>\n<code class=\"language-plaintext highlighter-rouge\">16</code>: Age Rating</li>\n  <li>\n<code class=\"language-plaintext highlighter-rouge\">51</code>: Audio channels, in this case 5.1 surround sound</li>\n  <li>\n<code class=\"language-plaintext highlighter-rouge\">4K</code>: Movie resolution, in this case 4096x1716 pixels</li>\n  <li>\n<code class=\"language-plaintext highlighter-rouge\">20240119</code>: Mastering timestamp</li>\n  <li>\n<code class=\"language-plaintext highlighter-rouge\">SMPTE</code>: DCP standard, there is also <code class=\"language-plaintext highlighter-rouge\">Interop</code>\n</li>\n  <li>\n<code class=\"language-plaintext highlighter-rouge\">OV</code>: Package type, original version or <code class=\"language-plaintext highlighter-rouge\">VF</code> for version file</li>\n</ul>\n\n<p>Credits: <a href=\"http://static.kinofreund.com/dcnt/\" target=\"_blank\">http://static.kinofreund.com/dcnt/</a></p>\n\n<h3 id=\"mastering-process\">Mastering Process</h3>\n\n<p>During the Mastering Process a static AES 128 bit key is generated and the original medium is converted into MXF files. One for picture and one for audio.</p>\n\n<p>For mastering the tool <a href=\"https://dcpomatic.com/\" target=\"_blank\">DCP-o-matic</a> can be used. There are also some commercial products.</p>\n\n<pre><code class=\"language-mermaid\">graph LR\n    movie[\"Source\"] --&gt; mastering{{\"Movie Mastering\"}}\n    mastering --&gt; dcpkey[\"Static&lt;br&gt;AES-128 Key\"]\n    mastering --&gt; dcp[\"Digital Cinema Package (DCP)\"]\n    dcp --&gt; dcppics[\"Encrypted&lt;br&gt;Picture Data\"]\n    dcp --&gt; dcpsound[\"Encrypted&lt;br&gt;Audio Data\"]\n    dcp --&gt; dcpsub[\"Encrypted&lt;br&gt;Subtitle Data\"]\n</code></pre>\n\n<p>The video stream is encoded as one single JPEG2000 picture per frame. Each frame is encrypted with the same static AES key.</p>\n\n<p>The audio stream is (most likely) chunked into one BWF (Broadcast Wave Format) stream per frame and also encrypted separately. (The author haven\u2019t found any information about this yet.)</p>\n\n<p>A DCP can have a size of 200 GB or more. Some newer releases can hit the Terabyte, if multiple versions (Languages, Subtitles, 2D/3D) are shipped on the same harddrive.</p>\n\n<p>The subtitles are provided in an XML file or are burned directly into the picture frames. If it is provided as an XML file, the projector will render the subtitles using a TTF font file.</p>\n\n<div class=\"language-xml highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"cp\">&lt;?xml version=\"1.0\" encoding=\"UTF-8\"?&gt;</span>\n<span class=\"nt\">&lt;DCSubtitle</span> <span class=\"na\">Version=</span><span class=\"s\">\"1.0\"</span><span class=\"nt\">&gt;</span>\n    <span class=\"nt\">&lt;SubtitleID&gt;</span>xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx<span class=\"nt\">&lt;/SubtitleID&gt;</span>\n    <span class=\"nt\">&lt;MovieTitle&gt;</span>MovieTitle<span class=\"nt\">&lt;/MovieTitle&gt;</span>\n    <span class=\"nt\">&lt;ReelNumber&gt;</span>1<span class=\"nt\">&lt;/ReelNumber&gt;</span>\n    <span class=\"nt\">&lt;Language&gt;</span>de<span class=\"nt\">&lt;/Language&gt;</span>\n    <span class=\"nt\">&lt;LoadFont</span> <span class=\"na\">Id=</span><span class=\"s\">\"Font1\"</span> <span class=\"na\">URI=</span><span class=\"s\">\"Arial.ttf\"</span> <span class=\"nt\">/&gt;</span>\n    <span class=\"nt\">&lt;Font</span> <span class=\"na\">Id=</span><span class=\"s\">\"Font1\"</span> <span class=\"na\">Color=</span><span class=\"s\">\"ffffffff\"</span> <span class=\"na\">Effect=</span><span class=\"s\">\"border\"</span> <span class=\"na\">EffectColor=</span><span class=\"s\">\"ff000000\"</span> <span class=\"na\">Italic=</span><span class=\"s\">\"no\"</span> <span class=\"na\">Script=</span><span class=\"s\">\"normal\"</span> <span class=\"na\">Size=</span><span class=\"s\">\"42\"</span> <span class=\"na\">Underlined=</span><span class=\"s\">\"no\"</span> <span class=\"na\">Weight=</span><span class=\"s\">\"normal\"</span> <span class=\"na\">AspectAdjust=</span><span class=\"s\">\"1\"</span> <span class=\"na\">Spacing=</span><span class=\"s\">\"0em\"</span><span class=\"nt\">&gt;</span>\n        <span class=\"nt\">&lt;Subtitle</span> <span class=\"na\">SpotNumber=</span><span class=\"s\">\"1\"</span> <span class=\"na\">TimeIn=</span><span class=\"s\">\"00:07:45:094\"</span> <span class=\"na\">TimeOut=</span><span class=\"s\">\"00:07:48:000\"</span> <span class=\"na\">FadeUpTime=</span><span class=\"s\">\"000\"</span> <span class=\"na\">FadeDownTime=</span><span class=\"s\">\"000\"</span><span class=\"nt\">&gt;</span>\n            <span class=\"nt\">&lt;Text</span> <span class=\"na\">HPosition=</span><span class=\"s\">\"0\"</span> <span class=\"na\">VAlign=</span><span class=\"s\">\"bottom\"</span> <span class=\"na\">VPosition=</span><span class=\"s\">\"7\"</span><span class=\"nt\">&gt;</span>W\u00dcSTE VON NEVADA - HEUTE<span class=\"nt\">&lt;/Text&gt;</span>\n        <span class=\"nt\">&lt;/Subtitle&gt;</span>\n    <span class=\"nt\">&lt;/Font&gt;</span>\n<span class=\"nt\">&lt;/DCSubtitle&gt;</span>\n</code></pre></div></div>\n\n<h3 id=\"supplemental-dcp-vf-version-file\">Supplemental DCP (VF, Version File)</h3>\n\n<p>A supplemental DCP allows to reuse the Original DCP (OV, Original File) picture and replace audio or subtitle tracks. This makes it possible to support different languages without the need of shipping hundreds of gigabytes of duplicated picture data to the cinemas.</p>\n\n<pre><code class=\"language-mermaid\">graph TD\n    f[PaktDerWoelfe_FTR_S-235_FR-XX_DE_51_4K_SC_20230926_EGB_SMPTE_OV]\n    f --&gt; a[PaktDerWoelBoC_FTR_S-235_DE-DE_DE_51_4K_SC_20231115_EGB_SMPTE_VF]\n    f --&gt; b[PaktDerWoelBoC_FTR_S-235_FR-DE_DE_51_4K_SC_20231115_EGB_SMPTE_VF]\n</code></pre>\n\n<p>Example: One version file for German with German subtitles and one for French with German subtitles.</p>\n\n<p>It is even possible to provide differnt cuts of a movie for different regions.</p>\n\n<h3 id=\"software\">Software</h3>\n\n<p>Two Open Source C++ implementations, both in active development, one from the industry!</p>\n\n<p>cth103/libdcp, a C++ library written by the makers of DCP-o-matic:</p>\n\n<ul>\n  <li><a href=\"https://git.carlh.net/gitweb/?p=dcpomatic.git;a=shortlog;h=refs/heads/main\" target=\"_blank\">https://git.carlh.net/gitweb/?p=dcpomatic.git;a=shortlog</a></li>\n  <li>\n<a href=\"https://dcpomatic.com/\" target=\"_blank\">https://dcpomatic.com/</a> (<a href=\"https://git.carlh.net/gitweb/?p=libdcp.git;a=summary\" target=\"_blank\">source code</a>)</li>\n</ul>\n\n<p>asdcplib, a C++ library written by companies active in the cinema industry:</p>\n\n<blockquote>\n  <p>The asdcplib project was originally exchanged by FTP. The project was on SourceForge between 2005 and 2008, when it moved to a release-only distribution via CineCert. As of late February 2019, its new home is on github.</p>\n\n  <p>\u2013 <a href=\"https://github.com/cinecert/asdcplib\" target=\"_blank\">https://github.com/cinecert/asdcplib</a></p>\n</blockquote>\n\n<p>Almost all software, commercial or open source is using one of these libraries.</p>\n\n<h2 id=\"distribution\">Distribution</h2>\n\n<p>The symmetric encryption key for the DCP has to be protected in some way. For this, the producer will create a DKDM XML file, containing the AES Key, which has been encrypted with the Certificate Public Key of the distributor.</p>\n\n<p>The distributor can then use the DKDM to create KDM XML files for cinemas. This means decrypting the DCP AES Key and encrypting it again with the Certificate Public Key of the target projection system.</p>\n\n<h2 id=\"certificate-chains\">Certificate Chains</h2>\n\n<p>Both projectors and distributors use SSL certificate chains to sign and encrypt/decrypt data.</p>\n\n<p>A chain always consists of a Root Certificate Authority (CA), Intermediate CA and a Leaf Certificate.</p>\n\n<pre><code class=\"language-mermaid\">graph TD\n    a[\"dnQualifier=ZwVjULQy61,CN=.smpte-430-2.root,OU=movies.serverless.industries,O=serverless.industries\"]\n    a --&gt; b[\"dnQualifier=/JLF0JtscFuJahVNXfYu1w1u7SI=,CN=.smpte-430-2.intermediate,OU=movies.serverless.industries,O=serverless.industries\"]\n    b --&gt; c[\"dnQualifier=TEqiLeRuVnkbNOUsPlbDHIdp1tM=,CN=CS.smpte-430-2.leaf,OU=movies.serverless.industries,O=serverless.industries\"]\n</code></pre>\n\n<p>The field <code class=\"language-plaintext highlighter-rouge\">dnQualifier</code> in the subject is the fingerprint of the public key of the generated certificate:</p>\n\n<div class=\"language-sh highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"nb\">cat </span>leaf.pem | openssl x509 <span class=\"nt\">-pubkey</span> <span class=\"nt\">-noout</span> | <span class=\"se\">\\</span>\n    openssl <span class=\"nb\">base64</span> <span class=\"nt\">-d</span> | <span class=\"nb\">dd </span><span class=\"nv\">bs</span><span class=\"o\">=</span>1 <span class=\"nv\">skip</span><span class=\"o\">=</span>24 2&gt;/dev/null | <span class=\"se\">\\</span>\n    openssl sha1 <span class=\"nt\">-binary</span> | openssl <span class=\"nb\">base64</span>\n</code></pre></div></div>\n\n<p>Script for creating a certificate chain: <a href=\"https://github.com/NEU-Deli/dcitools/blob/master/create-smpte-chain.sh\" target=\"_blank\">create-smpte-chain.sh</a></p>\n\n<h2 id=\"key-delivery-messages\">Key Delivery Messages</h2>\n\n<p>A KDM is a XML file which contains the AES key of the DCP encrypted with the projectors Public Key.</p>\n\n<ul>\n  <li>Various XML Schemas: SMPTE 430-1, SMPTE 430-3</li>\n  <li>Document signing with XML-DSig</li>\n  <li>One or more <code class=\"language-plaintext highlighter-rouge\">&lt;enc:CipherValue&gt;</code> elements contain the AES keys and some metadata for multiple DCP OV/VF, both encrypted</li>\n</ul>\n\n<p>Decrypt the <code class=\"language-plaintext highlighter-rouge\">&lt;enc:CipherValue&gt;</code> with <code class=\"language-plaintext highlighter-rouge\">openssl</code>:</p>\n\n<div class=\"language-sh highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"nb\">cat </span>KDM_KaizoTrap_FTR-1_F_XX-XX_20_2K_20240119_SMPTE_OV_My_nonexistent_cinema_Nonexistent_Screen_1.xml | <span class=\"se\">\\</span>\n    xq <span class=\"nt\">-r</span> <span class=\"s1\">'.DCinemaSecurityMessage.AuthenticatedPrivate.\"enc:EncryptedKey\"[0].\"enc:CipherData\".\"enc:CipherValue\"'</span> | <span class=\"se\">\\</span>\n    <span class=\"nb\">base64</span> <span class=\"nt\">-d</span> | <span class=\"se\">\\</span>\n    openssl pkeyutl <span class=\"nt\">-decrypt</span> <span class=\"nt\">-inkey</span> leaf.key <span class=\"nt\">-pkeyopt</span> rsa_padding_mode:oaep <span class=\"o\">&gt;</span> kdminfo.bin\n</code></pre></div></div>\n\n<p>Output of <code class=\"language-plaintext highlighter-rouge\">hexdump -C kdminfo.bin</code>:</p>\n\n<pre><code class=\"language-txt\">00000000  f1 dc 12 44 60 16 9a 0e  85 bc 30 06 42 f8 66 ab  |...D`.....0.B.f.|\n00000010  28 fd 80 bf a9 bb f1 dc  48 d9 87 e9 5c c9 a5 41  |(.......H...\\..A|\n00000020  ab 8e 14 82 88 40 c3 90  2a 0b 46 21 b4 10 49 6b  |.....@..*.F!..Ik|\n00000030  b8 a4 a9 4f 4d 44 41 4b  2e 2e 05 b5 bb ed 45 79  |...OMDAK......Ey|\n00000040  96 cc 9c d6 00 ed db 4c  32 30 32 34 2d 30 31 2d  |.......L2024-01-|\n00000050  32 38 54 31 39 3a 30 30  3a 30 30 2b 30 31 3a 30  |28T19:00:00+01:0|\n00000060  30 32 30 32 34 2d 30 32  2d 30 34 54 32 30 3a 30  |02024-02-04T20:0|\n00000070  30 3a 30 30 2b 30 31 3a  30 30 4c 3a b6 ed 71 eb  |0:00+01:00L:..q.|\n00000080  29 25 90 48 6b 4f 96 2f  44 f6                    |)%.HkO./D.|\n0000008a\n</code></pre>\n\n<p>The file can now be sliced into single fields with <code class=\"language-plaintext highlighter-rouge\">dd</code>:</p>\n\n<div class=\"language-sh highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c\"># fingerprint base64 encoded</span>\n<span class=\"nb\">dd </span><span class=\"k\">if</span><span class=\"o\">=</span>kdminfo.bin <span class=\"nv\">bs</span><span class=\"o\">=</span>1 <span class=\"nv\">skip</span><span class=\"o\">=</span>16 <span class=\"nv\">count</span><span class=\"o\">=</span>20 <span class=\"nv\">status</span><span class=\"o\">=</span>none | <span class=\"nb\">base64</span>\n\n<span class=\"c\"># AES key in hexadecimal</span>\n<span class=\"nb\">dd </span><span class=\"k\">if</span><span class=\"o\">=</span>kdminfo.bin <span class=\"nv\">bs</span><span class=\"o\">=</span>1 <span class=\"nv\">skip</span><span class=\"o\">=</span>122 <span class=\"nv\">count</span><span class=\"o\">=</span>16 <span class=\"nv\">status</span><span class=\"o\">=</span>none | hexdump <span class=\"nt\">-C</span>\n\n<span class=\"c\"># date string in plain text ASCII</span>\n<span class=\"nb\">dd </span><span class=\"k\">if</span><span class=\"o\">=</span>kdminfo.bin <span class=\"nv\">bs</span><span class=\"o\">=</span>1 <span class=\"nv\">skip</span><span class=\"o\">=</span>97 <span class=\"nv\">count</span><span class=\"o\">=</span>25 <span class=\"nv\">status</span><span class=\"o\">=</span>none<span class=\"p\">;</span> <span class=\"nb\">echo</span>\n</code></pre></div></div>\n\n<p>Format:</p>\n\n<table class=\"table table-sm\">\n  <thead>\n    <tr>\n      <th>Start</th>\n      <th>Length</th>\n      <th>Description</th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr>\n      <td>0</td>\n      <td>16</td>\n      <td>Structure ID, binary, static <code class=\"language-plaintext highlighter-rouge\">0xf1dc124460169a0e85bc300642f866ab</code>\n</td>\n    </tr>\n    <tr>\n      <td>16</td>\n      <td>20</td>\n      <td>Signer certificate fingerprint, binary, <code class=\"language-plaintext highlighter-rouge\">KP2Av6m78dxI2YfpXMmlQauOFII=</code>\n</td>\n    </tr>\n    <tr>\n      <td>36</td>\n      <td>16</td>\n      <td>Composition Playlist UUID, <code class=\"language-plaintext highlighter-rouge\">8840c390-2a0b-4621-b410-496bb8a4a94f</code>\n</td>\n    </tr>\n    <tr>\n      <td>52</td>\n      <td>4</td>\n      <td>Key Type, ASCII, <code class=\"language-plaintext highlighter-rouge\">MDAK</code> = Main Sound</td>\n    </tr>\n    <tr>\n      <td>56</td>\n      <td>16</td>\n      <td>Key UUID, <code class=\"language-plaintext highlighter-rouge\">2e2e05b5-bbed-4579-96cc-9cd600eddb4c</code>\n</td>\n    </tr>\n    <tr>\n      <td>72</td>\n      <td>25</td>\n      <td>Not valid before date string, ASCII, <code class=\"language-plaintext highlighter-rouge\">2024-01-28T19:00:00+01:00</code>\n</td>\n    </tr>\n    <tr>\n      <td>97</td>\n      <td>25</td>\n      <td>Not valid after date string, ASCII, <code class=\"language-plaintext highlighter-rouge\">2024-02-04T20:00:00+01:00</code>\n</td>\n    </tr>\n    <tr>\n      <td>122</td>\n      <td>16</td>\n      <td>AES decryption key, binary, <code class=\"language-plaintext highlighter-rouge\">0x4c3ab6ed71eb292590486b4f962f44f6</code>\n</td>\n    </tr>\n  </tbody>\n</table>\n\n<ul>\n  <li>Validate signer certificate: Should match the fingerprint of the leaf certificate used to sign the KDM XML structure</li>\n</ul>\n\n<div class=\"language-sh highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>openssl asn1parse <span class=\"nt\">-in</span> leaf.pem <span class=\"nt\">-noout</span> <span class=\"nt\">-strparse</span> 4 <span class=\"nt\">-out</span> - | <span class=\"se\">\\</span>\n    openssl dgst <span class=\"nt\">-sha1</span> <span class=\"nt\">-binary</span> | <span class=\"se\">\\</span>\n    openssl <span class=\"nb\">base64</span>\n</code></pre></div></div>\n\n<ul>\n  <li>Validate Composition Playlist UUID: Should appear in the KDM XML field <code class=\"language-plaintext highlighter-rouge\">&lt;CompositionPlaylistId&gt;</code>\n</li>\n  <li>Validate Key UUID: Should appear in the KDM XML field <code class=\"language-plaintext highlighter-rouge\">&lt;KeyId&gt;</code>\n</li>\n  <li>Validate Key Type: Should match the type for the respective Key UUID <code class=\"language-plaintext highlighter-rouge\">&lt;TypedKeyId&gt;</code> block</li>\n  <li>Validate Dates: Should appear in the KDM XML fields <code class=\"language-plaintext highlighter-rouge\">&lt;ContentKeysNotValidBefore&gt;</code> and <code class=\"language-plaintext highlighter-rouge\">&lt;ContentKeysNotValidAfter&gt;</code>\n</li>\n</ul>\n\n<p>(This is a subset of checks documented in the <a href=\"https://www.dcimovies.com/compliance_test_plan/\" target=\"_blank\">DCI Compliance Test Plan</a>)</p>\n\n<p>The projector itself has no trusted CA store for validating KDMs or DCPs. The whole process relies on the fingerprint stored in the <code class=\"language-plaintext highlighter-rouge\">&lt;enc:CipherValue&gt;</code> and that the recipient cinema has no access to the Projectors Private Key.</p>\n\n<h2 id=\"trusted-device-list\">Trusted Device List</h2>\n\n<p>Could one just build their own DCI/DCP projector?</p>\n\n<p>Yes and no.</p>\n\n<p>The software exists, in theory just a Linux PC and any projector / sound system is required.</p>\n\n<p>The distributors use a so called \u201cTrusted Device List\u201d provided by the DCI certified projection system manufacturers. Projectors which are not mentioned on these lists will not get a DCP/KDM from many distributors.</p>\n\n<p>Also a DCI certified projection system must be installed by an authorized company.</p>\n\n<ul>\n  <li>KDM Request by Serial Number: Many distributors just ask for projector model and serial number. They can query the actual projection system certificate in their distribution system</li>\n  <li>KDM Request by Certificate: The cinema has to provide their projector certificate which is then validated against the projection systems manufacturer root certificate</li>\n</ul>\n\n<p>If a cinema is not \u201cplaying by the rules\u201d, a 30k+ EUR projection system will become an expensive brick.</p>\n\n<h2 id=\"mxf-file-format\">MXF File Format</h2>\n\n<p>MXF is used by the whole movie and broadcasting industry.</p>\n\n<p>The paper of the SMPTE standard is again purchase-only.</p>\n\n<p>Since the standard is quite complex, the Author used <a href=\"https://github.com/Myriadbits/MXFInspect\" target=\"_blank\">MXFInspect</a> as a shortcut and only parsed a single Frame / Triplet.</p>\n\n<h3 id=\"ber-encoding\">BER Encoding</h3>\n\n<p><strong>B</strong>asic <strong>E</strong>ncoding <strong>R</strong>ules:</p>\n\n<ul>\n  <li>BER is indicated if the first bit of the byte is a <code class=\"language-plaintext highlighter-rouge\">1</code>\n</li>\n  <li>Bits 2-8 contain the number of bytes used for the length</li>\n  <li>The length bytes contain the length of the data</li>\n</ul>\n\n<table class=\"table table-sm\">\n  <thead>\n    <tr>\n      <th>BER Indicator</th>\n      <th>Length</th>\n      <th>Content</th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr>\n      <td>\n<code class=\"language-plaintext highlighter-rouge\">0x83</code> = <code class=\"language-plaintext highlighter-rouge\">10000011b</code><br>= 3 bytes for length</td>\n      <td>\n<code class=\"language-plaintext highlighter-rouge\">0x004F0C</code><br>= <code class=\"language-plaintext highlighter-rouge\">20236d</code>\n</td>\n      <td>20236 bytes of data</td>\n    </tr>\n  </tbody>\n</table>\n\n<h3 id=\"triplet-format-single-movie-frame\">Triplet Format (Single Movie Frame)</h3>\n\n<h4 id=\"frame-triplet-structure\">Frame Triplet Structure</h4>\n\n<table class=\"table table-sm\">\n  <thead>\n    <tr>\n      <th>Start</th>\n      <th>Type</th>\n      <th>Length</th>\n      <th>Description</th>\n    </tr>\n  </thead>\n  <tbody>\n    <tr>\n      <td>0</td>\n      <td>DAT</td>\n      <td>16</td>\n      <td>Encrypted triplet key</td>\n    </tr>\n    <tr>\n      <td>16</td>\n      <td>BER</td>\n      <td>1</td>\n      <td>BER <code class=\"language-plaintext highlighter-rouge\">0x83</code> = <code class=\"language-plaintext highlighter-rouge\">10000011b</code> = Next field is 3 bytes long</td>\n    </tr>\n    <tr>\n      <td>17</td>\n      <td>LEN</td>\n      <td>3</td>\n      <td>\n<code class=\"language-plaintext highlighter-rouge\">0x004F0C</code> = <code class=\"language-plaintext highlighter-rouge\">20236d</code> = Length of the data</td>\n    </tr>\n    <tr>\n      <td>20</td>\n      <td>BER</td>\n      <td>1</td>\n      <td>BER <code class=\"language-plaintext highlighter-rouge\">0x83</code> = <code class=\"language-plaintext highlighter-rouge\">10000011b</code> = Next field is 3 bytes long</td>\n    </tr>\n    <tr>\n      <td>21</td>\n      <td>LEN</td>\n      <td>3</td>\n      <td>\n<code class=\"language-plaintext highlighter-rouge\">0x000010</code> = <code class=\"language-plaintext highlighter-rouge\">16d</code> = Length of the next field</td>\n    </tr>\n    <tr>\n      <td>24</td>\n      <td>DAT</td>\n      <td>16</td>\n      <td>Cryptographic Context Link</td>\n    </tr>\n    <tr>\n      <td>40</td>\n      <td>BER</td>\n      <td>1</td>\n      <td>BER <code class=\"language-plaintext highlighter-rouge\">0x83</code> = <code class=\"language-plaintext highlighter-rouge\">10000011b</code> = Next field is 3 bytes long</td>\n    </tr>\n    <tr>\n      <td>41</td>\n      <td>LEN</td>\n      <td>3</td>\n      <td>\n<code class=\"language-plaintext highlighter-rouge\">0x000008</code> = <code class=\"language-plaintext highlighter-rouge\">8d</code> = Length of the next field</td>\n    </tr>\n    <tr>\n      <td>44</td>\n      <td>DAT</td>\n      <td>8</td>\n      <td>Plaintext offset</td>\n    </tr>\n    <tr>\n      <td>52</td>\n      <td>BER</td>\n      <td>1</td>\n      <td>BER <code class=\"language-plaintext highlighter-rouge\">0x83</code> = <code class=\"language-plaintext highlighter-rouge\">10000011b</code> = Next field is 3 bytes long</td>\n    </tr>\n    <tr>\n      <td>53</td>\n      <td>LEN</td>\n      <td>3</td>\n      <td>\n<code class=\"language-plaintext highlighter-rouge\">0x000010</code> = <code class=\"language-plaintext highlighter-rouge\">16d</code> = Length of the next field</td>\n    </tr>\n    <tr>\n      <td>56</td>\n      <td>DAT</td>\n      <td>16</td>\n      <td>Source Key</td>\n    </tr>\n    <tr>\n      <td>72</td>\n      <td>BER</td>\n      <td>1</td>\n      <td>BER <code class=\"language-plaintext highlighter-rouge\">0x83</code> = <code class=\"language-plaintext highlighter-rouge\">10000011b</code> = Next field is 3 bytes long</td>\n    </tr>\n    <tr>\n      <td>73</td>\n      <td>LEN</td>\n      <td>3</td>\n      <td>\n<code class=\"language-plaintext highlighter-rouge\">0x000008</code> = <code class=\"language-plaintext highlighter-rouge\">8d</code> = Length of the next field</td>\n    </tr>\n    <tr>\n      <td>76</td>\n      <td>DAT</td>\n      <td>8</td>\n      <td>Source Length</td>\n    </tr>\n    <tr>\n      <td>84</td>\n      <td>BER</td>\n      <td>1</td>\n      <td>BER <code class=\"language-plaintext highlighter-rouge\">0x83</code> = <code class=\"language-plaintext highlighter-rouge\">10000011b</code> = Next field is 3 bytes long</td>\n    </tr>\n    <tr>\n      <td>85</td>\n      <td>LEN</td>\n      <td>3</td>\n      <td>\n<code class=\"language-plaintext highlighter-rouge\">0x004e90</code> = <code class=\"language-plaintext highlighter-rouge\">20112d</code> = Length of the next field</td>\n    </tr>\n    <tr>\n      <td>88</td>\n      <td>DAT</td>\n      <td>20112</td>\n      <td>Encrypted Source Value</td>\n    </tr>\n  </tbody>\n</table>\n\n<!--\n```\nCryptographic Context Link = 2F347C27660A4F87844B36F957D753A6\nPlaintext Offset           = 0000000000000000\nSource Key                 = 060E2B34010201010D01030115010801\nSource Length              = 0000000000004E61 = 20065d\n```\n\n> [color=#e862ea] TODO: What are these fields? \ud83e\udd37\u200d\u2642\ufe0f\n-->\n\n<h4 id=\"slice-the-data\">Slice the data</h4>\n\n<div class=\"language-bash highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c\"># slice the first frame from the MXF bodypartition</span>\n<span class=\"c\"># offsets from MXFInspect</span>\n<span class=\"nb\">dd </span><span class=\"k\">if</span><span class=\"o\">=</span>file.mxf <span class=\"nv\">bs</span><span class=\"o\">=</span>1 <span class=\"nv\">count</span><span class=\"o\">=</span>20256 <span class=\"nv\">skip</span><span class=\"o\">=</span>16524 <span class=\"nv\">of</span><span class=\"o\">=</span>block.bin\n\n<span class=\"c\"># slice data from the frame</span>\n<span class=\"nb\">dd </span><span class=\"k\">if</span><span class=\"o\">=</span>block.bin <span class=\"nv\">skip</span><span class=\"o\">=</span>88 <span class=\"nv\">count</span><span class=\"o\">=</span>20112 <span class=\"nv\">bs</span><span class=\"o\">=</span>1 <span class=\"nv\">of</span><span class=\"o\">=</span>block-encrypted-data.bin\n</code></pre></div></div>\n\n<h3 id=\"decrypt-frame-data\">Decrypt Frame Data</h3>\n\n<p>The DCP encryption key: <code class=\"language-plaintext highlighter-rouge\">4c3ab6ed71eb292590486b4f962f44f6</code></p>\n\n<h4 id=\"the-frame-iv\">The frame IV</h4>\n\n<p>Every Frame is using a unique IV (Initialization Vector), which ensures that the AES Block Cipher generates always different cipher texts and makes brute force harder. This works similar to a Password Salt.</p>\n\n<div class=\"language-bash highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c\"># the data starts with 16 bytes of iv and 16 bytes of cv</span>\n<span class=\"c\"># AES iv bytes</span>\n<span class=\"nb\">dd </span><span class=\"k\">if</span><span class=\"o\">=</span>block-encrypted-data.bin <span class=\"nv\">bs</span><span class=\"o\">=</span>1 <span class=\"nv\">count</span><span class=\"o\">=</span>16 <span class=\"nv\">of</span><span class=\"o\">=</span>iv.bin\n</code></pre></div></div>\n\n<p>Output of <code class=\"language-plaintext highlighter-rouge\">hexdump -C iv.bin</code>:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>00000000  16 6e 7b d1 67 81 44 2e  7a ca de 3c 46 cc d7 39  |.n{.g.D.z..&lt;F..9|\n00000010\n</code></pre></div></div>\n\n<p><em>See also \u201cECB Penguin\u201d on $searchengine for more details why the IV is so important.</em></p>\n\n<h4 id=\"validate-cv\">Validate CV</h4>\n\n<p>Since there is no way to know if a decryption was successful when the content of the data is unknown, a CV (Check Value) is used. The Check Value is encrypted with the same AES Key + IV, but the plain text value is known.</p>\n\n<div class=\"language-bash highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c\"># the data starts with 16 bytes of iv and 16 bytes of cv</span>\n<span class=\"c\"># AES cv bytes</span>\n<span class=\"nb\">dd </span><span class=\"k\">if</span><span class=\"o\">=</span>block-encrypted-data.bin <span class=\"nv\">bs</span><span class=\"o\">=</span>1 <span class=\"nv\">count</span><span class=\"o\">=</span>16 <span class=\"nv\">skip</span><span class=\"o\">=</span>16 <span class=\"nv\">of</span><span class=\"o\">=</span>cv.bin\n</code></pre></div></div>\n\n<div class=\"language-bash highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"nb\">cat </span>cv.bin | openssl enc <span class=\"nt\">-aes128</span> <span class=\"nt\">-d</span> <span class=\"se\">\\</span>\n    <span class=\"nt\">-K</span> 4c3ab6ed71eb292590486b4f962f44f6 <span class=\"se\">\\</span>\n    <span class=\"nt\">-iv</span> 166e7bd16781442e7acade3c46ccd739 <span class=\"se\">\\</span>\n    <span class=\"nt\">-nosalt</span> <span class=\"nt\">-nopad</span> | hexdump <span class=\"nt\">-C</span>\n</code></pre></div></div>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>00000000  43 48 55 4b 43 48 55 4b  43 48 55 4b 43 48 55 4b  |CHUKCHUKCHUKCHUK|\n00000010\n</code></pre></div></div>\n\n<p>If the result matches <code class=\"language-plaintext highlighter-rouge\">0x4348554B4348554B4348554B4348554B</code> the key is correct.</p>\n\n<h4 id=\"decrypt-data\">Decrypt data</h4>\n\n<div class=\"language-bash highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c\"># Remove IV and CV from the encrypted data block</span>\n<span class=\"nb\">dd </span><span class=\"nv\">bs</span><span class=\"o\">=</span>1 <span class=\"nv\">skip</span><span class=\"o\">=</span>32 <span class=\"se\">\\</span>\n    <span class=\"k\">if</span><span class=\"o\">=</span>block-encrypted-data.bin <span class=\"se\">\\</span>\n    <span class=\"nv\">of</span><span class=\"o\">=</span>block-encrypted-data-nocryptinfo.bin\n</code></pre></div></div>\n\n<div class=\"language-bash highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c\"># Decrypt data block</span>\n<span class=\"nb\">cat </span>block-encrypted-data-nocryptinfo.bin | <span class=\"se\">\\</span>\n    openssl enc <span class=\"nt\">-aes128</span> <span class=\"nt\">-d</span> <span class=\"se\">\\</span>\n        <span class=\"nt\">-K</span> 4c3ab6ed71eb292590486b4f962f44f6 <span class=\"se\">\\</span>\n        <span class=\"nt\">-iv</span> 166e7bd16781442e7acade3c46ccd739 <span class=\"se\">\\</span>\n        <span class=\"nt\">-nosalt</span> <span class=\"nt\">-nopad</span> <span class=\"o\">&gt;</span> block-decrypted-data.bin\n</code></pre></div></div>\n\n<p>MXFs created by DCP-o-Matic contain <code class=\"language-plaintext highlighter-rouge\">libdcp</code> in the hexdump, which indicates that the decryption was successful.</p>\n\n<p>Output of <code class=\"language-plaintext highlighter-rouge\">hexdump -C block-decrypted-data.bin | head</code>:</p>\n\n<div class=\"language-plaintext highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code>00000000  3a f0 b7 f5 53 49 eb b7  c0 c0 cb a5 c9 2f 35 19  |:...SI......./5.|\n00000010  00 00 00 00 00 00 00 00  00 00 07 ce 00 00 04 38  |...............8|\n00000020  00 00 00 00 00 00 00 00  00 03 0b 01 01 0b 01 01  |................|\n00000030  0b 01 01 ff 52 00 12 01  04 00 01 01 05 03 03 00  |....R...........|\n00000040  00 77 88 88 88 88 88 ff  5c 00 23 22 97 20 96 f0  |.w......\\.#\". ..|\n00000050  96 f0 96 c0 8f 00 8f 00  8e e0 87 50 87 50 87 68  |...........P.P.h|\n00000060  70 05 70 05 70 47 77 d3  77 d3 77 62 ff 55 00 13  |p.p.pGw.w.wb.U..|\n00000070  00 50 00 00 00 4d 40 00  00 00 00 49 00 00 00 00  |.P...M@....I....|\n00000080  49 ff 64 00 0a 00 01 6c  69 62 64 63 70 ff 90 00  |I.d....libdcp...|\n00000090  0a 00 00 00 00 4d 40 00  03 ff 93 ef fe 2c 71 ff  |.....M@......,q.|\n</code></pre></div></div>\n\n<h2 id=\"magic-signatures\">Magic Signatures</h2>\n\n<p>At first, the decrypted Frame could not be opened in GIMP.</p>\n\n<p>To enable programs to open a file in the correct way, many files contain so called \u201c<a href=\"https://en.wikipedia.org/wiki/List_of_file_signatures\" target=\"_blank\">Magic Signatures</a>\u201d at its beginning.</p>\n\n<p>This magic value in <code class=\"language-plaintext highlighter-rouge\">block-decrypted-data.bin</code> is incorrect for unknown reasons. When extracting a JPEG2000 frame from an unencrypted MXF, the signature is correct. As a simple hack, the author just copied the signature from the unencrypted MXF frame to the decrypted one:</p>\n\n<div class=\"language-bash highlighter-rouge\"><div class=\"highlight\"><pre class=\"highlight\"><code><span class=\"c\"># offset again from MXFInspect</span>\n<span class=\"nb\">dd </span><span class=\"k\">if</span><span class=\"o\">=</span>j2c_caa6b39e-bd57-4676-9797-112e96a6f0c3.mxf <span class=\"nv\">bs</span><span class=\"o\">=</span>1 <span class=\"nv\">skip</span><span class=\"o\">=</span>16524 <span class=\"nv\">count</span><span class=\"o\">=</span>20085 <span class=\"nv\">of</span><span class=\"o\">=</span>frame.bin\n\n<span class=\"c\"># unencrypted frame does not contain crypto info</span>\n<span class=\"c\"># so we just have to slice the first BER header</span>\n<span class=\"nb\">dd </span><span class=\"k\">if</span><span class=\"o\">=</span>framedata.bin.j2k <span class=\"nv\">bs</span><span class=\"o\">=</span>1 <span class=\"nv\">skip</span><span class=\"o\">=</span>20 <span class=\"nv\">count</span><span class=\"o\">=</span>16 <span class=\"nv\">of</span><span class=\"o\">=</span>j2k_header.bin\n\n<span class=\"c\"># now back to the decrypted mxf frame</span>\n<span class=\"c\"># replace signature</span>\n<span class=\"nb\">cp </span>j2k_header.bin frame.j2k\n<span class=\"nb\">dd </span><span class=\"k\">if</span><span class=\"o\">=</span>block-decrypted-data.bin <span class=\"nv\">bs</span><span class=\"o\">=</span>1 <span class=\"nv\">skip</span><span class=\"o\">=</span>16 <span class=\"o\">&gt;&gt;</span> image.j2k\n</code></pre></div></div>\n\n<p>It should now be possible to open the file in GIMP. GIMP will show the frame with wrong colors, as MXF files use a custom color space.</p>\n\n<p><img src=\"https://serverless.industries/assets/kaizotrap-decrypted.jpg\" alt=\"Movie Screenshot with wrong color space\" class=\"img-fluid\"></p>\n\n<p>The original Frame with correct colors:</p>\n\n<p><img src=\"https://serverless.industries/assets/kaizotrap-original.jpg\" alt=\"Movie Screenshot with correct colors\" class=\"img-fluid\"></p>\n\n<p>Credit: <a href=\"https://www.youtube.com/watch?v=lIES3ii-IOg\" target=\"_blank\">Kaizo Trap by Guy Collins Animation</a></p>\n\n<h2 id=\"why-its-safe\">Why it\u2019s safe</h2>\n\n<p>Everything relies on \u201cnever reusing AES Keys\u201d and \u201cprotected private keys\u201d.</p>\n\n<ul>\n  <li>KDM data is encrypted with a 2048 bit RSA key</li>\n  <li>MXF data is encrypted with AES-128</li>\n  <li>Decryption keys are always stored in TPM-like hardware</li>\n  <li>Every DCP uses unique encryption keys</li>\n  <li>Every projector uses unique certificates/keys</li>\n  <li>A DCI certified projector is required</li>\n  <li>Distributors can verify if a certificate belongs to a certified DCI projector</li>\n</ul>\n\n<p><img src=\"https://serverless.industries/assets/xkcd-encryption-security.png\" alt=\"Encryption Security\" class=\"img-fluid\"></p>\n\n<p>Source: <a href=\"https://xkcd.com/538/\" target=\"_blank\">xkcd.com/538/</a></p>\n\n<h2 id=\"just-record-it\">Just record it</h2>\n\n<p>Encrypted DCPs use <a href=\"https://dcpomatic.com/forum/viewtopic.php?t=2372\" target=\"_blank\">Forensic Watermarks</a> which contain the serial number of the\nprojection system. So if a recorded copy of a movie appears online, the theatre will have to answer\nserious questions and may never get movies again.</p>\n\n<p>Pirated copies of movies have it\u2019s origin most likely from other sources.</p>\n\n<h2 id=\"how-its-going\">How it\u2019s going</h2>\n\n<p>DCI has released version 1.4.4 of the <a href=\"https://www.dcimovies.com/specification/\" target=\"_blank\">specification</a>, which now allows playback of DCPs with expired signer certificate.</p>\n\n<p>The manufacturers already started to work on a software update.</p>\n\n<h2 id=\"sources\">Sources</h2>\n\n<ul>\n  <li><a href=\"https://www.dcimovies.com/specification/index.html\" target=\"_blank\">DCI Specification</a></li>\n  <li><a href=\"https://www.dcimovies.com/compliance_test_plan/\" target=\"_blank\">DCI Compliance Test Plan</a></li>\n  <li><a href=\"https://marc.info/?l=openssl-users&amp;m=114884533706459&amp;w=2\" target=\"_blank\">OpenSSL Foo</a></li>\n  <li><a href=\"https://github.com/wolfgangw/digital_cinema_tools_distribution/blob/master/cinemaslides#L1737\" target=\"_blank\">Key Types</a></li>\n  <li><a href=\"https://github.com/wolfgangw/digital_cinema_tools_distribution/tree/master\" target=\"_blank\">DC Tools by WolfgangW</a></li>\n  <li><a href=\"https://github.com/Myriadbits/MXFInspect\" target=\"_blank\">MXFInspect</a></li>\n  <li><a href=\"https://tech.ebu.ch/docs/techreview/trev_2010-Q3_MXF-2.pdf\" target=\"_blank\">MXF Triplets</a></li>\n  <li><a href=\"https://web.archive.org/web/20201103045630id_/https://ieeexplore.ieee.org/ielx7/8984679/8984680/08984681.pdf\" target=\"_blank\">SMPTE Standard</a></li>\n  <li><a href=\"https://interop-docs.cinepedia.com/Document_Release_2.0/mpeg_ii_track_file_encryption.pdf\" target=\"_blank\">MXF Triplet Encryption</a></li>\n  <li><a href=\"https://github.com/cth103/libdcp/issues/11\" target=\"_blank\">Showing jpeg2000 in Gimp / parsing jpeg2000 data from mxf</a></li>\n  <li><a href=\"https://en.wikipedia.org/wiki/List_of_file_signatures\" target=\"_blank\">List of magic signatures</a></li>\n  <li><a href=\"https://dcpomatic.com/forum/viewtopic.php?t=2372\" target=\"_blank\">Forensic Watermarks in DCPs</a></li>\n</ul>\n\n<h2 id=\"credits\">Credits</h2>\n\n<p>Thanks for reading and feedback goes to: Pliskin, babel</p>\n\n<p>There are alot of comments on <a href=\"https://news.ycombinator.com/item?id=43745281\" target=\"_blank\">Hacker News</a>.</p>",
                        "value": "The Cinema Industry is using its own standards for creating and distributing movies in a secure way. The DCI (Digital Cinema Initiatives) specification defines everything from file formats and encryption to the projection systems itself.\nThe specification itself is publicly available but relies on various IEEE (Institute of Electrical and Electronics Engineers) and SMPTE (Society of Motion Picture and Television Engineers) standards, which have to be purchased. Scope\nThis document will show a rough overview of the DCI workflow and describe in detail how the encryption of a DCI movie works. It will not show how to break any encryption and no encryption has been broken while writing this document. From our perspective, the DCI standard is safe.\nThe author of this document has been operating a cinema since 2021, without any insight to the distribution or production side of the industry. Some information may be incomplete. How it\u2019s started\nEnd of 2023 the movie WONKA was released. Some cinemas reported, that they are unable to start this movie on their projector.\nThe reason was, that a certificate used by the distributor was expired. This certificate was used to sign the DCP files.\nThe distributor published new files, starting the movie worked again.\nOut of curiosity the author started to check how the validation process on the projector is working. Glossar DCP: Digital Cinema Package - A folder which contains all components of a movie. Metadata, Subtitles, Audio and Picture in seperate files CPL: Composition Playlist - A DCP can contain multiple audio and video streams which will combined in a CPL KDM: Key Delivery Message - A XML file which contains the cryptographical information to allow a movie playback on a specific DCI certified projection system DKDM: Distribution Key Delivery Message - Similar to a KDM, but for a remastering or distribution system, not for a projection system Distribution Process graph TD classDef red fill:#a91900 classDef green fill:#126500 classDef purple fill:#650072 legendprod[Producer]:::red legenddist[Distributor]:::green legendcine[Cinema]:::purple legendvendors[Projector Manufacturer] graph LR classDef red fill:#a91900 classDef green fill:#126500 classDef purple fill:#650072 dcpcreated([\"DCP\\ncreated\"]):::red --\"AES Key\"--> dkdmcreated dkdmcreated([\"DKDM\\ncreated\"]):::red --\"encrypted\\nAES Key\"--> dkdmp dkdmp[DKDM]:::green --> verify([\"Projector\\nverified\"]):::green dcpcreated --\"to Distributor\"--> dcp([\"DCP by\\nDownload or\\nHarddrive\"]):::green distcert[\"Distributor\\nCertificate\"]:::green --\"to Producer\"--> dkdmcreated verify --\"to Cinema\"--> kdm[KDM]:::purple dcp --\"to Cinema\"--> dcp2[\"DCP\"]:::purple cinecert[\"Projector\\nCertificate\"]:::purple --\"to Distributor\"--> verify trusted[\"Trusted Device List\"] --> verify dcp2 --> projector[Projector]:::purple kdm --> projector Projection Systems\nMost of the Projection Systems consists of Server, Audio Processor and Projector. graph LR server[Server] --> mediablock[Media Block] hdmi[HDMI Source] --> splitter[Audio Splitter] splitter --\"Video\"--> projector splitter --\"Audio\"--> sound subgraph Projector mediablock --\"Video\"--> projector[Projector] end projector --> screen[Theatre Screen] mediablock --\"Audio\"--> sound[Audio Processor] sound --> speakers[Speakers]\nThe Server stores the DCPs and KDMs, manages Playlists and controls the Projector and Audio Processor hardware.\nMany Cinemas also connect their Theatre Automation (light, curtains, etc.) to the projector. By setting time-based commands into the playlists, lights and screen curtain can be controlled automatically. The Projector Server provides 12V/24V relays for this.\nThese time-based commands are also used to set the correct Aspect Ratio and Volume on the different DCPs in a Playlist.\nThe Server can be controlled remotely by a PC or in larger cinemas by a Theatre Management System.\nThe DCPs are imported from USB/CRU hard drives or by internet download to the server. They are stored encrypted at all times.\nThe projector includes a so called \u201cMedia Block\u201d which handles DRM and decryption. It receives the DCP data and the KDM to decrypt each frame in real time.\nOn DCP playback the Projector will send the decrypted PCM audio to the Audio Processor which will then send each processed Audio Channel to the respective Speakers.\nDCP Disks DCP Format\nA DCP is a folder which contains XML metadata files and multiple MXF files for the actual movie. Folder Naming Pattern AwesomeMovie_FTR-2_S_DE-XX_DE-16_51_4K_20240119_SMPTE_OV AwesomeMovie: A short version of the movie title FTR: Media Type, in this case \u201cFeature\u201d 2: Version Number S: Aspect ratio, in this case 2.35:1 aka \u201cSope\u201d DE: Audio Language XX: Subtitle Language, in this case is no subtitle available DE: Territory 16: Age Rating 51: Audio channels, in this case 5.1 surround sound 4K: Movie resolution, in this case 4096x1716 pixels 20240119: Mastering timestamp SMPTE: DCP standard, there is also Interop OV: Package type, original version or VF for version file\nCredits: http://static.kinofreund.com/dcnt/ Mastering Process\nDuring the Mastering Process a static AES 128 bit key is generated and the original medium is converted into MXF files. One for picture and one for audio.\nFor mastering the tool DCP-o-matic can be used. There are also some commercial products. graph LR movie[\"Source\"] --> mastering{{\"Movie Mastering\"}} mastering --> dcpkey[\"Static<br>AES-128 Key\"] mastering --> dcp[\"Digital Cinema Package (DCP)\"] dcp --> dcppics[\"Encrypted<br>Picture Data\"] dcp --> dcpsound[\"Encrypted<br>Audio Data\"] dcp --> dcpsub[\"Encrypted<br>Subtitle Data\"]\nThe video stream is encoded as one single JPEG2000 picture per frame. Each frame is encrypted with the same static AES key.\nThe audio stream is (most likely) chunked into one BWF (Broadcast Wave Format) stream per frame and also encrypted separately. (The author haven\u2019t found any information about this yet.)\nA DCP can have a size of 200 GB or more. Some newer releases can hit the Terabyte, if multiple versions (Languages, Subtitles, 2D/3D) are shipped on the same harddrive.\nThe subtitles are provided in an XML file or are burned directly into the picture frames. If it is provided as an XML file, the projector will render the subtitles using a TTF font file. <?xml version=\"1.0\" encoding=\"UTF-8\"?> <DCSubtitle Version=\"1.0\"> <SubtitleID>xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx</SubtitleID> <MovieTitle>MovieTitle</MovieTitle> <ReelNumber>1</ReelNumber> <Language>de</Language> <LoadFont Id=\"Font1\" URI=\"Arial.ttf\" /> <Font Id=\"Font1\" Color=\"ffffffff\" Effect=\"border\" EffectColor=\"ff000000\" Italic=\"no\" Script=\"normal\" Size=\"42\" Underlined=\"no\" Weight=\"normal\" AspectAdjust=\"1\" Spacing=\"0em\"> <Subtitle SpotNumber=\"1\" TimeIn=\"00:07:45:094\" TimeOut=\"00:07:48:000\" FadeUpTime=\"000\" FadeDownTime=\"000\"> <Text HPosition=\"0\" VAlign=\"bottom\" VPosition=\"7\">W\u00dcSTE VON NEVADA - HEUTE</Text> </Subtitle> </Font> </DCSubtitle> Supplemental DCP (VF, Version File)\nA supplemental DCP allows to reuse the Original DCP (OV, Original File) picture and replace audio or subtitle tracks. This makes it possible to support different languages without the need of shipping hundreds of gigabytes of duplicated picture data to the cinemas. graph TD f[PaktDerWoelfe_FTR_S-235_FR-XX_DE_51_4K_SC_20230926_EGB_SMPTE_OV] f --> a[PaktDerWoelBoC_FTR_S-235_DE-DE_DE_51_4K_SC_20231115_EGB_SMPTE_VF] f --> b[PaktDerWoelBoC_FTR_S-235_FR-DE_DE_51_4K_SC_20231115_EGB_SMPTE_VF]\nExample: One version file for German with German subtitles and one for French with German subtitles.\nIt is even possible to provide differnt cuts of a movie for different regions. Software\nTwo Open Source C++ implementations, both in active development, one from the industry!\ncth103/libdcp, a C++ library written by the makers of DCP-o-matic: https://git.carlh.net/gitweb/?p=dcpomatic.git;a=shortlog https://dcpomatic.com/ (source code)\nasdcplib, a C++ library written by companies active in the cinema industry:\nThe asdcplib project was originally exchanged by FTP. The project was on SourceForge between 2005 and 2008, when it moved to a release-only distribution via CineCert. As of late February 2019, its new home is on github.\n\u2013 https://github.com/cinecert/asdcplib\nAlmost all software, commercial or open source is using one of these libraries. Distribution\nThe symmetric encryption key for the DCP has to be protected in some way. For this, the producer will create a DKDM XML file, containing the AES Key, which has been encrypted with the Certificate Public Key of the distributor.\nThe distributor can then use the DKDM to create KDM XML files for cinemas. This means decrypting the DCP AES Key and encrypting it again with the Certificate Public Key of the target projection system. Certificate Chains\nBoth projectors and distributors use SSL certificate chains to sign and encrypt/decrypt data.\nA chain always consists of a Root Certificate Authority (CA), Intermediate CA and a Leaf Certificate. graph TD a[\"dnQualifier=ZwVjULQy61,CN=.smpte-430-2.root,OU=movies.serverless.industries,O=serverless.industries\"] a --> b[\"dnQualifier=/JLF0JtscFuJahVNXfYu1w1u7SI=,CN=.smpte-430-2.intermediate,OU=movies.serverless.industries,O=serverless.industries\"] b --> c[\"dnQualifier=TEqiLeRuVnkbNOUsPlbDHIdp1tM=,CN=CS.smpte-430-2.leaf,OU=movies.serverless.industries,O=serverless.industries\"]\nThe field dnQualifier in the subject is the fingerprint of the public key of the generated certificate: cat leaf.pem | openssl x509 -pubkey -noout | \\ openssl base64 -d | dd bs=1 skip=24 2>/dev/null | \\ openssl sha1 -binary | openssl base64\nScript for creating a certificate chain: create-smpte-chain.sh Key Delivery Messages\nA KDM is a XML file which contains the AES key of the DCP encrypted with the projectors Public Key. Various XML Schemas: SMPTE 430-1, SMPTE 430-3 Document signing with XML-DSig One or more <enc:CipherValue> elements contain the AES keys and some metadata for multiple DCP OV/VF, both encrypted\nDecrypt the <enc:CipherValue> with openssl: cat KDM_KaizoTrap_FTR-1_F_XX-XX_20_2K_20240119_SMPTE_OV_My_nonexistent_cinema_Nonexistent_Screen_1.xml | \\ xq -r '.DCinemaSecurityMessage.AuthenticatedPrivate.\"enc:EncryptedKey\"[0].\"enc:CipherData\".\"enc:CipherValue\"' | \\ base64 -d | \\ openssl pkeyutl -decrypt -inkey leaf.key -pkeyopt rsa_padding_mode:oaep > kdminfo.bin\nOutput of hexdump -C kdminfo.bin: 00000000 f1 dc 12 44 60 16 9a 0e 85 bc 30 06 42 f8 66 ab |...D`.....0.B.f.| 00000010 28 fd 80 bf a9 bb f1 dc 48 d9 87 e9 5c c9 a5 41 |(.......H...\\..A| 00000020 ab 8e 14 82 88 40 c3 90 2a 0b 46 21 b4 10 49 6b |.....@..*.F!..Ik| 00000030 b8 a4 a9 4f 4d 44 41 4b 2e 2e 05 b5 bb ed 45 79 |...OMDAK......Ey| 00000040 96 cc 9c d6 00 ed db 4c 32 30 32 34 2d 30 31 2d |.......L2024-01-| 00000050 32 38 54 31 39 3a 30 30 3a 30 30 2b 30 31 3a 30 |28T19:00:00+01:0| 00000060 30 32 30 32 34 2d 30 32 2d 30 34 54 32 30 3a 30 |02024-02-04T20:0| 00000070 30 3a 30 30 2b 30 31 3a 30 30 4c 3a b6 ed 71 eb |0:00+01:00L:..q.| 00000080 29 25 90 48 6b 4f 96 2f 44 f6 |)%.HkO./D.| 0000008a\nThe file can now be sliced into single fields with dd: # fingerprint base64 encoded dd if=kdminfo.bin bs=1 skip=16 count=20 status=none | base64 # AES key in hexadecimal dd if=kdminfo.bin bs=1 skip=122 count=16 status=none | hexdump -C # date string in plain text ASCII dd if=kdminfo.bin bs=1 skip=97 count=25 status=none; echo\nFormat: Start Length Description 0 16 Structure ID, binary, static 0xf1dc124460169a0e85bc300642f866ab 16 20 Signer certificate fingerprint, binary, KP2Av6m78dxI2YfpXMmlQauOFII= 36 16 Composition Playlist UUID, 8840c390-2a0b-4621-b410-496bb8a4a94f 52 4 Key Type, ASCII, MDAK = Main Sound 56 16 Key UUID, 2e2e05b5-bbed-4579-96cc-9cd600eddb4c 72 25 Not valid before date string, ASCII, 2024-01-28T19:00:00+01:00 97 25 Not valid after date string, ASCII, 2024-02-04T20:00:00+01:00 122 16 AES decryption key, binary, 0x4c3ab6ed71eb292590486b4f962f44f6 Validate signer certificate: Should match the fingerprint of the leaf certificate used to sign the KDM XML structure openssl asn1parse -in leaf.pem -noout -strparse 4 -out - | \\ openssl dgst -sha1 -binary | \\ openssl base64 Validate Composition Playlist UUID: Should appear in the KDM XML field <CompositionPlaylistId> Validate Key UUID: Should appear in the KDM XML field <KeyId> Validate Key Type: Should match the type for the respective Key UUID <TypedKeyId> block Validate Dates: Should appear in the KDM XML fields <ContentKeysNotValidBefore> and <ContentKeysNotValidAfter>\n(This is a subset of checks documented in the DCI Compliance Test Plan)\nThe projector itself has no trusted CA store for validating KDMs or DCPs. The whole process relies on the fingerprint stored in the <enc:CipherValue> and that the recipient cinema has no access to the Projectors Private Key. Trusted Device List\nCould one just build their own DCI/DCP projector?\nYes and no.\nThe software exists, in theory just a Linux PC and any projector / sound system is required.\nThe distributors use a so called \u201cTrusted Device List\u201d provided by the DCI certified projection system manufacturers. Projectors which are not mentioned on these lists will not get a DCP/KDM from many distributors.\nAlso a DCI certified projection system must be installed by an authorized company. KDM Request by Serial Number: Many distributors just ask for projector model and serial number. They can query the actual projection system certificate in their distribution system KDM Request by Certificate: The cinema has to provide their projector certificate which is then validated against the projection systems manufacturer root certificate\nIf a cinema is not \u201cplaying by the rules\u201d, a 30k+ EUR projection system will become an expensive brick. MXF File Format\nMXF is used by the whole movie and broadcasting industry.\nThe paper of the SMPTE standard is again purchase-only.\nSince the standard is quite complex, the Author used MXFInspect as a shortcut and only parsed a single Frame / Triplet. BER Encoding\nBasic Encoding Rules: BER is indicated if the first bit of the byte is a 1 Bits 2-8 contain the number of bytes used for the length The length bytes contain the length of the data BER Indicator Length Content 0x83 = 10000011b\n= 3 bytes for length 0x004F0C\n= 20236d 20236 bytes of data Triplet Format (Single Movie Frame) Frame Triplet Structure Start Type Length Description 0 DAT 16 Encrypted triplet key 16 BER 1 BER 0x83 = 10000011b = Next field is 3 bytes long 17 LEN 3 0x004F0C = 20236d = Length of the data 20 BER 1 BER 0x83 = 10000011b = Next field is 3 bytes long 21 LEN 3 0x000010 = 16d = Length of the next field 24 DAT 16 Cryptographic Context Link 40 BER 1 BER 0x83 = 10000011b = Next field is 3 bytes long 41 LEN 3 0x000008 = 8d = Length of the next field 44 DAT 8 Plaintext offset 52 BER 1 BER 0x83 = 10000011b = Next field is 3 bytes long 53 LEN 3 0x000010 = 16d = Length of the next field 56 DAT 16 Source Key 72 BER 1 BER 0x83 = 10000011b = Next field is 3 bytes long 73 LEN 3 0x000008 = 8d = Length of the next field 76 DAT 8 Source Length 84 BER 1 BER 0x83 = 10000011b = Next field is 3 bytes long 85 LEN 3 0x004e90 = 20112d = Length of the next field 88 DAT 20112 Encrypted Source Value Slice the data # slice the first frame from the MXF bodypartition # offsets from MXFInspect dd if=file.mxf bs=1 count=20256 skip=16524 of=block.bin # slice data from the frame dd if=block.bin skip=88 count=20112 bs=1 of=block-encrypted-data.bin Decrypt Frame Data\nThe DCP encryption key: 4c3ab6ed71eb292590486b4f962f44f6 The frame IV\nEvery Frame is using a unique IV (Initialization Vector), which ensures that the AES Block Cipher generates always different cipher texts and makes brute force harder. This works similar to a Password Salt. # the data starts with 16 bytes of iv and 16 bytes of cv # AES iv bytes dd if=block-encrypted-data.bin bs=1 count=16 of=iv.bin\nOutput of hexdump -C iv.bin: 00000000 16 6e 7b d1 67 81 44 2e 7a ca de 3c 46 cc d7 39 |.n{.g.D.z..<F..9| 00000010\nSee also \u201cECB Penguin\u201d on $searchengine for more details why the IV is so important. Validate CV\nSince there is no way to know if a decryption was successful when the content of the data is unknown, a CV (Check Value) is used. The Check Value is encrypted with the same AES Key + IV, but the plain text value is known. # the data starts with 16 bytes of iv and 16 bytes of cv # AES cv bytes dd if=block-encrypted-data.bin bs=1 count=16 skip=16 of=cv.bin cat cv.bin | openssl enc -aes128 -d \\ -K 4c3ab6ed71eb292590486b4f962f44f6 \\ -iv 166e7bd16781442e7acade3c46ccd739 \\ -nosalt -nopad | hexdump -C 00000000 43 48 55 4b 43 48 55 4b 43 48 55 4b 43 48 55 4b |CHUKCHUKCHUKCHUK| 00000010\nIf the result matches 0x4348554B4348554B4348554B4348554B the key is correct. Decrypt data # Remove IV and CV from the encrypted data block dd bs=1 skip=32 \\ if=block-encrypted-data.bin \\ of=block-encrypted-data-nocryptinfo.bin # Decrypt data block cat block-encrypted-data-nocryptinfo.bin | \\ openssl enc -aes128 -d \\ -K 4c3ab6ed71eb292590486b4f962f44f6 \\ -iv 166e7bd16781442e7acade3c46ccd739 \\ -nosalt -nopad > block-decrypted-data.bin\nMXFs created by DCP-o-Matic contain libdcp in the hexdump, which indicates that the decryption was successful.\nOutput of hexdump -C block-decrypted-data.bin | head: 00000000 3a f0 b7 f5 53 49 eb b7 c0 c0 cb a5 c9 2f 35 19 |:...SI......./5.| 00000010 00 00 00 00 00 00 00 00 00 00 07 ce 00 00 04 38 |...............8| 00000020 00 00 00 00 00 00 00 00 00 03 0b 01 01 0b 01 01 |................| 00000030 0b 01 01 ff 52 00 12 01 04 00 01 01 05 03 03 00 |....R...........| 00000040 00 77 88 88 88 88 88 ff 5c 00 23 22 97 20 96 f0 |.w......\\.#\". ..| 00000050 96 f0 96 c0 8f 00 8f 00 8e e0 87 50 87 50 87 68 |...........P.P.h| 00000060 70 05 70 05 70 47 77 d3 77 d3 77 62 ff 55 00 13 |p.p.pGw.w.wb.U..| 00000070 00 50 00 00 00 4d 40 00 00 00 00 49 00 00 00 00 |.P...M@....I....| 00000080 49 ff 64 00 0a 00 01 6c 69 62 64 63 70 ff 90 00 |I.d....libdcp...| 00000090 0a 00 00 00 00 4d 40 00 03 ff 93 ef fe 2c 71 ff |.....M@......,q.| Magic Signatures\nAt first, the decrypted Frame could not be opened in GIMP.\nTo enable programs to open a file in the correct way, many files contain so called \u201cMagic Signatures\u201d at its beginning.\nThis magic value in block-decrypted-data.bin is incorrect for unknown reasons. When extracting a JPEG2000 frame from an unencrypted MXF, the signature is correct. As a simple hack, the author just copied the signature from the unencrypted MXF frame to the decrypted one: # offset again from MXFInspect dd if=j2c_caa6b39e-bd57-4676-9797-112e96a6f0c3.mxf bs=1 skip=16524 count=20085 of=frame.bin # unencrypted frame does not contain crypto info # so we just have to slice the first BER header dd if=framedata.bin.j2k bs=1 skip=20 count=16 of=j2k_header.bin # now back to the decrypted mxf frame # replace signature cp j2k_header.bin frame.j2k dd if=block-decrypted-data.bin bs=1 skip=16 >> image.j2k\nIt should now be possible to open the file in GIMP. GIMP will show the frame with wrong colors, as MXF files use a custom color space.\nMovie Screenshot with wrong color space\nThe original Frame with correct colors:\nMovie Screenshot with correct colors\nCredit: Kaizo Trap by Guy Collins Animation Why it\u2019s safe\nEverything relies on \u201cnever reusing AES Keys\u201d and \u201cprotected private keys\u201d. KDM data is encrypted with a 2048 bit RSA key MXF data is encrypted with AES-128 Decryption keys are always stored in TPM-like hardware Every DCP uses unique encryption keys Every projector uses unique certificates/keys A DCI certified projector is required Distributors can verify if a certificate belongs to a certified DCI projector\nEncryption Security\nSource: xkcd.com/538/ Just record it\nEncrypted DCPs use Forensic Watermarks which contain the serial number of the projection system. So if a recorded copy of a movie appears online, the theatre will have to answer serious questions and may never get movies again.\nPirated copies of movies have it\u2019s origin most likely from other sources. How it\u2019s going\nDCI has released version 1.4.4 of the specification, which now allows playback of DCPs with expired signer certificate.\nThe manufacturers already started to work on a software update. Sources DCI Specification DCI Compliance Test Plan OpenSSL Foo Key Types DC Tools by WolfgangW MXFInspect MXF Triplets SMPTE Standard MXF Triplet Encryption Showing jpeg2000 in Gimp / parsing jpeg2000 data from mxf List of magic signatures Forensic Watermarks in DCPs Credits\nThanks for reading and feedback goes to: Pliskin, babel\nThere are alot of comments on Hacker News.",
                        "lang": "en"
                    }
                ],
                "author": [
                    {
                        "type": [
                            "h-card"
                        ],
                        "properties": {
                            "name": [
                                "christian"
                            ],
                            "url": [
                                "https://serverless.industries/people/christian.html"
                            ]
                        },
                        "lang": "en",
                        "value": "christian"
                    }
                ]
            },
            "lang": "en"
        },
        {
            "type": [
                "h-card"
            ],
            "properties": {
                "note": [
                    "Hi! \ud83d\udc4b My name is Christian. I am a software engineer with strong IT infra\u00adstructure skills. I help running the Cinema and Arthouse NEU-Deli in Einbeck in my freetime."
                ],
                "name": [
                    "Christian"
                ],
                "photo": [
                    {
                        "value": "https://serverless.industries/christian.jpg",
                        "alt": "Picture of Christian"
                    }
                ],
                "url": [
                    "https://serverless.industries/people/christian.html"
                ],
                "uid": [
                    "https://serverless.industries/people/christian.html"
                ]
            },
            "lang": "en"
        }
    ],
    "rels": {
        "stylesheet": [
            "https://serverless.industries/css/main.css?commit=cc5a27a251b3c7815f33e676803e1803720b0e17",
            "https://serverless.industries/css/icons.css?commit=cc5a27a251b3c7815f33e676803e1803720b0e17"
        ],
        "webmention": [
            "https://intercom.serverless.industries/webmentions/endpoint"
        ],
        "pingback": [
            "https://intercom.serverless.industries/pingback/endpoint"
        ],
        "authorization_endpoint": [
            "https://intercom.serverless.industries/auth/relme/auth"
        ],
        "token_endpoint": [
            "https://intercom.serverless.industries/auth/relme/token"
        ],
        "indieauth-metadata": [
            "https://intercom.serverless.industries/auth/relme/metadata"
        ],
        "micropub": [
            "https://intercom.serverless.industries/micropub"
        ],
        "canonical": [
            "https://serverless.industries/2024/05/31/digital-cinema.en.html"
        ],
        "alternate": [
            "https://serverless.industries/feed/frontpage.xml"
        ],
        "search": [
            "https://serverless.industries/opensearch.xml"
        ],
        "icon": [
            "https://serverless.industries/favicon/favicon-96x96.png?commit=cc5a27a251b3c7815f33e676803e1803720b0e17",
            "https://serverless.industries/favicon/favicon.svg?commit=cc5a27a251b3c7815f33e676803e1803720b0e17",
            "https://serverless.industries/favicon.ico?commit=cc5a27a251b3c7815f33e676803e1803720b0e17"
        ],
        "shortcut": [
            "https://serverless.industries/favicon.ico?commit=cc5a27a251b3c7815f33e676803e1803720b0e17"
        ],
        "apple-touch-icon": [
            "https://serverless.industries/favicon/apple-touch-icon.png?commit=cc5a27a251b3c7815f33e676803e1803720b0e17"
        ],
        "manifest": [
            "https://serverless.industries/site.webmanifest?commit=cc5a27a251b3c7815f33e676803e1803720b0e17"
        ],
        "me": [
            "https://einbeck.social/@christian",
            "https://github.com/perryflynn",
            "https://chaos.social/@perryflynn",
            "https://git.brickburg.de/christian"
        ],
        "external": [
            "https://einbeck.social/@christian",
            "https://github.com/perryflynn",
            "https://commons.wikimedia.org/wiki/File:Datacenter_Empty_Floor_(22166545884).jpg",
            "https://github.com/perryflynn/serverless.industries/commit/cc5a27a251b3c7815f33e676803e1803720b0e17"
        ],
        "nofollow": [
            "https://einbeck.social/@christian",
            "https://github.com/perryflynn",
            "https://commons.wikimedia.org/wiki/File:Datacenter_Empty_Floor_(22166545884).jpg",
            "https://github.com/perryflynn/serverless.industries/commit/cc5a27a251b3c7815f33e676803e1803720b0e17"
        ],
        "noreferrer": [
            "https://einbeck.social/@christian",
            "https://github.com/perryflynn",
            "https://commons.wikimedia.org/wiki/File:Datacenter_Empty_Floor_(22166545884).jpg"
        ],
        "noopener": [
            "https://einbeck.social/@christian",
            "https://github.com/perryflynn",
            "https://commons.wikimedia.org/wiki/File:Datacenter_Empty_Floor_(22166545884).jpg"
        ],
        "license": [
            "http://creativecommons.org/licenses/by-nc/4.0/"
        ]
    },
    "rel-urls": {
        "https://serverless.industries/css/main.css?commit=cc5a27a251b3c7815f33e676803e1803720b0e17": {
            "rels": [
                "stylesheet"
            ]
        },
        "https://serverless.industries/css/icons.css?commit=cc5a27a251b3c7815f33e676803e1803720b0e17": {
            "rels": [
                "stylesheet"
            ]
        },
        "https://intercom.serverless.industries/webmentions/endpoint": {
            "rels": [
                "webmention"
            ]
        },
        "https://intercom.serverless.industries/pingback/endpoint": {
            "rels": [
                "pingback"
            ]
        },
        "https://intercom.serverless.industries/auth/relme/auth": {
            "rels": [
                "authorization_endpoint"
            ]
        },
        "https://intercom.serverless.industries/auth/relme/token": {
            "rels": [
                "token_endpoint"
            ]
        },
        "https://intercom.serverless.industries/auth/relme/metadata": {
            "rels": [
                "indieauth-metadata"
            ]
        },
        "https://intercom.serverless.industries/micropub": {
            "rels": [
                "micropub"
            ]
        },
        "https://serverless.industries/2024/05/31/digital-cinema.en.html": {
            "rels": [
                "canonical"
            ]
        },
        "https://serverless.industries/feed/frontpage.xml": {
            "title": "Serverless Industries Posts RSS",
            "type": "application/rss+xml",
            "rels": [
                "alternate"
            ]
        },
        "https://serverless.industries/opensearch.xml": {
            "title": "serverless.industries",
            "type": "application/opensearchdescription+xml",
            "rels": [
                "search"
            ]
        },
        "https://serverless.industries/favicon/favicon-96x96.png?commit=cc5a27a251b3c7815f33e676803e1803720b0e17": {
            "type": "image/png",
            "rels": [
                "icon"
            ]
        },
        "https://serverless.industries/favicon/favicon.svg?commit=cc5a27a251b3c7815f33e676803e1803720b0e17": {
            "type": "image/svg+xml",
            "rels": [
                "icon"
            ]
        },
        "https://serverless.industries/favicon.ico?commit=cc5a27a251b3c7815f33e676803e1803720b0e17": {
            "rels": [
                "icon",
                "shortcut"
            ]
        },
        "https://serverless.industries/favicon/apple-touch-icon.png?commit=cc5a27a251b3c7815f33e676803e1803720b0e17": {
            "rels": [
                "apple-touch-icon"
            ]
        },
        "https://serverless.industries/site.webmanifest?commit=cc5a27a251b3c7815f33e676803e1803720b0e17": {
            "rels": [
                "manifest"
            ]
        },
        "https://einbeck.social/@christian": {
            "text": "\n                \n                @christian@einbeck.social\n            ",
            "rels": [
                "external",
                "me",
                "nofollow",
                "noopener",
                "noreferrer"
            ]
        },
        "https://github.com/perryflynn": {
            "text": "\n                    \n                    perryflynn\n                ",
            "rels": [
                "external",
                "me",
                "nofollow",
                "noopener",
                "noreferrer"
            ]
        },
        "http://creativecommons.org/licenses/by-nc/4.0/": {
            "text": "\n                    \n                ",
            "rels": [
                "license"
            ]
        },
        "https://commons.wikimedia.org/wiki/File:Datacenter_Empty_Floor_(22166545884).jpg": {
            "text": "Carl Lender (CC BY 2.0)",
            "rels": [
                "external",
                "nofollow",
                "noopener",
                "noreferrer"
            ]
        },
        "https://github.com/perryflynn/serverless.industries/commit/cc5a27a251b3c7815f33e676803e1803720b0e17": {
            "text": "cc5a27a2",
            "rels": [
                "external",
                "nofollow"
            ]
        },
        "https://chaos.social/@perryflynn": {
            "text": "Mastodon via chaos.social",
            "rels": [
                "me"
            ]
        },
        "https://git.brickburg.de/christian": {
            "text": "Brickburg GitLab Profile",
            "rels": [
                "me"
            ]
        }
    },
    "debug": {
        "package": "https://packagist.org/packages/mf2/mf2",
        "source": "https://github.com/indieweb/php-mf2",
        "version": "v0.5.0",
        "note": [
            "This output was generated from the php-mf2 library available at https://github.com/indieweb/php-mf2",
            "Please file any issues with the parser at https://github.com/indieweb/php-mf2/issues",
            "Using the Masterminds HTML5 parser"
        ]
    }
}