diff --git a/DRMDecryptSmooth/App.config b/DRMDecryptSmooth/App.config new file mode 100644 index 0000000..88fa402 --- /dev/null +++ b/DRMDecryptSmooth/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/DRMDecryptSmooth/DRMDecryptSmooth.csproj b/DRMDecryptSmooth/DRMDecryptSmooth.csproj new file mode 100644 index 0000000..b780f1b --- /dev/null +++ b/DRMDecryptSmooth/DRMDecryptSmooth.csproj @@ -0,0 +1,60 @@ + + + + + Debug + AnyCPU + {F63FEE07-8040-4F89-8166-E7ACBE724877} + Exe + Properties + DRMDecryptSmooth + DRMDecryptSmooth + v4.5.2 + 512 + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DRMDecryptSmooth/Program.cs b/DRMDecryptSmooth/Program.cs new file mode 100644 index 0000000..be80c8f --- /dev/null +++ b/DRMDecryptSmooth/Program.cs @@ -0,0 +1,167 @@ +/* + +Copyright (c) Microsoft Corporation. All rights reserved. +Licensed under the MIT License. + + */ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; +using System.Xml.Linq; + +namespace DRMDecryptSmooth +{ + // This application demonstrates how to use bento4 to decrypt a Smooth Streaming PlayReady asset. + + class Program + { + // Location of mp4decrypt from Bento4 + const string mp4decrypt = @"C:\Temp\Bento4-SDK-1-5-1-620.x86-microsoft-win32-vs2010\bin\mp4decrypt.exe"; + + static void Main(string[] args) + { + // MANTATORY: Please insert here the key seed in base64 format. Example : "XVBovsmzhP9gRIZxWfFta3VVRPzVEWmJsazEJ46I" + string keySeed = ""; + + // MANTATORY: Please insert here the key id in guid format. Example : "8d080fae-2b52-4427-9f2c-f2ea93141b45" + string keyId = ""; + + // Folder where is the asset to process + string inputAsset = @"C:\source"; + + // Folder where the decrypted asset will be copied (folder must exist) + string outputAsset = inputAsset + @"\decrypted"; + Directory.CreateDirectory(outputAsset); + + // let's calculate the key + string key = ByteArrayToHexString(GeneratePlayReadyContentKey(Convert.FromBase64String(keySeed), new Guid(keyId))); + + var files = Directory.GetFiles(inputAsset); + + foreach (var filePath in files) + { + string ext = Path.GetExtension(filePath).ToLower(); + + if (ext == ".isma" || ext == ".ismv") + { + // we need to decrypt the PIFF file + string keyArg = " --key {0}:{1}"; + string arguments = "--show-progress"; + var piffFilesCount = files.Where(f => Path.GetExtension(f).ToLower() == ".isma" || Path.GetExtension(f).ToLower() == ".ismv").Count(); + for (int i = 1; i <= piffFilesCount; i++) + { + arguments += string.Format(keyArg, i, key); + } + arguments += " " + filePath + " " + outputAsset + @"\" + Path.GetFileName(filePath); + Console.WriteLine($"Decrypting {Path.GetFileName(filePath)}"); + ExecuteCommandSync(mp4decrypt + " " + arguments); + } + else if (ext == ".ismc") + { + // let remove the Playready part in the manifest + var manifest = XDocument.Load(filePath); + var smoothmedia = manifest.Element("SmoothStreamingMedia"); + var videotrack = smoothmedia.Element("Protection"); + videotrack.Remove(); + Console.WriteLine($"Modifying {Path.GetFileName(filePath)}"); + manifest.Save(outputAsset + @"\" + Path.GetFileName(filePath)); + } + else + { + File.Copy(filePath, outputAsset + @"\" + Path.GetFileName(filePath), true); + } + } + } + + static public void ExecuteCommandSync(object command) + { + try + { + // create the ProcessStartInfo using "cmd" as the program to be run, + // and "/c " as the parameters. + // Incidentally, /c tells cmd that we want it to execute the command that follows, + // and then exit. + System.Diagnostics.ProcessStartInfo procStartInfo = + new System.Diagnostics.ProcessStartInfo("cmd", "/c " + command); + + // The following commands are needed to redirect the standard output. + // This means that it will be redirected to the Process.StandardOutput StreamReader. + procStartInfo.RedirectStandardOutput = true; + procStartInfo.UseShellExecute = false; + // Do not create the black window. + procStartInfo.CreateNoWindow = true; + // Now we create a process, assign its ProcessStartInfo and start it + System.Diagnostics.Process proc = new System.Diagnostics.Process(); + proc.StartInfo = procStartInfo; + proc.Start(); + // Get the output into a string + string result = proc.StandardOutput.ReadToEnd(); + // Display the command output. + Console.WriteLine(result); + } + catch () + { + // Log the exception + } + } + + public static byte[] GeneratePlayReadyContentKey(byte[] keySeed, Guid keyId) + { + const int DRM_AES_KEYSIZE_128 = 16; + byte[] contentKey = new byte[DRM_AES_KEYSIZE_128]; + // + // Truncate the key seed to 30 bytes, key seed must be at least 30 bytes long. + // + byte[] truncatedKeySeed = new byte[30]; + Array.Copy(keySeed, truncatedKeySeed, truncatedKeySeed.Length); + // + // Get the keyId as a byte array + // + byte[] keyIdAsBytes = keyId.ToByteArray(); + // + // Create sha_A_Output buffer. It is the SHA of the truncatedKeySeed and the keyIdAsBytes + // + SHA256Managed sha_A = new SHA256Managed(); + sha_A.TransformBlock(truncatedKeySeed, 0, truncatedKeySeed.Length, truncatedKeySeed, 0); + sha_A.TransformFinalBlock(keyIdAsBytes, 0, keyIdAsBytes.Length); + byte[] sha_A_Output = sha_A.Hash; + // + // Create sha_B_Output buffer. It is the SHA of the truncatedKeySeed, the keyIdAsBytes, and + // the truncatedKeySeed again. + // + SHA256Managed sha_B = new SHA256Managed(); + sha_B.TransformBlock(truncatedKeySeed, 0, truncatedKeySeed.Length, truncatedKeySeed, 0); + sha_B.TransformBlock(keyIdAsBytes, 0, keyIdAsBytes.Length, keyIdAsBytes, 0); + sha_B.TransformFinalBlock(truncatedKeySeed, 0, truncatedKeySeed.Length); + byte[] sha_B_Output = sha_B.Hash; + // + // Create sha_C_Output buffer. It is the SHA of the truncatedKeySeed, the keyIdAsBytes, + // the truncatedKeySeed again, and the keyIdAsBytes again. + // + SHA256Managed sha_C = new SHA256Managed(); + sha_C.TransformBlock(truncatedKeySeed, 0, truncatedKeySeed.Length, truncatedKeySeed, 0); + sha_C.TransformBlock(keyIdAsBytes, 0, keyIdAsBytes.Length, keyIdAsBytes, 0); + sha_C.TransformBlock(truncatedKeySeed, 0, truncatedKeySeed.Length, truncatedKeySeed, 0); + sha_C.TransformFinalBlock(keyIdAsBytes, 0, keyIdAsBytes.Length); + byte[] sha_C_Output = sha_C.Hash; + for (int i = 0; i < DRM_AES_KEYSIZE_128; i++) + { + contentKey[i] = Convert.ToByte(sha_A_Output[i] ^ sha_A_Output[i + DRM_AES_KEYSIZE_128] + ^ sha_B_Output[i] ^ sha_B_Output[i + DRM_AES_KEYSIZE_128] + ^ sha_C_Output[i] ^ sha_C_Output[i + DRM_AES_KEYSIZE_128]); + } + return contentKey; + } + + public static string ByteArrayToHexString(byte[] bytes) + { + return string.Join(string.Empty, Array.ConvertAll(bytes, b => b.ToString("X2"))); + } + } +}