Data security within the .NET framework
Lesson 1: Authenticating and authorising users
Authentication
- checking users identity. Most visible and fundamental security concept.
Authorisation
- Verify users right to access resources.
The two processes closely related and often confused.
WindowsIdentity class
Represents a windows user account. Provides access to user name, authentication type and account token. Does not allow authentication - windows has already done this, it simply stores the result of the authentication (including user name and authentication token). Call one of following methods to create instance:
- GetAnonymous - represents anonymous, unauthenticated user. Use this method to impersonate anonymous user to ensure code operates without credentials.
- GetCurrent - represents current windows users.
- Impersonate - represents a specified user on a system.
- Properties - provide information about user, e.g.
- AuthenticationType - string representing authentication method, usually NTLM
- IsAnonymous - true if anonymous
- IsAuthenticated - true if authenticated
- IsGuest - true if guest
- IsSystem - true if user is part of system
- Name - represents authentication domain and user name in format "DOMAIN\Username". If account in local user database, then DOMAIN is the machine name.
- Token - integer representing user authentication token - assigned by computer that authenticated the user.
Examination of object is useful if for example sections of code displays information that should only be available to authenticated users.
WindowsPrincipal class
Provides access to the groups a user belongs to.
Created by instance of WindowsIdentity class, e.g.
WindowsIdentity currentIdentity = WindowsIdentity.GetCurrent();
WindowsPincipal currentPrincipal = new WindowsPrincipal(currentIdentity);
Alternatively, can extract WindowsPrincipal from current thread, e.g.:
AppDomain.CurrentDomain.SetPrincipal(PrincipalPolicy.WindowsPrincipal);
WindowsPrincipal currentPrincipal = (WindowsPrincipal)Thread.CurrentPrincipal;
To query for built in groups pass to the WindowsPrincipal.IsInRole method a member of the System.Security.Principal.WindowsBuiltInRole class (each member of this class representing a built in group).
To query for custom groups, pass a string value (in format DOMAIN\Group Name) to the overloaded IsInRole method.
PrincipalPermission Class
Enables you to check active principal for both declarative and imperative security actions. Used to decoratively demand that users running code have been authenticated or belong to specified role. By passing identity info (user name or role) to constructor the class can be used to demand that identity of actual principal match this information. Can set any combination of three properties:
- Authenticated - boolean, if true permission requires user to be authenticated
- Name - string that must match the users name
- Role - string that must match one of principals role
Demand method verifies active principal meets requirements specified in properties.
Declarative Role Based Security to Restrict Method Access
Instructs runtime to perform checks before running method. Most secure way to restrict access to code as checks performed before code executed. Two primary disadvantages:
- Can only be used to restrict access to entire methods
May result in runtime throwing exception. If method was called by a windows event, this will catch the exception and application execution halts.
To use have three elements in code:
- System.AppDomain.CurrentDomain.SetPrincipalPolicy to specify the principal security policy
- Try/Catch block to catch unprivileged access attempts and report them
- PrincipalPermission attribute to declare methods access requirements.
e.g. Following code calls method that is protected with a declarative RBS demand, displays a message box if user lacks necessary permission:
try
{
AdministratorsOnlyMehtod();
}
catch (System.Security.SecurityException)
{
MessageBox.Show("You lack permission.");
}
[PrincipalPermission(SecurityAction.Demand, Role=@"BUILTIN\Adminstrators")]
static void AdministratorsOnlyMethod()...
Can use multiple declarative demands to enable users who meet any of the demands to execute the code.
Imperative Role Based Security to Restrict Access To Parts of Methods
Declared within code. To use must have four elements:
- System.AppDomain.CurrentDomain.SetPrincipalPolicy to specify the principal security policy
- Try/Catch block to catch unprivileged access attempts and report them
- PrincipalPermission attribute to declare restrictions to impose
- A call to PrincipalPermission.Demand to declare methods access requirements.
Use one of three overloaded constructors:
- PrincipalPermission(PermissionState)
- PrincipalPermission(Name, Role) - if only require a user name or role then specify null for other
- PrincipalPermission(Name, Role, Authenticated)
//
Use Windows security policy
System.AppDomain.CurrentDomain.SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal);
try
{
// Grant access to members of VS Developers group
PrincipalPermission p = new PrincipalPermission(null, System.Environment.MachineName + @"\VS Developers", true);
p.Demand();
...
}
catch (System.Security.SecurityException)
{
...
}
Implement Custom Users and Roles
To authenticate against custom database use System.Security.PrincipalIIdentity and IPrincipal interfaces. Can extend interfaces with own classes adding customer properties and functionalities (e.g. customer address).
IIdentity is template for creating identity classes. WindowsIdentity is an example of an implementation, the bulk of its method and properties being inherited directly from IIdentity. FormsIdentity and PassportIdentity support web authentication whilst GenericIdentity provides a very flexible implementation. To implement must implement:
- AuthenticationType
- describes the authentication mechanism to allow apps to determine whether to trust the authentication, e.g. one app may determine that Passport meets its requirements, whilst for another it does not. If using a custom authentication mechanism then specify a unique AuthenticationType.
- IsAuthenticated - true if use has been authenticated
- Name - string storing users name
Class constructor should define each of objects properties
When NOT to implement IIdentity
If want to add properties to a windows logon whilst still using the Windows token or other Windows security properties then derive custom identity from WindowsIdentity. Same applies for IPrincipal and WindowsPrincipal.
Creating Custom Principal Class
Objects based on IPrincipal interface represent security context of user - includes identity and roles (or groups) they belong to. To implement must provide at least one constructor, one property and one method. Constructor takes IIdentity object and array of settings containing identity roles. Property implements IPrincipal.Identity and returns the principals identify object. The method is the boolean IPrincipal.IsInRole taking a string and role being queried and returns true if the identity is in the role.
Create Simple User Privilege Models
If don't want to use framework classes based on IIdentity and IPrincipal and need only basic functionality provided by these interfaces then use the GenericIdentity and GenericPrincipal classes. These classes only implement the methods required by the interfaces. GenericIdentity has two constructors, either username or username and authentication type. GenericPrincipal has a single constructor taking a GenericIdentity object and array of strings representing the roles.
GenericIdentity myUser = new GenericIDentity("TAdams", "SmartCard");
GenericPrincipal myPrincipal = new GenericPrincipal(myUser, new string[] {"IT", "Users"});
RBS
Demands with Custom Identities and Principals
Whether using custom or generic implementations of IIdentity and IPrincipal can take advantage of same declarative and imperative RBS techniques used for WindowsPrincipal and WindowsIdentity.
- Create IIdentity or GenericIdentity representing current user
- Create IPrincipal or GenericPrincipal based on IIdentity object.
- Set Thread.CurrentPrincipal to IPrincipal object
- Add declarative or imperative RBS demands.
Handling
Authentication Exceptions in Streams
When authentication to remote computers using NegotiateStream or SslStream class the framework will throw exception if client or server can not be authenticated. Should always be prepared to catch the following:
- AuthenticationException - prompt the user for different credentials and retry operation
- InvalidCredentialException - underlying system is in invalid state, cannot retry authentication.
Lesson 2: Access Controls Lists
Operating systems use ACLs to provide similar functionality to permission demands.
Discretionary Access Control List
A DACL is an authorisation restriction mechanism that identifies users and groups allowed or denied access to an object. If a DACL does not explicitly identify a user or any groups that user is a member of then the user is denied access to that object. A DACL contains access control entities (ACE) that determine user access to object. An ACE is an entry in the DACL that grants permission to a user or group.
To make managing permission efficient they support inheritance. When Windows first installed most objects only have inherited permissions. Inherited permissions propagate to an object form its parent.
Calculating Effective Permissions
To calculate effective permissions Windows must do more than just look up users name in ACL. ACEs can assign permissions to users or groups. Users can be members of multiple groups and groups can be nested within one another. Therefore a user can have several different ACEs in a DACL.
Granted permissions are cumulative, but ACEs that deny access always override ACEs that grant access.
ACEs in .NET Framework
Different resources have unique permissions that define an ACE, e.g. Both files and registry entries have Full Control and Delete permissions, Read & Execute is unique to files and Query Values is unique to the registry. Fortunately permissions for different resources function similarly so all classes inherit from common base classes.
To specify file and folder permissions use the FileSystemsRights enumeration.
Security Access Control List
A SACL is a usage logging mechanism that determines how file or folder access is audited. Unlike DACL cannot restrict access to object. A SACL causes an event to be recorded in the security event log when a user access the object. Used to troubleshoot problems and identify intrusions.
By default Windows does not log audit events. Must enable Audit Object Access security policy via the Local Security Policy MMC.
View and Configure ACLs within an Assembly
System.Security.AccessControl namespace contains variety of classes for viewing and accessing ACLs. For each resource type the namespace provides three classes:
- Security - provide methods for retrieving collection of DACLs or SACLs and adding / removing ACL. Inherit from NativeObjectSecurity.
- AccessRule - Set of access rights allowed or denied for a user or group. Inherit from AccessRule (which derives from AuthorizationRule).
- AuditRule - Set of access rights to be audited for a user. Inherit from AuditRule (which derives from AuthorizationRule).
Analyse ACLs
- Create instance of class deriving from NativeObjectSecurity, e.g. FileSecurity.
- Call GetAccessRules method to retrieve instance of AuthorizationRulesCollection
- Iterate collection to analyse individual ACE.
RegistrySecurity rs = Registry.LocalMachine.GetAccessControl();
AuthorizationRuleCollection arc = rs.GetAccessRules(true, true, type(NTAccount));
foreach(RegistryAccessRule ar in arc) Console.Writeline(ar.IdentityReference + ar.AccessControlType + ar.RegistryRights);
Configure ACLs
- Call GetAccessControl method to get instance of class deriving from NativeObjectSecurity, e.g. FileSecurity.
- Add / remove ACL entries from object. Typically provide user or group name, enumeration describing rights and an AccessControlType indicating whether to grant or deny rights,
- Call SetAccessControl to apply changes.
DirectorySecurity ds = Directory.GetAccessControl(dir);
ds.AddAccessRule(new FileSystemAccessRule("Guest", FileSystemRights.Read, AccessControlType.Allow));
Directory.SetAccessControl(dir, ds);
Lesson 3: Encryption and Decryption
Using Symmetric Keys
Also known as secret-key encryption. Relies on both parties knowing the key.
Algorithms (called cyphers) process plain text with encryption key to generate encrypted data (called the cypher text). The cypher text cannot be decrypted into plain text without knowing the secret key.
Symmetric cyphers are fast and suited to encrypting large amounts of data.
To identify the plain text an attacker need only use a brute force attack by trying every possible key combination. Typically this will take hundreds of years (if not longer).
Symmetric encryption presumes the two parties have agreed on a key. Agreeing on a key can be difficult as it cannot be encrypted, consequently a secure mechanism must exist for exchanging keys. Encryption keys should be changed regularly (for the same reasons as passwords should be changed).
Symmetric Algorithm Classes in the .Net Framework
Found in the System.Security.Cryptography namespace.
| ** Class ** | ** Key Length ** | ** Description ** | | RijndaelManaged | 128 - 256 bits (32 bit increments)| Also known as AES. Only framework class that is fully managed. Use in preference. | | RC2 | Variable | Designed to replace DES with variable key sizes | | DES | 56 bits | Short key length = easy to crack so should be avoided. Commonly used as compatible with wide range of legacy platforms. | | TripleDES | 156 bits (only 112 used) | Applies DES 3 times |
All derive from SymmetricAlgorithm base class and share following properties:
- BlockSize - number of bits algorithm processes at a single time (can usually be ignored)l
- FeedbackSize - determines one aspect of the algorithms encryption technique, but as a developer this can be ignored
- IV - the initialisation vector. Like Key property both parties must specify same value. To avoid overhead of transferring securely it may be good idea to statically define in both parties. The IV is used to obscure the first block of data being encrypted thereby making decryption harder.
- Key - secret key for algorithm. Automatically generated if not specifically defined.
- KeySize - the runtime will automatically choose the largest key supported by the algorithm. If the recipient does not support this size then use the property to set the highest value supported by both parties.
- LegalBlockSize - the block sizes supported by the algorithm. MinSize and MaxSize indicate the valid range in bits whilst SkipSize indicates the intervals between key sizes.
- LegalKeySizes - the key sizes supported by the algorithm, MinSize and MaxSize indicate the valid range in bits whilst SkipSize indicates the intervals between key sizes.
- Mode - Determines one aspect of algorithm behaviour. Usually left at default of Cipher Block Chaining (CBC). If changed to one of its other enumerated values then the partner must be set to use the same mode.
- Padding - determines how the algorithm fills out any difference between the block size and length of plain text. Don't generally need to change property.
Establishing a Symmetric Key
Cannot use any piece of data as a key, must be of specific length.
To generate random key simply create instance of algorithm object. Can create another random key by calling the GenerateKey method on the object.
Can generate valid key from user supplied password via the Rfc2898DeriveBytes class. Class requires three values in addition to the password - a salt value, IV and number of iterations used to generate key. The password and these values must be common between both parties. These values can be passed in to the class constructor and are available via class properties.
String password = "[P@55w0r]()]";
RijndaelManaged myAlg = new RijndaelManaged();
byte[] salt = Encoding.ASCII.GetBytes("My SALT");
Rfc2898DeriveBytes key = new Rfc2898DeriveBytes(password, salt);
myAlg.Key = key.GetBytes(myAlg.KeySize / 8);
myAlg.IV = key.GetBytes(myAlg.BlockSize / 8);
Encrypting / Decrypting Messages
Similar to reading and writing to files and streams.
- Create a Stream object to interface with memory of file to be reading or writing
- Create SymmetricAlgorithm object
- Specify algorithms key, IV or both
- Call SymmetricAlgorithm.CreateEncryptor() or SymmetricAlgorithm.CreateDescriptor() to create ICryptoTransform object.
- Create a CryptoStream object using the Stream and ICryptoTransform object.
- Read or write the CryptoStream
Using Asymmetric Keys
Also known as public-key encryption.
Overcomes symmetric encryption significant disability - requiring both parties to know the key.
Relies on key pairs, public key that is freely distributed and private key used to decrypt cypher text encrypted with the private key.
Generally both client and server exchange public keys, but encryption is only required in one direction then only the peer receiving encrypted communications needs to supply a key.
Not as fast as symmetric algorithms and so unsuited to encrypting large amounts of data. One common use of asymmetric algorithm is to encrypt and transfer symmetric key and IV - this is technique used by HTTPS and SSL.
Asymmetric Algorithms in .NET
Two classes provided, both deriving from System.Security.Cryptography.AsymmetricAlgorithm. Has following properties (several similar to SymmetricAlgorithm):
- KeyExchangeAlgorithm - returns name of key exchange algorithm
- KeySize - size of secret key. Typically much larger than symmetric keys, e.g. RSA algorithm supports lengths from 384 to 16384 bits.
- LegalKeySizes - a KeySizes array describing the supported key sizes, each entry features a MinSize and MaxSize property setting bounds and a SkipSize specifying intervals between valid key sizes.
- SignatureAlgorithm - URL of XML document describing signature algorithm. The AsymmetricAlgorithm has no useful methods. These are provided by its two implementations:
- RSACryptoServiceProvider - Managed wrapper around unmanaged RSA implementation. Used for all asymmetric encryption and decryption calls.
- DSACryptoServiceProvider - Used to digitally sign messages.
The RSACryptoServiceProvider class also provides these properties:
- PersistKeyInCsp - set to true when want to reuse the key without exporting it.
- UseMachineKeyStore - indicates if the key should be persisted in the computers key store instead of the user profile store.
- Default constructors populate algorithm with strongest defaults available to runtime environment.
The RSACryptoServiceProvider class also provides these methods:
-
DecryptingEncrypt - decrypts data
-
Encrypt - encrypts data
-
ExportParameters - exports RASParameters structure defining the key pair. Pass true to method to export both public and private keys, otherwise only public keys are exported.
-
FromXmlString - imports key pair from xml
-
ImportParameters - imports key pair from RASParameters structure
-
SignData - computes hash of specified data and stores in byte array
-
SignHash - computes signature of specified hash by encrypting it with private key and storing signature in byte array
-
ToXmlString - exports key pair to xml
-
VerifyData - verifies specified signature data by comparing it with signature computed from specified data
-
VerifyHash- verifies specified signature data by comparing it with signature computed fro specified hash
Export / Import Asymmetric Keys and Key Pairs
Much more complex than symmetric keys. Key pair represented by RASParameters structure, significant members:
| ID | The private key | | Exponent | Also known as e, this is short part of public key | | Modulus | Also known as n, this is the long part of the public key. |
Need to export public key as without this no one can send encrypted messages to you. Only export private key if need to reuse later, if it is stored then the application must protect the privacy of the private key.
To store or transmit the exported key use RSACryptoServiceProvider.ToXmlString(). Like ExportParameters it takes a boolean indicating if private key should be exported.
Storing key pairs for later reuse
Can export keys to the cryptographic service provider (CSP) using CryptoAPI key storage.
- Create CspParameters object
- Specify CspParameters.KeyContainerName property
- Create RSACryptoServiceProvider passing in CspParameters object
- Set RSACryptoServiceProvider.PersistKetInCsp to true
Framework handles creating and retrieving keys. First time specify a CspParameters object and set PersistKetInCsp to true the framework will create key container and store key. When called subsequently the framework will detect that a key container for that name exists already and retrieve the stored private key.
Encrypt / Decrypt messages using asymmetric encryption
Both the encrypt and decrypt methods take two parameters
- byte[] rgb - message to be encrypted / decrypted
- byte fOAEP - when true encryption will use OEAP data padding 0 only supported by XP and later. When false, PKCS#1 v1.5 data padding is used. Both encryption and decryption calls must use same padding.
string msg - "Hello, world!";
RSACryptoServiceProvider myRSA = new RSACryptoServiceProvider();
byte []msgBytes = Encoding.Unicode.GetBytes(msg);
byte []encryptedMsg = myRSA.Encrypt(msgBytes, true);
byte []decryptedBytes = myRsa.Decrypt(encryptedMsg, true);
string msg2 = Encoding.Unicode.GetString(decryptedBytes);
Validating Integrity With Hashes
Important use of cryptography is to protect data using hashes. A hash is a checksum unique to some data. Can use hash to verify that data has not been modified after hash generated.
Cannot derive original data from hash, even if original data is very small. Often used to verify passwords without storing password themselves.
Framework includes six non-keyed hash algorithms and two keyed algorithms.
Non-keyed hashing algorithms
| Abstract class | Implementation Class | Description | | MD5 | MD5CryptoServiceProvider | MD5 with hash size of 128 bits | | RIPEMD160 | RIPEMD160Managed | MD160 with hash size of 160 bits | | SHA1 | SHA1CryptoServiceProvider | SHA of 160 bits | | SHA256 | SHA256Managed SHA of 256 bits | | SHA384 | SHA384MAnaged | SHAof 384 bits | | SHA512 | SHA512Managed | SHA of 512 bits |
Keys have to be protected against modification - otherwise it defeats their value. The framework provides two classes that encrypt the key using a secret key known to both sender and receiver
| Class | Description | | HMACSHA1 | Hash based message authentication code. Used to determine if message sent over insecure channel has been tampered with. Accepts keys of any size and produces hash of 20 bytes | | MACTripleDES | Message authentication code using TripleDES. Accepts key length of 8, 16 or 24 bytes. Produces hash of 8 bytes. |
Compute non-keyed hash
// Create hash algorithm object
MD5 myHash = new MD5CryptoSevicePovider();
// Store data to be hashed in byte array
FileStream file = new FileStream(args[0], FileMode.Open, FileAccess.Read);
BinaryReader reader = new BinaryReader(file);
// Call HashAlgorithm.ComputeHash
myHash.ComputeHash(reader.ReadBytes((int)file.Length);
// Retrieve the HashAlgorith.Hash byte array
Console.WriteLine(Convert.ToBase64String(myHash.Hash));
Compute keyed hash
// Create secret key (shared by using the hash
byte[] salt = Encoding.ASCII.GetBytes("This is a salt");
Rfc2898DeriveBytes passwordKey = new Rfc2898DeriveBytes(args[0], salt);
byte[] secretKey = passwordKey.GetBytes(16);
// Create hash algorithm object
HMACSHA1 myHash = new HMACSHA1(secretKey);
// Store data to be hashed in byte array
FileStream file = new FileStream(args[0], FileMode.Open, FileAccess.Read);
BinaryReader reader = new BinaryReader(file);
// Call HashAlgorithm.ComputeHash
myHash.ComputeHash(reader.ReadBytes((int)file.Length);
// Retrieve the HashAlgorith.Hash byte array
Console.WriteLine(Convert.ToBase64String(myHash.Hash));
Signing Files
Digital signature is appended to electronic data to prove it was created by someone possessing a specific private key. The framework provides two classes for generating and verifying signatures - DSACryptoServiceProvider and SRACryptoServiceProvider. Both implement following methods:
- SignHash - creates signature based on hash of file
- SignData - creates digital signature by first generating hash for file then generating signature based on hash
- VerifyHash - verifies signature based on hash of file
- VerifyData - verifies signature given entire file contents.
Separate methods are provided to generate and verify signatures (unlike hash generation) as the signature is generated by an asymmetric algorithm, i.e. the recipient checking the signature only has access to the senders public key.
Signing and Verifying File
// Create digital signature algorithm object
DSACryptoServiceProvider signer = new DSACryptoServiceProvider();
// Store data to be signed in byte array
FileStream file = new FileStream(args[0], FileMode,Open, FileAccess.Read);
BinaryReader reader = new BinaryReader(file);
byte[] data = reader.GetBytes((int)file.Length);
// Generate signature
byte[] signature = signer.SignData(data);
// Export the key
string publicKey = signer.ToXmlString(false);
...
// Create digital signature algorithm object
DSACryptoServiceProvider verifier = new DSACryptoServiceProvider();
// Import public key
verifier.FromXmlString(publicKey);
// Store data to be verified in byte array
FileStream file2 = new FileStream(args[0], FileMode,Open, FileAccess.Read);
BinaryReader reader2 = new BinaryReader(file2);
byte[] data2 = reader2.GetBytes((int)file2.Length);
// Verify the signature
if (!verifier.VerifyData(data2, signature)) Console.WriteLine("Error");