DrmDecryptSmooth/DRMDecryptSmooth/Program.cs

168 lines
7.3 KiB
C#

/*
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")));
}
}
}