Convert a SID to String with Java

Blog post featured image

A Security Identifier (SID) is a unique identifier widely used in Microsoft’s systems, such as identifying users within Windows or within an Active Directory. The SID, a binary value of variable length, can also be represented as a string. This conversion is facilitated by the function ConvertSidToStringSid, provided by the Advapi32.dll library, exclusive to Windows.

Therefore, to perform this conversion, one can either use Advapi32.dll (Windows only) or rewrite the conversion algorithm. Having this conversion implemented in a specific language, like Java, makes it universally usable. After extensive research, I found that most available implementations were incorrect, despite generally working. Hence, I decided to create my own Java implementation, rigorously tested for accuracy.

A string SID follows this format:

S-1-IdentifierAuthority-SubAuthority1-SubAuthority2-...-SubAuthorityn

This syntax is formally defined using ABNF as follows:

SID= "S-1-" IdentifierAuthority 1*SubAuthority
IdentifierAuthority= IdentifierAuthorityDec / IdentifierAuthorityHex
  ; If the identifier authority is < 2^32, the
  ; identifier authority is represented as a decimal
  ; number
  ; If the identifier authority is >= 2^32,
  ; the identifier authority is represented in
  ; hexadecimal
IdentifierAuthorityDec =  1*10DIGIT
  ; IdentifierAuthorityDec, top level authority of a
  ; security identifier is represented as a decimal number
IdentifierAuthorityHex = "0x" 12HEXDIG
  ; IdentifierAuthorityHex, the top-level authority of a
  ; security identifier is represented as a hexadecimal number
SubAuthority= "-" 1*10DIGIT
  ; Sub-Authority is always represented as a decimal number
  ; No leading "0" characters are allowed when IdentifierAuthority
  ; or SubAuthority is represented as a decimal number
  ; All hexadecimal digits must be output in string format,
  ; pre-pended by "0x"

Each field corresponds to a specific byte group in the binary SID, as officially detailed.

The final Java algorithm I implemented is as follows:

public static String convertSidToStringSid(byte[] sid) {
  int offset, size;

  // sid[0] is the Revision, we allow only version 1, because it's the
  // only that exists right now.
  if (sid[0] != 1)
    throw new IllegalArgumentException("SID revision must be 1");

  StringBuilder stringSidBuilder = new StringBuilder("S-1-");

  // The next byte specifies the numbers of sub authorities (number of
  // dashes minus two)
  int subAuthorityCount = sid[1] & 0xFF;

  // IdentifierAuthority (6 bytes starting from the second) (big endian)
  long identifierAuthority = 0;
  offset = 2;
  size = 6;
  for (int i = 0; i < size; i++) {
    identifierAuthority |= (long) (sid[offset + i] & 0xFF) << (8 * (size - 1 - i));
    // The & 0xFF is necessary because byte is signed in Java
  }
  if (identifierAuthority < Math.pow(2, 32)) {
    stringSidBuilder.append(Long.toString(identifierAuthority));
  } else {
    stringSidBuilder.append("0x").append(
        Long.toHexString(identifierAuthority).toUpperCase());
  }

  // Iterate all the SubAuthority (little-endian)
  offset = 8;
  size = 4; // 32-bits (4 bytes) for each SubAuthority
  for (int i = 0; i < subAuthorityCount; i++, offset += size) {
    long subAuthority = 0;
    for (int j = 0; j < size; j++) {
      subAuthority |= (long) (sid[offset + j] & 0xFF) << (8 * j);
      // The & 0xFF is necessary because byte is signed in Java
    }
    stringSidBuilder.append("-").append(subAuthority);
  }

  return stringSidBuilder.toString();
}

This algorithm was extensively tested by generating numerous SIDs and comparing the results with those from the original Advapi32.dll conversion. For instance, the following test function was used:

private static void generateCombinations(byte[] sid, int offset) throws Exception {
  String convertedSid, convertedSid2;
  if (offset >= sid.length) {
    convertedSid = Advapi32Util.convertSidToStringSid(new PSID(sid));
    convertedSid2 = convertSidToStringSid(sid);
    if (!convertedSid.equals(convertedSid2)) {
      throw new Exception("Conversion Error: "
        + convertedSid2 + " instead of " + convertedSid);
    }
    return;
  }
  for (int i = 0; i <= 255; i += 255/3) {
    sid[offset] = (byte)(i & 0xFF);
    generateCombinations(sid, offset+1);
  }
}

You can download the complete code along with tests from the following link: https://gist.github.com/miromannino/04be6a64ea0b5f4d4254bb321e09d628