Question

I'm writing a program in Java, using Spring-LDAP. I need to implement a method, which should search a user by SID. For this reason I use a filter like "&((objectClass=User)(objectSid="+sid+"))". The search doesn't work with sid in String format like "S-1-12-345677-5676743-223344-...".

Using Apache Directory Studio, I can query my AD LDAP database regulary using a filter like: (objectSid=\ff\01\03\04\1a\2b\...) successfully. Here is the objectSid in hexadecimal format.

Now, how to translate the SID from String to hexadecimal and vice versa in a program, in Java?

Was it helpful?

Solution

There's the convertSidToStringSid and convertStringSidToSid methods in the Advapi32Util class.

I'm not sure if that'll be the exact formats you need, but hopefully will get you closer.

And here's an blog post that describes the SID format which might be helpful.

OTHER TIPS

The solution suggested by ho1 (using the Advapi32Util) class has two limitations:

  1. It needes the JNA library.
  2. It only works when running on the Windows platform, because it uses a Windows-API.

Therefore, I have written a class that is pure Java, has no external dependencies and will work on all platforms. This class can convert security identifiers from their binary to their textual representation and vice-versa.

If you are using Spring-LDAP, you can also use the methods provided by the LdapUtils class, however those methods do not peform any checks on the format of the SID and thus might produce incorrect results or unexpected exceptions.

import java.nio.ByteBuffer;
import java.nio.ByteOrder;

public class SidConverter {

    private final static int MASK_8_BIT = 0xff;
    private final static long MASK_32_BIT = 0xffffffffL;
    private final static long MASK_48_BIT = 0xffffffffffffL;

    public static String bytesToString(byte[] bytes) {
        if (bytes.length < 8) {
            throw new IllegalArgumentException(
                    "Binary SID representation must have at least 8 bytes but passed byte array has only "
                            + bytes.length + " bytes.");
        }
        // The revision number is an unsigned 8-bit unsigned integer.
        int revision = bytes[0] & MASK_8_BIT;
        // The number of sub-authority parts is specified as an 8-bit unsigned
        // integer.
        int numberOfSubAuthorityParts = bytes[1] & MASK_8_BIT;
        if (bytes.length != 8 + numberOfSubAuthorityParts * 4) {
            throw new IllegalArgumentException(
                    "According to byte 1 of the SID it total length should be "
                            + (8 + 4 * numberOfSubAuthorityParts)
                            + " bytes, however its actual length is "
                            + bytes.length + " bytes.");
        }
        // The authority is a 48-bit unsigned integer stored in big-endian
        // format.
        long authority = ByteBuffer.wrap(bytes).getLong() & MASK_48_BIT;
        // The sub-authority consists of up to 255 32-bit unsigned integers in
        // little-endian format. The number of integers is specified by
        // numberOfSubAuthorityParts.
        int[] subAuthority = new int[numberOfSubAuthorityParts];
        ByteBuffer.wrap(bytes, 8, bytes.length - 8)
                .order(ByteOrder.LITTLE_ENDIAN).asIntBuffer().put(subAuthority);
        StringBuilder sb = new StringBuilder();
        sb.append("S-");
        sb.append(revision);
        sb.append("-");
        sb.append(authority);
        for (int subAuthorityPart : subAuthority) {
            sb.append("-");
            sb.append(subAuthorityPart & MASK_32_BIT);
        }
        return sb.toString();
    }

    public static byte[] stringToBytes(String sid) {
        if (!sid.startsWith("S-") && !sid.startsWith("s-")) {
            throw new IllegalArgumentException("Invalid SID \"" + sid
                    + "\": A valid SID must start with \"S-\".");
        }
        String[] parts = sid.split("-");
        if (parts.length < 3) {
            throw new IllegalArgumentException("Invalid SID \"" + sid
                    + "\": A valid SID must have at least two dashes.");
        }
        if (parts.length > MASK_8_BIT + 3) {
            throw new IllegalArgumentException("Invalid SID \"" + sid
                    + "\": A valid SID must not have more than 257 dashes.");
        }
        int revision;
        try {
            revision = Integer.parseInt(parts[1]);
        } catch (NumberFormatException e) {
            throw new IllegalArgumentException(
                    "Invalid revision part in SID \""
                            + sid
                            + "\": The revision must be an integer number between 0 and 255.");
        }
        if (revision < 0 || revision > MASK_8_BIT) {
            throw new IllegalArgumentException(
                    "Invalid revision part in SID \""
                            + sid
                            + "\": The revision must be an integer number between 0 and 255.");
        }
        int numberOfSubAuthorityParts = parts.length - 3;
        long authority;
        try {
            authority = Long.parseLong(parts[2]);
        } catch (NumberFormatException e) {
            throw new IllegalArgumentException(
                    "Invalid authority part in SID \""
                            + sid
                            + "\": The authority must be an integer number between 0 and 281474976710655.");
        }
        if (authority < 0 || authority > MASK_48_BIT) {
            throw new IllegalArgumentException(
                    "Invalid authority part in SID \""
                            + sid
                            + "\": The authority must be an integer number between 0 and 281474976710655.");
        }
        int[] subAuthority = new int[numberOfSubAuthorityParts];
        for (int i = 0; i < numberOfSubAuthorityParts; i++) {
            long subAuthorityPart;
            try {
                subAuthorityPart = Long.parseLong(parts[3 + i]);
            } catch (NumberFormatException e) {
                throw new IllegalArgumentException(
                        "Invalid sub-authority part in SID \""
                                + sid
                                + "\": The sub-authority parts must be integer numbers between 0 and 4294967295.");
            }
            if (subAuthorityPart < 0 || subAuthorityPart > MASK_32_BIT) {
                throw new IllegalArgumentException(
                        "Invalid sub-authority part in SID \""
                                + sid
                                + "\": The sub-authority parts must be integer numbers between 0 and 4294967295.");
            }
            subAuthority[i] = (int) subAuthorityPart;
        }
        byte[] bytes = new byte[8 + numberOfSubAuthorityParts * 4];
        // We have to write the authority first, otherwise it would overwrite
        // the revision and length bytes.
        ByteBuffer.wrap(bytes).putLong(authority);
        bytes[0] = (byte) revision;
        bytes[1] = (byte) numberOfSubAuthorityParts;
        ByteBuffer.wrap(bytes, 8, bytes.length - 8)
                .order(ByteOrder.LITTLE_ENDIAN).asIntBuffer().put(subAuthority);
        return bytes;
    }

}

use convertToString from InPlaceMsAdObjectSidValueEditor.java :

protected String convertToString( byte[] bytes )
{
    /*
     * The binary data structure, from http://msdn.microsoft.com/en-us/library/cc230371(PROT.10).aspx:
     *   byte[0] - Revision (1 byte): An 8-bit unsigned integer that specifies the revision level of the SID structure. This value MUST be set to 0x01.
     *   byte[1] - SubAuthorityCount (1 byte): An 8-bit unsigned integer that specifies the number of elements in the SubAuthority array. The maximum number of elements allowed is 15.
     *   byte[2-7] - IdentifierAuthority (6 bytes): A SID_IDENTIFIER_AUTHORITY structure that contains information, which indicates the authority under which the SID was created. It describes the entity that created the SID and manages the account.
     *               Six element arrays of 8-bit unsigned integers that specify the top-level authority 
     *               big-endian!
     *   and then - SubAuthority (variable): A variable length array of unsigned 32-bit integers that uniquely identifies a principal relative to the IdentifierAuthority. Its length is determined by SubAuthorityCount. 
     *              little-endian!
     */

    if ( bytes == null || bytes.length < 8 )
    {
        return Messages.getString( "InPlaceMsAdObjectSidValueEditor.InvalidSid" ); //$NON-NLS-1$
    }

    char[] hex = Hex.encodeHex( bytes );
    StringBuffer sb = new StringBuffer();

    // start with 'S'
    sb.append( 'S' );

    // revision
    int revision = Integer.parseInt( new String( hex, 0, 2 ), 16 );
    sb.append( '-' );
    sb.append( revision );

    // get count
    int count = Integer.parseInt( new String( hex, 2, 2 ), 16 );

    // check length
    if ( bytes.length != ( 8 + count * 4 ) )
    {
        return Messages.getString( "InPlaceMsAdObjectSidValueEditor.InvalidSid" ); //$NON-NLS-1$
    }

    // get authority, big-endian
    long authority = Long.parseLong( new String( hex, 4, 12 ), 16 );
    sb.append( '-' );
    sb.append( authority );

    // sub-authorities, little-endian
    for ( int i = 0; i < count; i++ )
    {
        StringBuffer rid = new StringBuffer();
        for ( int k = 3; k >= 0; k-- )
        {
            rid.append( hex[16 + ( i * 8 ) + ( k * 2 )] );
            rid.append( hex[16 + ( i * 8 ) + ( k * 2 ) + 1] );
        }

        long subAuthority = Long.parseLong( rid.toString(), 16 );
        sb.append( '-' );
        sb.append( subAuthority );
    }

    return sb.toString();
}

(from Apache Directory Studio)

Example Without JNA

Here's some nice compact code for converting SIDs without using JNA. It even has some "error checking" built in by returning NULL, EMPTY, or a zero-length array if something isn't quite right.

For SID to String:

/**
 * Converts Windows SID to a String. NULL input returns NULL.
 * Invalid byte array returns EMPTY.
 * @param sid SID as byte array.
 * @return SID as String.
 */
public static String convertSidToStr(byte[] sid) {
    if (sid==null) return null;
    if (sid.length<8 || sid.length % 4 != 0) return "";
    StringBuilder sb = new StringBuilder();
    sb.append("S-").append(sid[0]);
    int c = sid[1]; // Init with Subauthority Count.
    ByteBuffer bb = ByteBuffer.wrap(sid);
    // bb.order(ByteOrder.BIG_ENDIAN); // Not needed, default big endian.
    sb.append("-").append((long)bb.getLong() & 0XFFFFFFFFFFFFL);
    bb.order(ByteOrder.LITTLE_ENDIAN); // Now switch.
    for (int i=0; i<c; i++) { // Create Subauthorities.
        sb.append("-").append((long)bb.getInt() & 0xFFFFFFFFL);
    }        
    return sb.toString();    
}

For String to SID:

/**
 * Converts Windows SID String to byte array. NULL input returns NULL.
 * Invalid String returns zero-length byte array.
 * @param sid SID as String.
 * @return SID as byte array.
 */
public static byte[] convertStrToSid(String sid) {
    if (sid==null) return null;
    if (!sid.matches("^[sS]-\\d-\\d{1,13})"
            + "(?:-\\d{1,10})*$")) return new byte[0];
    String[] ss = sid.split("-");
    int c=ss.length-3; // Init with Subauthority Count.
    byte[] b=new byte[2+6+(c*4)];
    ByteBuffer bb = ByteBuffer.wrap(b);
    // bb.order(ByteOrder.BIG_ENDIAN); // Not needed, default big endian.
    bb.putLong(Long.parseLong(ss[2]));
    // Overlay bytes 0 and 1 with Revision and Identifier Authority.
    b[0]=(byte)Short.parseShort(ss[1]);
    b[1]=(byte)c;
    bb.order(ByteOrder.LITTLE_ENDIAN); // Now switch.
    for (int i=0; i<c; i++) { // Create Subauthorities.
        bb.putInt((int)Long.parseLong(ss[i+3]));
    }
    return b;    
}

For reference, see TechNet's Security Identifier Structure.

From http://miromannino.com/blog/convert-a-sid-to-string-with-java/

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();
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top