Click here to Skip to main content
12,758,605 members (33,680 online)
Click here to Skip to main content
Add your own
alternative version


20 bookmarked
Posted 18 May 2005

Securing a Registry Key ACL using .NET

, 19 May 2005
Rate this:
Please Sign up or sign in to vote.
Managing ACLs and securing registry keys using .NET.


The .NET API provided by Microsoft allows you to create, modify, and delete keys in the Windows registry easily, all from managed code. The problem comes in when you want to control access to the registry keys, which, until the release of .NET 2.0, must be performed by Win32 API calls. For example, you may store secure information for your application in the registry and you want to be sure that information is only available to the users of that application and not read or modified by other users. To do so, your application must modify the Access Control List (ACL) for that registry key.

This article demonstrates the process of looking up a Security Identifier (SID) for either a user account or a built-in group (like Administrators), creating Access Control Entities (ACE) to define the access rights for each SID, and adding those ACEs to an ACL which is then used to create a registry key with restricted access.


My first shot at this task attempted to perform the entire process in C#, using P/Invoke to make calls to the Win32 APIs. I gave up and moved to Managed C++ after unsuccessfully troubleshooting a NullPointerException in the call to SetEntriesInAcl(). The majority of this code will work in unmanaged C++ or C just as well; the only .NET specific code deals with marshalling the string parameter from .NET to an LPCTSTR for the Win32 calls and a few lines of console output.

Using the code

This code is intended to demonstrate a number of functions, including looking up a SID based on username or built in group, creating an ACL, and applying that ACL to a registry key. You may only want to lookup a SID, or you may want to apply your ACL to a file. The process is generally the same, but you will need to deviate from the code here to meet your application needs. To apply an ACL to a file rather than a registry key, instead of passing the ACL to a SECURITY_ATTRIBUTE that is passed to RegCreateKeyEx, you will pass the ACL to a SECURITY_DESCRIPTOR to pass to SetFileSecurity.

The code initially takes in a .NET string and creates an LPCTSTR which is necessary for the Win32 API calls. Next, a call to LookupAccountName is used to get the SID for that user. To retrieve the SID for a well known group (Administrators, in this case), a call to AllocateAndInitializeSid is used.

Next, the code creates an array of EXPLICIT_ACCESS structs where the access is defined for each SID. These structures will equate to the ACEs in the final ACL. The order of the structs in the array is important, since an ACL is evaluated from top to bottom. That is, if a user is explicitly given read only access in the first structure, and a group to which that user belongs is given full control in a later struct, the first structure will take precedence and the user will have read only access.

Once the EXPLICIT_ACCESS array is created, a call is made to SetEntriesInAcl to actually create the ACL. This method also accepts a pointer to another ACL, if your intention is to modify a current ACL rather than creating a new one. After the ACL is created, it's just a few more API calls to InitializeSecurityDescriptor to create a SECURITY_DESCRIPTOR struct and SetSecurityDescriptorDacl to set the ACL to the SECURITY_DESCRIPTOR. This SECURITY_DESCRIPTOR is the one you would pass in a call to SetFileSecurity if you wanted to set the ACL on a file.

Once the SECURITY_DESCRIPTOR is ready, it can be set to a SECURITY_ATTRIBUTES struct. This SECURITY_ATTRIBUTES structure is passed by reference to the RegCreateKeyEx method. Note that you will need to open any higher level registry keys such as the the SOFTWARE key to access the location where the new registry key will be created. Finally, there is a labeled section of code called Cleanup: that releases any resources that were allocated during the method's execution.

Managed C++ source file

<PRE lang=c++>// This is the main DLL file. #include "stdafx.h" #include "SecureRegistry.h" namespace SecureRegistry { long KeyManager::CreateRestrictedRegKey(String* account) { // Convert .NET string type to LPCTSTR (remember to free LPCTSTR after use) LPCTSTR accountName = (const char *) (Marshal::StringToHGlobalAnsi(account)).ToPointer(); DWORD dwRes, dwDisposition; PSID pAdminSID = NULL; PSID pUserSID = NULL; PACL pACL = NULL; PSECURITY_DESCRIPTOR pSD = NULL; EXPLICIT_ACCESS ea[2]; SID_IDENTIFIER_AUTHORITY SIDAuthNT = SECURITY_NT_AUTHORITY; SECURITY_ATTRIBUTES sa; DWORD cbSid = 0; DWORD dwRefDomain = NULL; SID_NAME_USE peUse; HKEY softwareKey = NULL; HKEY companyKey = NULL; HKEY securedKey = NULL; LONG lRes = 0; // Lookup SID for specific user account that is passed in to this method if(!LookupAccountName(NULL,accountName,NULL, &cbSid,NULL,&dwRefDomain,&peUse)) { pUserSID = LocalAlloc(LPTR,cbSid); TCHAR refDomain[128]; if(!LookupAccountName(NULL,accountName,pUserSID, &cbSid,refDomain,&dwRefDomain,&peUse)) { System::Console::WriteLine("Unable to initialize" " User SID - {0}", GetLastError().ToString()); Marshal::FreeHGlobal(System::IntPtr((void*)accountName)); return 0; } } // Frees LPCTSTR Marshal::FreeHGlobal(System::IntPtr((void*)accountName)); // Validate SID before continuing if(!IsValidSid(pUserSID)) { System::Console::WriteLine("Invalid UserSID"); goto Cleanup; } // Lookup a well known group, Administrators if(!AllocateAndInitializeSid(&SIDAuthNT, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0,0,0,0,0,0,&pAdminSID)) { System::Console::WriteLine("Unable to initialize" " Admin SID - {0}", GetLastError().ToString()); goto Cleanup; } // Create two EXPLICIT_ATTRIBUTES to be used in the ACL // Administrators will have full access in this ACL ea[0].grfAccessPermissions = KEY_ALL_ACCESS; ea[0].grfAccessMode = SET_ACCESS; ea[0].grfInheritance = NO_INHERITANCE; ea[0].Trustee.TrusteeForm = TRUSTEE_IS_SID; ea[0].Trustee.TrusteeType = TRUSTEE_IS_GROUP; ea[0].Trustee.ptstrName = (LPTSTR) pAdminSID; // The specified user will have read access in the ACL ea[1].grfAccessPermissions = KEY_READ; ea[1].grfAccessMode = SET_ACCESS; ea[1].grfInheritance = NO_INHERITANCE; ea[1].Trustee.TrusteeForm = TRUSTEE_IS_SID; ea[1].Trustee.TrusteeType = TRUSTEE_IS_USER; ea[1].Trustee.ptstrName = (LPTSTR) pUserSID; // Create the new ACL containing the EXPLICIT_ATTRIBUTES dwRes = SetEntriesInAcl(2, ea, NULL, &pACL); if(dwRes != ERROR_SUCCESS) { System::Console::WriteLine("Unable to set entries" " in ACL - {0}", GetLastError().ToString()); goto Cleanup; } // Allocate memory for a SECURITY_DESCRIPTOR pSD = (PSECURITY_DESCRIPTOR)LocalAlloc(LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH); // Initialize a new SECURITY_DESCRIPTOR if(!InitializeSecurityDescriptor(pSD, SECURITY_DESCRIPTOR_REVISION)) { System::Console::WriteLine("Unable to initialize" " security descriptor - {0}", GetLastError().ToString()); goto Cleanup; } // Set the ACL to the SECURITY_DESCRIPTOR if(!SetSecurityDescriptorDacl(pSD, TRUE, pACL, FALSE)) { System::Console::WriteLine("Unable to set" " security descriptor DACL - {0}", GetLastError().ToString()); goto Cleanup; } // Apply the SECURITY_DESCRIPTOR to the SECURITY_ATTRIBUTES struct sa.nLength = sizeof(SECURITY_ATTRIBUTES); sa.lpSecurityDescriptor = pSD; sa.bInheritHandle = FALSE; // Lookup registry key where security should be applied lRes = RegOpenKeyEx(HKEY_LOCAL_MACHINE, "SOFTWARE",0,KEY_ALL_ACCESS,&softwareKey); if(lRes == ERROR_SUCCESS) { lRes = RegOpenKeyEx(softwareKey,"MyCompany", 0, KEY_ALL_ACCESS,&companyKey); } if(lRes == ERROR_SUCCESS) { // Create the new registry key, applying the SECURITY_ATTRIBUTES lRes = RegCreateKeyEx(companyKey,"MySecuredKey",0,"",0, KEY_READ | KEY_WRITE, &sa, &securedKey, &dwDisposition); } Cleanup: // Free any resources allocated during execution of this method { if(pAdminSID) FreeSid(pAdminSID); if(pUserSID) FreeSid(pUserSID); if(pACL) LocalFree(pACL); if(pSD) LocalFree(pSD); if(securedKey) LocalFree(securedKey); if(companyKey) LocalFree(companyKey); if(softwareKey) LocalFree(softwareKey); } return lRes; } }

Header file

<PRE lang=c++>// SecureRegKeyNET.h #pragma once using namespace System; using namespace System::Runtime::InteropServices; namespace SecureRegistry { public __gc class KeyManager { public: static long CreateRestrictedRegKey(String* account); }; }

stadfx.h header file

<PRE lang=c++>#pragma once #include <windows.h> #include <aclapi.h>

I look forward to the day when .NET 2.0 is released and I don't have to use Win32 API calls to manage ACLs on registry keys. Until then, I am happy to use C++ to perform the dirty work and provide a simple static method call to use in other .NET applications. If you have any questions, suggestions, or improvements to this code, or if you managed to successfully tackle this using P/Invoke, please let me know.


This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


About the Author

Dave Curylo, MCAD
Web Developer
United States United States
Systems Analyst specializing in Java and C# development.

You may also be interested in...


Comments and Discussions

QuestionHard isn't it? Pin
oshah22-May-05 11:35
memberoshah22-May-05 11:35 
I'm not surprised you gave up trying to do this in C#.

Even in native C++, ACL editing can be tough. I came close to madness when I was setting ACLs using the NT3.x APIs (and this was in native C!). My finished program only worked for one registry ACL, was not reusable and ended up being unmaintainable. I don't really want to see that code again.

Before continuing, you should stop using SetEntriesInAcl to set your ACLs. SetEntriesInAcl was an experimental API for NT4 that has been obsoleted by ConvertStringSecurityDescriptorToSecurityDescriptor.

How to set ACLs with SDDL:

long KeyManager::CreateRestrictedRegKey(String *sddlStr)
  /** You are trying to create this SD:
  *   sddlStr = S"D:PAI(A;;KA;;;BA)(A;;KR;;;S-1-5-21-1547161642-1383384898-1957994488-1015)";
  *   You can regenerate the SID using LookupAccountName().
  HKEY securedKey = NULL;
  DWORD dwDisposition = 0;
  /* Lock on the System::String */
  LPCTSTR lpszsddlStr = (const char *)

  if(!ConvertStringSecurityDescriptorToSecurityDescriptor(lpszsddlStr, SDDL_REVISION_1, &pSD, NULL))
    Console::WriteLine(S"Failure generating security descriptor, Error %d", GetLastError());
    goto Cleanup;
  /* you now have a binary security descriptor, suitable for securing registry keys */

  /* Frees LPCTSTR */

  /* Assemble a SECURITY_ATTRIBUTES */
  sa.nLength = sizeof(SECURITY_ATTRIBUTES);
  sa.lpSecurityDescriptor = pSD;
  sa.bInheritHandle = FALSE;

  /* Create the secured registry key. */
  lRes = RegCreateKeyEx(HKEY_LOCAL_MACHINE, _T("SOFTWARE\\MyCompany\\MySecuredKey"), 0, NULL, 0,
    KEY_READ | KEY_WRITE, &sa, &securedKey, &dwDisposition);

    RegCloseKey(securedKey); securedKey = NULL;
    LocalFree(pSD); pSD = NULL;

  return lRes;

SetEntriesInAcl doesn't work in NT3.x, it doesn't work in Win9x, it doesn't work in NT4.0 (pre-SP3), and it has been replaced by SDDL in Win2000. SetEntriesInAcl just doesn't work--don't use it. Next, although it doesn't apply to your code, make sure you prefer SetNamedSecurityInfo over SetFileSecurity / RegSetKeySecurity. This is to preserve auto-inheritance settings when you edit a registry key.

.NET v1.1: Microsoft have written a class library at GotDotNet that is able to set DACLs for registry keys as well as files/directories/WMI/process objects. Make sure you read the comments for code fixes.

However, my ultimate recommendation is to wait for .NET v2.0 to come out. What I've said above will no longer apply once .NET v2.0 is released Smile | :) .
AnswerRe: Hard isn't it? Pin
Dave Curylo, MCAD23-May-05 6:13
memberDave Curylo, MCAD23-May-05 6:13 
GeneralRe: Hard isn't it? Pin
oshah24-May-05 12:50
memberoshah24-May-05 12:50 
GeneralRe: Hard isn't it? Pin
Ian_Rintoul4-Aug-09 9:54
memberIan_Rintoul4-Aug-09 9:54 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.170217.1 | Last Updated 19 May 2005
Article Copyright 2005 by Dave Curylo, MCAD
Everything else Copyright © CodeProject, 1999-2017
Layout: fixed | fluid