EternalWindows
アカウント管理 / SIDの形式

SIDは、システム内で動作する単位を識別するためのセキュリティIDです。 SIDは、ユーザーやローカルグループ、ドメイングループ、ローカルコンピュータ、 ドメインコンピュータ、ドメインメンバーなどを表すことができ、 その実体は一意の可変長数値です。 ユーザーなどをSIDで表す理由は、そのユーザーを常に固定した値で表したいからです。 たとえば、ユーザーはユーザー名という形で一般的に表されていますが、 このユーザー名はときとして変更する場合があります。 仮にシステム内部でユーザーがユーザー名で表されていた場合、 ユーザー名を格納するオブジェクトの中身を一斉に変更しなければなりませんが、 オブジェクトがSIDを格納している場合は、こうした事態は起こることがありません。 ユーザー名が変更されても、SIDが表しているのはそのユーザーに変わりありませんから、 これまで通り同じSIDで同じユーザーを表すことができるわけです。

システム内部でSIDが使用されている関係上、 一部の関数ではユーザー名ではなくSIDを要求することがあります。 たとえば、特定のユーザーに対して特権を割り当てるLsaAddAccountRightsは、 ユーザーのSIDを受け取ることになっています。 こうした関数に対応するため、アプリケーションはユーザー名からSIDを取得するための方法を理解しておく必要があります。 次に、NetUserGetInfoを呼び出してSIDを取得する例を示します。

PUSER_INFO_4 pUserInfo;

if (NetUserGetInfo(NULL, szUserName, 4, (LPBYTE *)&pUserInfo) != NERR_Success)
	return 0;
	
// pUserInfo->usri4_user_sidがSIDを表す

NetUserGetInfoに情報レベル4を指定した場合は、USER_INFO_4構造体を使用することになります。 この構造体のusri4_user_sidにSIDが格納されているため、 これを参照することでszUserNameのユーザーのSIDを使用できます。 なお、SIDを取得するユーザーが現在のユーザーである場合は、 OpenProcessTokenでトークンを取得し、GetTokenInformationでSIDを取得する方法もよく使用されます。

ユーザーのSIDに限らず、グループやコンピュータのSIDを取得したい場合は、 LookupAccountNameを呼び出すことになります。

BOOL WINAPI LookupAccountName(
  LPCTSTR lpSystemName,
  LPCTSTR lpAccountName,
  PSID Sid,
  LPDWORD cbSid,
  LPTSTR ReferencedDomainName,
  LPDWORD cchReferencedDomainName,
  PSID_NAME_USE peUse
);

lpSystemNameは、アカウントの検索に使用するシステムの名前を指定します。 NULLを指定した場合は、ローカルコンピュータが参照されます。 lpAccountNameは、SIDを取得するアカウントの名前を指定します。 このアカウントにはコンピュータなども含まれます。 Sidは、SIDを受け取るバッファを指定します。 cbSidは、Sidのサイズを格納した変数のアドレスを指定します。 サイズが足りない場合は、必要なサイズが格納されます。 ReferencedDomainNameは、アカウントが見つかったドメインを受け取るバッファを指定します。 cchReferencedDomainNameは、ReferencedDomainNameのサイズを格納した変数のアドレスを指定します。 サイズが足りない場合は、必要なサイズが格納されます。 peUseは、SIDの種類を受け取る変数のアドレスを指定します。

SID_NAME_USE型として定義されている値を次に示します。

typedef enum _SID_NAME_USE {
  SidTypeUser             = 1,
  SidTypeGroup,
  SidTypeDomain,
  SidTypeAlias,
  SidTypeWellKnownGroup,
  SidTypeDeletedAccount,
  SidTypeInvalid,
  SidTypeUnknown,
  SidTypeComputer,
  SidTypeLabel 
} SID_NAME_USE, *PSID_NAME_USE;

SidTypeUserは、ユーザーのSIDであることを意味します。 SidTypeGroupは、グループのSIDであることを意味します。 SidTypeDomainは、ドメインSIDであることを意味します。 SidTypeAliasは、エイリアスSIDであることを意味します。 SidTypeWellKnownGroupは、既知のグループのSIDであることを意味します。 SidTypeDeletedAccountは、削除されたアカウントのSIDであることを意味します。 SidTypeInvalidは、無効なSIDであることを意味します。 SidTypeUnknownは、不明なSIDであることを意味します。 SidTypeComputerは、コンピュータのSIDであることを意味します。 SidTypeLabelは、整合性レベルのSIDであることを意味します。

SIDの実体は可変長数値ですが、 文字列の形式で表されることもよくあります。 次に、SIDのフォーマットを示します。

S−R−I−S・・・

S−文字列がテキストSIDであることを明示するため必ずSという文字になる。
R−SIDのリビジョンレベルであり、現在は1となっている。
I−識別子機関値と呼ばれ、SIDを発行したローカルシステムまたはドメインシステムを表す。
S−サブ機関値またはRID(相対識別子)と呼ばれ、複数存在することもある。

次の文字列は、Administratorsグループを表すSIDをテキストSIDで表現したものです。 上の表と照らし合わせて見てください。

S-1-5-32-544

先の表で示したように、それぞれの値がハイフンで区切られ、 最初のSという文字はテキストSIDを表しています。 その次の1という値はリビジョンレベルで、これも先の表で示したように1となっています。 5は識別子機関値を表し、残りの2つの値はサブ機関値であるため、複数存在しています。 識別子機関値が取り得る値を次に示します。

識別子機関値 定義 意味
0 SECURITY_NULL_SID_AUTHORITY NULLグループ(ユーザーなし)を示すSIDの作成時に使用される。
1 SECURITY_WORLD_SID_AUTHORITY 既知のグループであるEveryoneを示すSIDの作成時に使用される。
2 SECURITY_LOCAL_SID_AUTHORITY 既知のグループであるLOCALを示すSIDの作成時に使用される。
3 SECURITY_CREATOR_SID_AUTHORITY 既知のCreator Ownerで使用される。
4 SECURITY_NON_UNIQUE_AUTHORITY 一意に識別されないSIDを作成する時に使用される。 最初のサブ機関値は常に、0x15に対応する SECURITY_NON_UNIQUE_AUTHORITYに設定される。
5 SECURITY_NT_AUTHORITY システムが作成するユーザーやグループに使われる。

識別子機関値の後にはサブ機関値が続きますが、 これらの値にはそれほど深い意味があるわけではありません。 同一のSIDが作成されないように調整されているだけと考えて問題ないでしょう。

SIDを文字列形式に変換するには、ConvertSidToStringSidを呼び出します。

BOOL ConvertSidToStringSid(
  PSID Sid,
  LPTSTR *StringSid
);

Sidは、SIDを指定します。 StringSidは、文字列化されたSIDを受け取る変数のアドレスを指定します。 不要になった文字列は、LocalFreeで開放します。

今回のプログラムは、コンピュータのSIDと現在のユーザーのSIDを表示します。 ConvertSidToStringSidを呼び出すため、sddl.hをインクルードしています。

#include <windows.h>
#include <sddl.h>

BOOL ConvertNameToSid(LPTSTR lpszName, PSID *ppSid);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{	
	TCHAR  szUserName[256];
	TCHAR  szComputerName[256];
	TCHAR  szBuf[256];
	LPTSTR lpszUserSid;
	LPTSTR lpszComputerSid;
	DWORD  dwBufferSize;
	PSID   pSidUser;
	PSID   pSidComputer;

	dwBufferSize = sizeof(szUserName) / sizeof(TCHAR);
	GetUserName(szUserName, &dwBufferSize);
	ConvertNameToSid(szUserName, &pSidUser);

	dwBufferSize = sizeof(szComputerName) / sizeof(TCHAR);
	GetComputerName(szComputerName, &dwBufferSize);
	ConvertNameToSid(szComputerName, &pSidComputer);
	
	ConvertSidToStringSid(pSidUser, &lpszUserSid);
	ConvertSidToStringSid(pSidComputer, &lpszComputerSid);

	wsprintf(szBuf, TEXT("%s \n%s"), lpszUserSid, lpszComputerSid);
	MessageBox(NULL, szBuf, TEXT("ユーザーとコンピュータのSID"), MB_OK);

	LocalFree(pSidUser);
	LocalFree(pSidComputer);
	LocalFree(lpszUserSid);
	LocalFree(lpszComputerSid);
	
	return 0;
}

BOOL ConvertNameToSid(LPTSTR lpszName, PSID *ppSid)
{
	TCHAR        szDomainName[256];
	DWORD        dwSizeDomain = sizeof(szDomainName) / sizeof(TCHAR);
	DWORD        dwSizeSid = 0;
	SID_NAME_USE sidName;

	LookupAccountName(NULL, lpszName, NULL, &dwSizeSid, szDomainName, &dwSizeDomain, &sidName);

	*ppSid = (PSID)LocalAlloc(LPTR, dwSizeSid);

	return LookupAccountName(NULL, lpszName, *ppSid, &dwSizeSid, szDomainName, &dwSizeDomain, &sidName);
}

GetUserNameで取得したユーザー名とGetComputerNameで取得したコンピュータ名は、 それぞれConvertNameToSidという自作関数でSIDに変換されることになります。 この関数は、LookupAccountNameの呼び出しをラッピングしています。

BOOL ConvertNameToSid(LPTSTR lpszName, PSID *ppSid)
{
	TCHAR        szDomainName[256];
	DWORD        dwSizeDomain = sizeof(szDomainName) / sizeof(TCHAR);
	DWORD        dwSizeSid = 0;
	SID_NAME_USE sidName;

	LookupAccountName(NULL, lpszName, NULL, &dwSizeSid, szDomainName, &dwSizeDomain, &sidName);

	*ppSid = (PSID)LocalAlloc(LPTR, dwSizeSid);

	return LookupAccountName(NULL, lpszName, *ppSid, &dwSizeSid, szDomainName, &dwSizeDomain, &sidName);
}

LookupAccountNameの1回目の呼び出しでは、SIDを受け取るために必要なバッファのサイズが分からないため、 第3引数にNULLを指定し、第4引数に0を指定します。 このようにすれば、関数から制御が返ったときには、第4引数に必要なサイズが格納されています。 サイズを取得したら、SIDを受け取るためのバッファを確保し、 2回目のLookupAccountNameで実際にSIDを取得します。 このとき、第3引数には先ほど確保したバッファを指定しておきます。

SIDを取得したら、これをConvertSidToStringSidで文字列に変換し、 MessageBoxで表示しています。 結果を見たら分かるように、SIDの文字列形式はSを起点として始まり、 いくつかの数値がハイフンで繋がれた形になっています。 また、ユーザーとコンピュータのSIDは、 最後の数値の部分を除いて同じであることも分かります。 次節からは、この詳細について取り上げます。


戻る