Learn how Win2K encrypts and decrypts files
Last month, I began this two-part series by introducing the basics of Encrypting File System (EFS). I concluded by discussing how EFS generates keys and stores the keys in an EFS attribute with the file the keys will encrypt. This month, I conclude my walk through the encryption process. I also discuss the decryption process and other functionality that the EFS driver provides, including encrypted file backup and restore and the ability to view information about encrypted files.
Encrypting File Data, Continued
I concluded Part 1 at the point at which EFSEncryptFileSrv finishes creating Data Decryption Field (DDF) and Data Recovery Field (DRF) key fields for a file that is undergoing encryption. Figure 1, page 54, depicts the encryption process' flow. After EfsEncryptFileSrv constructs the necessary information for a file a user wants to encrypt, EFS can begin encrypting the file. The Local Security Authority Server's (LSASRV's) EncryptFileSrv function guides this phase of the encryption process. First, EncryptFileSrv creates a backup file, efs0.tmp, for the file undergoing encryption. (EncryptFileSrv uses higher numbers in the backup filename if other backup files exist.) EncryptFileSrv creates the backup file in the directory that contains the file undergoing encryption. Then, EFS applies a restrictive security descriptor to the backup file so that only the OS (i.e., the System account) can access the file's contents. EFS initializes the log file that EfsRpcEncryptFileSrv created in the first phase of the encryption process. Finally, EFS records in the log file that EncryptFileSrv created the backup file. EFS encrypts the original file only after the file is completely backed up.
EncryptFileSrv next prepares to send the EFS device driver a command to add to the original file the EFS information that EncryptFileSrv just created. EncryptFileSrv encrypts the command with DESX (a stronger variant of Data Encryption StandardDES) and a session key before sending the command. When the system initializes, LSASRV uses the CryptoAPI function CryptGenRandom to produce a 128-bit number to serve as the session key. The EFS driver asks LSASRV for the session key via local procedure call (LPC); when the driver has the key, the driver can decrypt the control commands that EncryptFileSrv encrypted with the session key. EFS encrypts control commands so that malicious users can't develop programs to send commands specifying that files adopt incorrect EFS information, or that destroy files' valid EFS information. (A file with invalid EFS information is essentially destroyed because EFS can't then interpret the file's data correctly.)
NTFS receives the command to add the EFS information to the original file, but because NTFS doesn't understand EFS commands, NTFS calls the EFS driver function EfsFileControl. EfsFileControl uses its copy of the session key to decrypt the control command, then calls the EfsSetEncrypt function. EfsSetEncrypt takes the EFS information that LSASRV sent and uses exported NTFS functions to apply the information to the file. The exported NTFS functions let EFS add meta data information to NTFS files. The EFS driver uses NtOfsCreateAttributeEx, NtOfsSetLength, NtOfsPutData, and NtOfsCloseAttribute to create the $EFS NTFS meta data attribute (which is new to Windows 2000Win2K) and copy the EFS information to the attribute. Simultaneously, the EFS driver returns a block of context data to NTFS and associates the data with the file's NTFS in-memory management data. NTFS will pass this data, or context information, to EFS whenever NTFS invokes an EFS driver callback function for the file. EFS makes use of the context information to store an unencrypted version of the file's file encryption key (FEK).
Execution returns to EncryptFileSrv, which copies the contents of the file undergoing encryption to the backup file. When the backup copy is complete, including backups of all alternate data streams, EncryptFileSrv records in the log file that the backup file is up-to-date. EncryptFileSrv then sends another DESX-encrypted command to NTFS to tell NTFS to encrypt the contents of the original file.
When NTFS receives the EFS command to encrypt the file, NTFS deletes the contents of the original file and copies the backup data to the file. After NTFS copies each section of the file, NTFS flushes the section's data from the file system cache, which prompts the Cache Manager to tell NTFS to write the file's data to disk. Because the file is marked as encrypted, at this point in the file-writing process, NTFS calls EFS to encrypt the data before NTFS writes the data to disk. The EFS function EfsWrite uses the unencrypted FEK in the file's context information to perform DESX encryption of the file, one sector (512 bytes) at a time.
On Win2K versions approved for export outside the United States, the EFS driver implements a 56-bit key DESX encryption. I stated in Part 1 that a FEK is 128 bits long; however, only the first 56 bits constitute the DESX key for the EFS export version. For the US-only version of Win2K, the key is 128 bits long, and the entire FEK constitutes the key.
After EFS encrypts the file, EncryptFileSrv records in the log file that the encryption was successful and deletes the file's backup copy. Finally, EncryptFileSrv deletes the log file and returns control to the application that requested the file's encryption.
Table 1 summarizes the steps EFS performs to encrypt a file. If the system crashes during the encryption process, either the original file remains intact or the backup file contains a consistent copy. When LSASRV initializes after a system crash, it looks for log files under the System Volume Information subdirectory on each NTFS drive on the system. If LSASRV finds one or more log files, it examines their contents and determines how recovery should take place. LSASRV deletes the log file and the corresponding backup file if the original file wasn't modified at the time of the crash; otherwise, LSASRV copies the backup file over the original, partially encrypted file, then deletes the log and backup. After EFS processes log files, the file system will be in a consistent state with respect to encryption, with no loss of user data.
As I stated in Part 1, the OS can designate directories as encrypted. EFS automatically encrypts any files a user moves into or creates in an encrypted directory.
The Decryption Process
The decryption process begins when a user opens an encrypted file. NTFS examines the file's attributes when opening the file, then executes the callback function EfsOpenFile in the EFS driver. EfsOpenFile reads the EFS attribute associated with the encrypted file. To read the attribute, EfsOpenFile calls several of the EFS support functions that NTFS exports for EFS's use. First, EfsOpenFile calls NtOfsCreateAttributeEx to open the attribute. NtOfsCreateAttributeEx supports opening any of a file's NTFS attributes, including the file's name attribute (which stores the file's name and attribute data), security attribute, and data attributes (which store the file data). For a refresher course on attributes, see NT Internals: "Inside NTFS," January 1998. To use EfsOpenFile to open the EFS attribute, EFS specifies $EFS as the attribute name and the numeric identifier of the $EFS attribute.
EfsOpenFile next opens the EFS attribute and calls the NTFS function NtOfsQueryLength to determine the attribute's length. Then, EfsOpenFile allocates a buffer to store the attribute's contents, calls NtOfsMapAttribute to obtain a virtual memory pointer to the contents, and copies the contents into the buffer. EfsOpenFile returns the EFS attribute data and bookkeeping information to NTFS. If EfsOpenFile can't open the EFS attribute, NTFS fails the file's open operation and returns an appropriate error code to the application that tried to open the file.
If the EFS attribute opens successfully, NTFS completes the necessary steps to open the file. When the file-open operation completes, NTFS invokes the EFS callback function EFSFilePostCreate. NTFS passes the context information from EfsOpenFile as a parameter to EFSFilePostCreate. EFSFilePostCreate ensures that the user opening the file has access to the file's encrypted data (i.e., that an encrypted FEK in either the DDF or DRF key rings corresponds to a public key/private key pair associated with the user). As EFS performs this validation, EFS obtains the file's decrypted FEK to use in subsequent data operations the user might perform on the file.
EFSFilePostCreate can't decrypt a FEK and relies on LSASRV (which can use the CryptoAPI) to perform FEK decryption. Before EFSFilePostCreate sends an LPC message to LSASRV, EFSFilePostCreate extracts the security ID (SID) of the user opening the file from the current process token. Then, EFSFilePostCreate attaches to the lsass.exe process (LSASRV's location). When a kernel thread attaches to a process, the thread associates with the process' (in this case lsass.exe) user-mode virtual memory; the thread doesn't attach to the virtual memory of the process to which the thread belongs (in this case the process opening the file). EFSFilePostCreate allocates a buffer in the LSASS process' virtual memory and copies the EFS context informationwhich includes the EFS attribute data that EfsOpenFile passed to EFSFilePostCreate indirectly
into the buffer. Because EFSFilePostCreate is now executing as a thread in the lsass.exe process (which executes in the System account) instead of as a thread in the process executing in the user's account that is opening the file, EFSFilePostCreate must impersonate the user opening the file. EFSFilePostCreate calls the PsImpersonateClient Security Manager function to accomplish the impersonation. Now, all the information LSASRV needs is in place, and EFSFilePostCreate sends an LPC message by way of the ksecdd.sys driver to LSASRV. The LPC message asks LSASRV to obtain the decrypted form of the encrypted FEK in the EFS attribute data that corresponds to the user the thread is now impersonating.
When LSASRV receives the LPC message, LSASRV executes the EFS function LpcEfsDecryptFek. To execute in the security context of the user opening the file, rather than in the System security context, LpcEfsDecryptFek uses an LPC impersonation facility that changes the function's security context to the user's security context. As I described in Part 1, both EFS and the CryptoAPI require access to a user's Registry profile settings; therefore, LpcEfsDecryptFek executes the userenv.dll (User Environment DLL) LoadUserProfile API to bring the user's profile into the Registry, if the profile isn't already loaded.
LpcEfsDecryptFek calls the DecryptFek function to perform decryption. DecryptFek doesn't know which of the encrypted FEKs in the DDF and DRF key rings correspond to the user opening the file. To find the user's FEK, DecryptFek proceeds through each key field in the EFS attribute data, using the user's private key to try to decrypt each FEK. Figure 2, reprinted from Part 1, shows an example EFS attribute's format. For each key, DecryptFek calls the function GetFekFromEncryptedKeys, which attempts to decrypt a DDF or DRF key entry's FEK. GetFekFromEncryptedKeys first obtains a CryptoAPI reference to the user's private key. Last month, I explained that a certificate hash that the encryption process stores in a user's private key entry uniquely identifies a user's key pair. Because a user can have more than one EFS public key/private key pair, GetFekFromEncryptedKeys relies on the certificate hash in the key entry to identify the private key it will use to try to decrypt the encrypted FEK stored in that field. Because EFS stores EFS key pairs in the My certificate storage area, GetFekFromEncryptedKeys specifies the My storage area when requesting the CryptoAPI function CertOpenSystemStore to open the appropriate certificate store. GetFekFromEncryptedKeys gives CertFindCertificateInStore the current key entry's certificate hash, and CertFindCertificateInStore searches the My store for the key pair the certificate hash identifies.