Thanks for your answer. Yes in WMI you find the partial key as usual but I also found that the office key information is located somewhere else (in C2R) and it is no more encrypted. But I need to further investigate. There are so many different way to install O 2019 ...
For reference it seems that with Office 2019 the key is no more encrypted in DigitalProductID It is now provided not encrypted in ProductKeys. If you have several office product installed the keys are concatenated separated by comma. This key is located here: SOFTWARE\Microsoft\Office\ClickToRun\Scenario\INSTALL The code to use looks like: Code: List<string> DetectedKeys = new List<string>(); string RegistrationPathName, DigitalIdPath; RegistryKey RegistryBase, registry; // if office is 32-bits we want the to use the 32-bit registry if (GetOfficeArch() == "x86") RegistryBase = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32); else RegistryBase = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64); // get keys for office 2010 (14.0), 2013 (15.0), 2016 (16.0) ... // get keys for office 2019 (placed inClickToRun ...) RegistrationPathName = @"SOFTWARE\Microsoft\Office\ClickToRun\Scenario\INSTALL"; using (registry = RegistryBase.OpenSubKey(RegistrationPathName, false)) { if (registry != null) { // keys for all product are concatenated (separated by comma) string keys = (string)registry.GetValue("ProductKeys"); foreach (string key in keys.Split(',').ToList<string>()) DetectedKeys.Add(key); } }
I don't know if this thread still alive or no.. But I'm trying to get the key for office 2013 Pro using the below powershell script I found over the internet. Though its mentioned that the code is for 2016 but I guess its only a game of proper offset selection. Script: Code: function Get-MSOfficeProductKey { param( [string[]]$computerName = "." ) $product = @() $hklm = 2147483650 $path = "SOFTWARE\Microsoft\Office" foreach ($computer in $computerName) { $wmi = [WMIClass]"\\$computer\root\default:stdRegProv" $subkeys1 = $wmi.EnumKey($hklm,$path) foreach ($subkey1 in $subkeys1.snames) { $subkeys2 = $wmi.EnumKey($hklm,"$path\$subkey1") foreach ($subkey2 in $subkeys2.snames) { $subkeys3 = $wmi.EnumKey($hklm,"$path\$subkey1\$subkey2") foreach ($subkey3 in $subkeys3.snames) { $subkeys4 = $wmi.EnumValues($hklm,"$path\$subkey1\$subkey2\$subkey3") foreach ($subkey4 in $subkeys4.snames) { if ($subkey4 -eq "digitalproductid") { $temp = "" | select ComputerName,ProductName,ProductKey $temp.ComputerName = $computer $productName = $wmi.GetStringValue($hklm,"$path\$subkey1\$subkey2\$subkey3","productname") $temp.ProductName = $productName.sValue $data = $wmi.GetBinaryValue($hklm,"$path\$subkey1\$subkey2\$subkey3","digitalproductid") $valueData = ($data.uValue)[52..67] # decrypt base24 encoded binary data $productKey = "" $chars = "BCDFGHJKMPQRTVWXY2346789" for ($i = 24; $i -ge 0; $i–-) { $r = 0 for ($j = 14; $j -ge 0; $j–-) { $r = ($r * 256) -bxor $valueData[$j] $valueData[$j] = [math]::Truncate($r / 24) $r = $r % 24 } $productKey = $chars[$r] + $productKey if (($i % 5) -eq 0 -and $i -ne 0) { $productKey = "-" + $productKey } } $temp.ProductKey = $productKey $product += $temp } } } } } } $product } I've trying all the different offset discussed in this thread but they all are returning wrong key(when verifying using OSPP.vbs script). Any Suggestions or help?
@abhinavp649 Spoiler Code: function Get-MSOfficeProductKey { param( [string[]]$computerName = "." ) $product = @() $hklm = 2147483650 $path = "SOFTWARE\Microsoft\Office" $keyOffset = 808 foreach ($computer in $computerName) { $wmi = [WMIClass]"\\$computer\root\default:stdRegProv" $subkeys1 = $wmi.EnumKey($hklm,$path) foreach ($subkey1 in $subkeys1.snames) { $subkeys2 = $wmi.EnumKey($hklm,"$path\$subkey1") foreach ($subkey2 in $subkeys2.snames) { $subkeys3 = $wmi.EnumKey($hklm,"$path\$subkey1\$subkey2") foreach ($subkey3 in $subkeys3.snames) { $subkeys4 = $wmi.EnumValues($hklm,"$path\$subkey1\$subkey2\$subkey3") foreach ($subkey4 in $subkeys4.snames) { if ($subkey4 -eq "digitalproductid") { $temp = "" | select ComputerName,ProductName,ProductKey $temp.ComputerName = $computer $productName = $wmi.GetStringValue($hklm,"$path\$subkey1\$subkey2\$subkey3","productname") $temp.ProductName = $productName.sValue $data = $wmi.GetBinaryValue($hklm,"$path\$subkey1\$subkey2\$subkey3","digitalproductid") $endOffset = ($data.uValue)[($keyOffset + 14)] $isWin8 = ($endOffset / 6) -and 1 $endOffset = ($endOffset -band 247) -bor (($isWin8 -band 2) * 4) $valueData = ($data.uValue)[($keyOffset)..($keyOffset + 13)] + $endOffset $productKey = "" $chars = "BCDFGHJKMPQRTVWXY2346789" for ($i = 24; $i -ge 0; $i–-) { $r = 0 for ($j = 14; $j -ge 0; $j–-) { $r = ($r * 256) -bxor $valueData[$j] $valueData[$j] = [math]::Truncate($r / 24) $r = $r % 24 } $productKey = $chars[$r] + $productKey $l = $r } if ($isWin8) { $productKey = $productKey.substring(1,$l) + 'N' + $productKey.substring(($l + 1), $productKey.length - ($l + 1)) } $productKey = $productKey.substring(0,5) + '-' + $productKey.substring(5,5) + '-' + $productKey.substring(10,5) + '-' + $productKey.substring(15,5) + '-' + $productKey.substring(20,5) $temp.ProductKey = $productKey $product += $temp } } } } } } $product }
@abbodi1406 I guess your code is not properly working as the key I got from your code is almost correct but not fully. This is what I'm getting from a third party key finding software: And this is what your code is calculating: Order of first group is different while in the second group there is a wrong word. Why is this happening?
This quick fix actually solved the issue. Can you please explain a bit why we were even using $isWin8 in the first place? does the DigitalProductID keeps check of the OS on which the Office is installed? or is it something entirely different?
I made few changes, now it properly work, see the first code N character has a special meaning in product keys since Windows 8 / Office 2013 (so $isWin8 cover both) evey key should have 1 N, and it should not be the last character
Awesome, now the code works perfectly and also thanks for such interesting information. I seriously googled many keys and found almost every key has a N in either 1st or 2nd group. But I also found 1 or 2 keys which had N at the last of the group. I guess those will be counted as Invalid?!
Hello, I was trying to convert this code into a C# console application but suck at doing bitwise operations. I traversed through the registry and found the data on which the operations will have to be performed but the later part of the powershell script about bitwise ANDing or XORing the values is a bit confusing for me to translate it to C#. Rest of the syntax is pretty same as that of C# with a mere difference of keywords. Can anyone provide a code for the same or if just help me out with the logical operations?
@abhinavp649 did you try this? https://forums.mydigitallife.net/threads/c-decrypt-office-2010-digital-product-id.28039/#post-462578
Thanks as always for reply. But yes, I tried that code and I've encountered a few code issues that prevented my application from compiling. The error prone section where I had issues was: Code: // Extract byte 52 to 67 inclusive. ArrayList hexPid = new ArrayList(); for (int i = keyStartIndex; i <= keyEndIndex; i++) { hexPid.Add(digitalProductId); } for (int i = decodeLength - 1; i >= 0; i--) { // Every sixth char is a separator. if ((i + 1) % 6 == 0) { decodedChars[i] = '-'; // I had to add '[i]' after decodedChars to avoid 'cannot implicitly convert type char to char[]' } else { // Do the actual decoding. int digitMapIndex = 0; for (int j = decodeStringLength - 1; j >= 0; j--) { int byteValue = (digitMapIndex << 8) | (byte)hexPid[j]; //<----Runtime InvalidCastException hexPid[j] = (byte)(byteValue / 24); digitMapIndex = byteValue % 24; decodedChars[j] = digits[digitMapIndex]; // had to add '[j]' after decodedChars to avoid 'cannot implicitly convert type char to char[]' } } } I've commented the lines where I made the changes or are error prone during runtime. I might be doing some very stupid mistake which I can't figure out yet and hence I'm seeking help.
@abhinavp649 Code: ArrayList hexPid = new ArrayList(); for (int i = keyStartIndex; i <= keyEndIndex; i++) { hexPid.Add(digitalProductId[i]); // missing [i] } for (int i = decodeLength - 1; i >= 0; i--) { // Every sixth char is a separator. if ((i + 1) % 6 == 0) { decodedChars[i] = '-'; } else { // Do the actual decoding. int digitMapIndex = 0; for (int j = decodeStringLength - 1; j >= 0; j--) { int byteValue = (digitMapIndex << 8) | hexPid[j]; hexPid[j] = (byte)(byteValue / 24); digitMapIndex = byteValue % 24; decodedChars[i] = digits[digitMapIndex]; // [i] not [j] } } } see Keys.cs from Microsoft Toolkit source Spoiler Code: /// <summary> /// Decode Data read in from a DigitalProductID /// </summary> /// <param name="digitalProductId">Byte array containing DigitalProductID data</param> /// <param name="keyStartIndex">Where in the byte array does the Product Key start</param> /// <returns>Product Key contained in the DigitalProductID</returns> private static string DecodeProductKey(IList<byte> digitalProductId, int keyStartIndex) { int keyEndIndex = keyStartIndex + 15; const int numLetters = 24; // Possible alpha-numeric characters in product key. char[] digits = {'B', 'C', 'D', 'F', 'G', 'H', 'J', 'K', 'M', 'P', 'Q', 'R', 'T', 'V', 'W', 'X', 'Y', '2', '3', '4', '6', '7', '8', '9'}; // Check if Windows 8/Office 2013 Style Key (Can contain the letter "N") int containsN = (digitalProductId[keyStartIndex + 14] / 6) & 1; digitalProductId[keyStartIndex + 14] = (byte)((digitalProductId[keyStartIndex + 14] & 0xF7) | ((containsN & 2) * 4)); // Length of decoded product key const int decodeLength = 29; // Length of decoded product key in byte-form. // Each byte represents 2 chars. const int decodeStringLength = 15; // Array of containing the decoded product key. char[] decodedChars = new char[decodeLength]; // Extract byte 52 to 67 inclusive. List<byte> hexPid = new List<byte>(); for (int i = keyStartIndex; i <= keyEndIndex; i++) { hexPid.Add(digitalProductId[i]); } for (int i = decodeLength - 1; i >= 0; i--) { // Every sixth char is a separator. if ((i + 1) % 6 == 0) { decodedChars[i] = '-'; } else { // Do the actual decoding. int digitMapIndex = 0; for (int j = decodeStringLength - 1; j >= 0; j--) { int byteValue = (digitMapIndex << 8) | hexPid[j]; hexPid[j] = (byte)(byteValue / numLetters); digitMapIndex = byteValue % numLetters; decodedChars[i] = digits[digitMapIndex]; } } } // Remove first character and put N in the right place if (containsN != 0) { int firstLetterIndex = 0; for (int index = 0; index < numLetters; index++) { if (decodedChars[0] != digits[index]) { continue; } firstLetterIndex = index; break; } string keyWithN = new string(decodedChars); keyWithN = keyWithN.Replace("-", string.Empty).Remove(0, 1); keyWithN = keyWithN.Substring(0, firstLetterIndex) + "N" + keyWithN.Remove(0, firstLetterIndex); keyWithN = keyWithN.Substring(0, 5) + "-" + keyWithN.Substring(5, 5) + "-" + keyWithN.Substring(10, 5) + "-" + keyWithN.Substring(15, 5) + "-" + keyWithN.Substring(20, 5); return keyWithN; } return new string(decodedChars); }
The solutions were everywhere. I realize it was me who wasn't looking properly in the first place . I didn't knew the source for the toolkit I used a lot has a source available to learn . Thank you once again.