セキュリティ的に重要なデータを管理するためには、データの暗号化が重要な意味を持ちます。 これにより、そのデータは鍵を知るユーザー以外に複合化できなくなります。 しかし、このように暗号化されたデータがどのユーザーからでもアクセスできる場所に保存されていれば、 複合化はできないものの、データの中身を変更することができますし、 データそのものが削除されてしまうことも考えられます。 こうしたことから、重要なデータは暗号化されると共に、安全な場所に保存されるべきといえます。 LSAシークレットとは、システムとAdministratorsグループのみがアクセス可能な場所であり、 データが暗号化されて保存されることから、いくつかの重要なデータが格納されています。 LSAシークレットは、次のレジストリキーで表されます。
HKEY_LOCAL_MACHINE\SECURITY\Policy\SecretsSECURITYキーは既定でAdministratorsにアクセスを許可していないため、 レジストリエディタなどで上記キーを参照する場合は、 SECURITYキーのセキュリティ記述子を変更することになります。 上記キーには主に下記のようなサブキーが存在し、 これらがLSAシークレットに格納されるデータになります。
データを識別するキー | 意味 |
---|---|
L$xxx | ローカルコンピュータ上から取得可能なデータ。 リモートから取得しようとした場合は、アクセスが拒否される。 |
G$xxx | ドメインの全てのドメインコントローラ上で存在するデータ。 |
M$ | システムのみアクセスできるデータ。 |
NL$xxx | キャッシュドメインログオンで使用するユーザーアカウントやパスワード。 |
$MACHINE.ACC | コンピュータアカウントのパスワード。 |
DefaultPassword | 自動ログオン時に使用されるデフォルトのパスワード。 |
_SC_xxx | サービスが特定のユーザーとしてログオンする場合の、そのユーザーの認証情報。システムのみ取得可。 |
RasDialParamsxxx | ダイヤルアップ接続時に使用するパスワード。 |
xxxには任意の値が入りますが、キーによってはこの値が想像できる場合もあります。 たとえば、_SC_xxxのxxxにはサービスの名前が入り、 RasDialParamsxxxにはユーザーのSIDが入ります。 LSAシークレットに格納されたデータは、 Administratorsのメンバのユーザーとして動作していれば、 どのプロセスからでも取得することができるため、この点は注意してください。
LSAシークレットにデータを格納するには、LsaStorePrivateDataを呼び出します。
NTSTATUS LsaStorePrivateData( LSA_HANDLE PolicyHandle, PLSA_UNICODE_STRING KeyName, PLSA_UNICODE_STRING PrivateData );
PolicyHandleは、ポリシーオブジェクトのハンドルを指定します。 このハンドルには、POLICY_CREATE_SECRETアクセス権が割り当てられている必要があります。 KeyNameは、格納するデータを識別するためのキーを指定します。 PrivateDataは、LSAシークレットに格納したいデータを指定します。 NULLを指定した場合は、KeyNameで識別されるデータが削除されます。
LSAシークレットに格納されたデータを取得するには、LsaRetrievePrivateDataを呼び出します。
NTSTATUS LsaRetrievePrivateData( LSA_HANDLE PolicyHandle, PLSA_UNICODE_STRING KeyName, PLSA_UNICODE_STRING *PrivateData );
PolicyHandleは、ポリシーオブジェクトのハンドルを指定します。 このハンドルには、POLICY_GET_PRIVATE_INFORMATIONアクセス権が割り当てられている必要があります。 KeyNameは、取得したいデータを表すキーを指定します。 PrivateDataは、取得したデータを受け取る変数のアドレスを指定します。
今回のプログラムは、独自のデータをLSAシークレットに格納します。
#include <windows.h> #include <ntsecapi.h> BOOL StorePrivateData(LSA_HANDLE hPolicy, LPWSTR lpszKeyName, LPWSTR lpszPrivateData); BOOL RetrievePrivateData(LSA_HANDLE hPolicy, LPWSTR lpszKeyName, LPWSTR lpszPrivateData); int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow) { WCHAR szKeyName[] = L"L$MySecret"; WCHAR szPrivateData[] = L"sample-data"; WCHAR szRetriveData[1024]; NTSTATUS ns; LSA_HANDLE hPolicy; LSA_OBJECT_ATTRIBUTES objectAttributes; BOOL bStorePrivateData = TRUE; ZeroMemory(&objectAttributes, sizeof(LSA_OBJECT_ATTRIBUTES)); objectAttributes.Length = sizeof(LSA_OBJECT_ATTRIBUTES); ns = LsaOpenPolicy(NULL, &objectAttributes, POLICY_CREATE_SECRET | POLICY_GET_PRIVATE_INFORMATION, &hPolicy); if (LsaNtStatusToWinError(ns) != ERROR_SUCCESS) { MessageBox(NULL, TEXT("ポリシーオブジェクトのハンドルの取得に失敗しました。"), NULL, MB_ICONWARNING); return 0; } if (bStorePrivateData) { if (StorePrivateData(hPolicy, szKeyName, szPrivateData)) MessageBox(NULL, TEXT("LSAシークレットにデータを格納しました。"), TEXT("OK"), MB_OK); else MessageBox(NULL, TEXT("データの格納に失敗しました。"), NULL, MB_ICONWARNING); } else { if (RetrievePrivateData(hPolicy, szKeyName, szRetriveData)) MessageBox(NULL, szRetriveData, TEXT("OK"), MB_OK); else MessageBox(NULL, TEXT("データの取得に失敗しました。"), NULL, MB_ICONWARNING); } LsaClose(hPolicy); return 0; } BOOL StorePrivateData(LSA_HANDLE hPolicy, LPWSTR lpszKeyName, LPWSTR lpszPrivateData) { NTSTATUS ns; LSA_UNICODE_STRING lsaName; LSA_UNICODE_STRING lsaData; lsaName.Length = (USHORT)(lstrlenW(lpszKeyName) * sizeof(WCHAR)); lsaName.MaximumLength = lsaName.Length + sizeof(WCHAR); lsaName.Buffer = lpszKeyName; if (lpszPrivateData != NULL) { lsaData.Length = (USHORT)(lstrlenW(lpszPrivateData) * sizeof(WCHAR)); lsaData.MaximumLength = lsaData.Length + sizeof(WCHAR); lsaData.Buffer = lpszPrivateData; ns = LsaStorePrivateData(hPolicy, &lsaName, &lsaData); } else ns = LsaStorePrivateData(hPolicy, &lsaName, NULL); return LsaNtStatusToWinError(ns) == ERROR_SUCCESS; } BOOL RetrievePrivateData(LSA_HANDLE hPolicy, LPWSTR lpszKeyName, LPWSTR lpszPrivateData) { NTSTATUS ns; LSA_UNICODE_STRING lsaName; PLSA_UNICODE_STRING plsaData; lsaName.Length = (USHORT)(lstrlenW(lpszKeyName) * sizeof(WCHAR)); lsaName.MaximumLength = lsaName.Length + sizeof(WCHAR); lsaName.Buffer = lpszKeyName; ns = LsaRetrievePrivateData(hPolicy, &lsaName, &plsaData); if (LsaNtStatusToWinError(ns) != ERROR_SUCCESS) return FALSE; lstrcpynW(lpszPrivateData, plsaData->Buffer, (plsaData->Length / sizeof(WCHAR)) + 1); LsaFreeMemory(plsaData); return TRUE; }
szNameがデータを識別する名前を表し、szDataが格納されるデータを表しています。 よって、今回のプログラムを実行すると、 L$MySecretというキーがSecretsキーの下に作成され、そこにszDataのデータが暗号化されて格納されます。 キーの先頭にL$が付加されているため、データはローカルコンピュータからのみアクセスすることができます。
実際のところ、現在のWindowsではLSAシークレットの利用は推奨されていません。 重要なデータはCryptProtectDataなどのDPAPIで暗号化し、 アプリケーション自身でデータを適切に管理することが望まれています。 この1つの原因は、LSAシークレットに格納できるデータの数が4096個と少なく、 さらにこのうちの2048個がシステムによって予約されているためであると思われます。