Tuesday, November 12, 2019

CSharp : Convert a GUID to a Compressed GUID

Here's how to compress and decompress Packed GUIDs or some people refer to them as Compressed GUIDs in C-Sharp

Input GUID         {12345678-ABCD-EFAB-1234-ABCDEFABCDEF}
Packed GUID is 87654321DCBABAFE2143BADCFEBADCFE

A Packed GUID format is loosely defined  as having a length of 32 characters, where the GUID is rearranged and the braces '{' and dashes '-' are removed.


Note: A "Compressed GUID" is a intermediate format, that rearranges the GUID and is Base85 encoded (using a restricted set) and has a length of 21 characters long and used to create Darwin Descriptors.  
A Darwin Descriptor is an encoded string and when decoded produces a combination of the ProductCode, Feature & ComponentId(s) and generally used for Windows installer, because it provides feature resilience through a mechanism called COM Advertising
Here's an example of a Darwin Descriptor, which you may have though was a virus entry in your registry!
"w_1^VX!!!!!!!!!MKKSkEXCELFiles>tW{~$4Q]c@II=l2xaTO5Z" 
decoded yields a GUID, feature name, and a GUID.
{91120000-0030-0000-0000-0000000ff1ce}EXCELFiles{0638c49d-bb8b-4cd1-b191-052e8f325736}


To decode Darwin Descriptors that are in the registry, use RegtoText tool to decode all values en masse (free demo available). 


In example code below, I use "compressed GUID" to describe Packed GUID. 


using System;
using System.Collections.Generic; 

//
public class Program
{
 public static bool IsHex(IEnumerable<char> chars)
 {
  bool isHex; 
  foreach(var c in chars)
  {
   isHex = ((c >= '0' && c <= '9') || 
      (c >= 'a' && c <= 'f') || 
      (c >= 'A' && c <= 'F'));

   if(!isHex)
    return false;
  }
  return true;
    }
 public static void Main()
 {
     //Source - http://www.hanselman.com/blog/BATCHFILEVOODOODetermineIfMultipleAndWhichVersionsOfAnMSIinstalledProductAreInstalledUsingUpgradeCode.aspx
     //like to but to bulky - https://docs.microsoft.com/en-us/dotnet/standard/base-types/how-to-define-and-use-custom-numeric-format-providers
     //https://installpac.wordpress.com/2008/03/31/packed-guids-darwin-descriptors-and-windows-installer-reference-counting/
   
        //PROTO    
     //{12345678-ABCD-WXYZ-1234-ABCDEFGHIJKL}
     //should be-> 87654321DCBAZYXW2134BADCFEHGJILK
     //string inStrGUID = "{12345678-ABCD-WXYZ-1234-ABCDEFGHIJKL}"; //cannot use this contrived GUID has hex values above > (A-F) 
     
     string inStrGUID = "{12345678-ABCD-EFAB-1234-ABCDEFABCDEF}"; 
     string expectedCompressedGUID = "87654321DCBABAFE2143BADCFEBADCFE";
     //https://docs.microsoft.com/en-us/dotnet/api/system.guid.parse?view=netframework-4.7.2 - N has no dashes
     string outputFormat = "N"; //outputFormat can be N, D, B, P - N has no dashes
     string outCompressedGuid = ""; 
     string outDecompressedGuid = ""; 
     
     Guid inGuid = new Guid();
     Guid outGuid = new Guid(); 
     Guid outDCGuid = new Guid(); 
  
     bool isValidGuid = Guid.TryParse(inStrGUID, out inGuid); 
  
     Console.WriteLine("Input GUID " + inStrGUID+ " is valid? "+isValidGuid); 
        
     if (isValidGuid) {
      
      string raw = inGuid.ToString("N"); //N for no dashes
      char[] aRaw = raw.ToCharArray();
      //compressed format reverses 11 byte sequences of the original guid
      int[] revs
       = new int[]{8, 4, 4, 2, 2, 2, 2, 2, 2, 2, 2};
      int pos = 0;
      for (int i = 0; i < revs.Length; i++)
      {
       Array.Reverse(aRaw, pos, revs[i]);
       pos += revs[i];
      }
      string newstrGuid = new string(aRaw);
      
      bool isoutValidGuid = Guid.TryParse(newstrGuid, out outGuid); 
      
      if ( isoutValidGuid ) {
       
      //GUID in registry are all caps.
      outCompressedGuid = outGuid.ToString(outputFormat).ToUpper(); 
      
       Console.WriteLine("out  CGUID "+   outGuid.ToString("B").ToUpper()+" before full compression (removal of dashes) here for readability"); //for readability
      
       Console.WriteLine("\nCompressed guid is "+ outCompressedGuid );
         
       
       if (outCompressedGuid == expectedCompressedGUID) 
        Console.WriteLine("Matched expectations."); 
      else
       Console.WriteLine("Did not met expectations.");   
       
       
      } else
       Console.WriteLine("Failed to compress GUID."); 
     }
     
     Console.WriteLine("\nInput Compressed GUID " + outCompressedGuid); 
  
   
   char[] chrArrCGUID = outCompressedGuid.ToCharArray();
     
     //Here's a typical GUID Compressed in a Registry Key       
     //what if ? S-1-5-21-xxxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxx - split on new char[] {' ','-'} check any that are length 32 
  
     //Expand/Decompress/Decrypt Compressed GUID
     if (outCompressedGuid.Length == 32 && Program.IsHex(chrArrCGUID)==true){ //validate potential Compressed GUID
         
      int[] reversalidxs= new int[]{8, 4, 4, 2, 2, 2, 2, 2, 2, 2, 2};
       
      int pos = 0;
      for (int i = 0; i < reversalidxs.Length; i++)
      {
       Array.Reverse(chrArrCGUID, pos, reversalidxs[i]);
       pos += reversalidxs[i];
      }
      string newstrGuid = new string(chrArrCGUID);
      
      bool isoutValidDCGuid = Guid.TryParse(newstrGuid, out outDCGuid );
      
      if ( isoutValidDCGuid ) {
       
       //GUID in registry are all caps.
       outDecompressedGuid = outDCGuid.ToString("B").ToUpper(); 

       //Console.WriteLine("\nInput Compressed GUID "+   outDCGuid.ToString("B").ToUpper()+" this line for comparison only"); //for readability
       Console.WriteLine("Output Decomprsd GUID "+   outDCGuid.ToString("N").ToUpper()+" this line for comparison only"); //for readability

       Console.WriteLine("\nDecompressed compressed guid is "+ outDecompressedGuid );
     }
     
     }
     else 
     Console.WriteLine("Failed to decompressed CGUID.");  
  
 }
 
}

1 comment:

  1. var ng = Guid.NewGuid();

    var uncompressedLength = ng.ToString().Length; // Uncompressed 36;
    var compressedLength = ng.ToString("N").Length; // length 32
    var compressedStringGuid = ng.ToString("N"); // Compressed Guid

    ReplyDelete