def BinarySIDtoStringSID(sid):
  """Converts a binary SID to its string representation.

  https://msdn.microsoft.com/en-us/library/windows/desktop/aa379597.aspx

  The byte representation of an SID is as follows:
    Offset  Length  Description
    00      01      revision
    01      01      sub-authority count
    02      06      authority (big endian)
    08      04      subauthority #1 (little endian)
    0b      04      subauthority #2 (little endian)
    ...

  Args:
    sid: A `bytes` instance for a SID to convert.

  Returns:
    A `unicode` representation of given SID.

  Raises:
    ValueError: If the binary SID is malformed.
  """
  precondition.AssertType(sid, bytes)

  # TODO: This seemingly no-op is actually not a no-op. The reason
  # is that `sid` might be either `bytes` from the future package or `str` (e.g.
  # a bytes literal on Python 2). This call ensures that we get a `bytes` object
  # with Python 3 semantics. Once GRR's codebase is ready to drop support for
  # Python 2 this line can be removed as indeed then it would be a no-op.
  sid = bytes(sid)

  if not sid:
    return u""

  str_sid_components = [sid[0]]
  # Now decode the 48-byte portion

  if len(sid) >= 8:
    subauthority_count = sid[1]

    identifier_authority = struct.unpack(">H", sid[2:4])[0]
    identifier_authority <<= 32
    identifier_authority |= struct.unpack(">L", sid[4:8])[0]
    str_sid_components.append(identifier_authority)

    start = 8
    for i in range(subauthority_count):
      authority = sid[start:start + 4]
      if not authority:
        break

      if len(authority) < 4:
        message = ("In binary SID '%s', component %d has been truncated. "
                   "Expected 4 bytes, found %d: (%s)")
        message %= (sid, i, len(authority), authority)
        raise ValueError(message)

      str_sid_components.append(struct.unpack("<L", authority)[0])
      start += 4

  return u"S-%s" % (u"-".join(map(str, str_sid_components)))