Introducing Personal Data Encryption for developers
Personal Data Encryption (PDE) along with BitLocker constitutes Windows data protection on Windows devices. BitLocker is a Windows security feature that provides encryption for entire volumes, addressing the threats of data theft or exposure from lost, stolen, or inappropriately decommissioned devices. However, there are some cases in which BitLocker protection alone might not be sufficient. For example, Trusted Platform Module (TPM) bus sniffing, targeting devices that do not have BitLocker TPM + PIN options set, or trying to get encryption keys by sniffing the unsecured bus between the CPU and TPM can all put BitLocker protected personal data at risk. Direct Memory Access (DMA) based drive-by attacks target devices with unsecured DMA ports and work by bypassing the sign in and getting directly to the end user’s data. Applications and browsers that utilize AI to power recommendation engines capture sensitive user data and also need to be protected.
PDE provides an extra layer of security, in addition to that provided by BitLocker, for when the device is locked and powered on, protecting it from sophisticated physical attacks. PDE uses Windows Hello for Business to link data encryption keys with user credentials. When a user signs in to a device using Windows Hello for Business, decryption keys are released, and encrypted data is accessible to the user. It’s important to note that PDE and BitLocker are not dependent on each other. PDE can be used with or without any other full disk encryption solutions, although it is highly recommended to use both.
PDE API offers a comprehensive and extensible set of low-level APIs for the protection of end-user content. These APIs enable the encryption of end-user data, and the keys used for encryption are protected by the user’s Windows Hello credentials. It is important to note that PDE is exclusively available in Windows Enterprise and Education editions.
Content-generating applications can use the PDE API to protect content for two levels of security:
L1 (AfterFirstUnlock) level of protection: Data protected to this level is accessible only after the first device unlock, and it will continue to be available thereafter.
L2 (WhileUnlocked) level protection: Data protected to this level is only available when the device is unlocked and provides additional protection.
Now let’s look at how an application that generates content can use PDE API to protect files, folders, and buffers.
Use cases for PDE API
PDE API provides a feature set for app developers building Windows applications that generate or modify end-user content on Windows devices.
Industries such as defense, banking, healthcare, and insurance are just some examples of commercial environments that handle a lot of sensitive data and need additional protection to help ensure data security.
Note: PDE is also applied to all content within the known Windows folders such as Documents, Desktop and Pictures that have the L1 level of protection and are available as part of the OS, enabled as a Microsoft Intune policy. This new feature will be available in Windows 11, version 24H2, which is currently available in the Windows Insider Program via the Release Preview Channel.
Using the PDE API
Before getting started, PDE needs to be enabled on the device being used for development using the PDE API. PDE is enabled by policy from a Microsoft Device Management solution like Intune to a group of users in an organization by the IT admin. For more details, see our PDE documentation and our documentation on PDE with the Data Protection API.
Below, we outline a sample application that uses the different functions in the PDE API and the possible scenarios in which they can be used. As shown in the screenshot below, the application walks through protecting folders, files, and text (buffers) with two levels of security as well as unprotecting them. The complete code is available on GitHub.
Packages or libraries to import to start using PDE API in applications
Since PDE API is a Windows Runtime API, integrating it into a project requires some initial steps, as outlined in this article about Windows Runtime APIs. When set up is complete, ensure that Microsoft.Windows.SDK.Contracts package (latest stable version) is installed through the Nuget Package Manager.
The library Microsoft.Windows.SDK.Contracts is installed using the Nuget Package Manager, and then you can import Windows.Security.DataProtection library into code.
<code>
using Windows.Security.DataProtection;
</code>
Windows.Security.DataProtection namespace contains different classes that provide methods to protect/unprotect files and buffers, provide information about availability of the storage item, and provide the status of unprotecting a buffer and callback methods to block/unblock future events.
Global variables in code
Below is the set of global variables that are referenced in code:
UserDataProtectionManager dataProtectionManager;
String selectedFolder = String.Empty;
String selectedFile = String.Empty;
Protecting a folder or file using functions in Windows.Security.DataProtection
The DataProtection namespace provides the UserDataProtectionManager class. When instantiated, this class provides static methods to protect/unprotect folders, files, and buffers.
<code>
dataProtectionManager = UserDataProtectionManager.TryGetDefault();
</code>
The TryGetDefault() method called on the UserDataProtectionManager returns an instance of this class for the current user. The returned instance, if null, means that the PDE policy is not yet enabled on the device or PDE is not supported on the device.
ProtectStorageItemAsync (IStorageItem, UserDataAvailability) is the method used to protect files andfolders. The method takes two parameters: IStorageItem object, which is an encapsulation of a path, and UserDataAvailability object, which is an Enum representing the availability of the protected data. ProtectStorageItemAsync protects one storage item at a time. If the path represents a folder, the onus for recursively protecting all the files and subfolders is on the application. The folder needs to be protected before its contents are. This ensures that any item that is added to the folder later will automatically be protected to the same level as the parent. The code for it could look something like the code snippet below.
Note: It is a security best practice to always encrypt files and folders before data is written to them especially if at startup the OS detects that PDE is available. If PDE is available, the app should protect the folder where it caches its data and protect any file it creates before writing any data if the file isn’t in a folder that is already protected.
Folder or file protection
Please note the call to the ProtectAndLog to protect the folder before protecting all the files in the folder ensures any new additions are automatically protected. The difference between protecting a file or folder is the IStorageItem that gets created. Once an item is created, it is the same ProtectStorageItemSync method that is called for protecting both the file and folder.
<code>
async void ProtectAndLog(IStorageItem item, UserDataAvailability level)
{
try
{
var protectResult = await dataProtectionManager.ProtectStorageItemAsync(item, level);
if (protectResult == UserDataStorageItemProtectionStatus.Succeeded)
{
LogLine(“Protected ” + item.Name + ” to level ” + level);
}
else
{
LogLine(“Protection failed for ” + item.Name + ” to level ” + level + “, status: ” + protectResult);
}
}
catch (NullReferenceException)
{
LogLine(“PDE not enabled on the device, please enable before proceeding!!”);
}
}
async void ProtectFolderRecursively(StorageFolder folder, UserDataAvailability level)
{
// Protect the folder first so new files / folders after this point will
// get protected automatically.
ProtectAndLog(folder, level);
// Protect all sub-folders recursively.
var subFolders = await folder.GetFoldersAsync();
foreach (var subFolder in subFolders)
{
ProtectFolderRecursively(subFolder, level);
}
// Finally protect all existing files in the folder.
var files = await folder.GetFilesAsync();
foreach (var file in files)
{
ProtectAndLog(file, level);
}
}</code>
Folder or file protection status
UserDataStorageItemProtectionStatus is an enum that is populated with the result of the Protect call. This enum is used in the above example to log the appropriate result. The other values in this enum are:
DataUnavailable (2): Requested protection cannot be applied because the data are currently unavailable. For example, changing availability from “WhileUnlocked” to “AfterFirstUnlock” is not possible while the device is locked.
NotProtectable (1): The system does not support protection of the specified storage item.
Further exploring the properties of a protected file gives the end user a view of the availability level to which the file is PDE protected. It also provides information about the On/Off status of Personal Data Encryption.
Protecting buffers using functions in Windows.Security.DataProtection
Along with files, systems also store data in buffers as part of processing. If not protected, these buffers can lead to compromises in security. Buffers in scope are the ones that are persisted.
The ProtectBufferAsync method takes as input the buffer object (any object that implements iBuffer interface) and the level (Enum UserDataAvailability) to which the buffer would need to be protected.
Note: PDE doesn’t protect streams directly. The application will have to protect them in chunks.
The text in the sample below represents a string from any source:
<code>
async void ProtectBuffer(String text, UserDataAvailability level)
{
// Empty buffers cannot be protected, please ensure that text length is not zero.
if (text.Length == 0)
{
return;
}
try
{
var buffer = CryptographicBuffer.ConvertStringToBinary(text, BinaryStringEncoding.Utf8);
var protectedContent = await dataProtectionManager.ProtectBufferAsync(buffer, level);
String protectbase64EncodedContent = CryptographicBuffer.EncodeToBase64String(protectedContent);
bufferOutputTextBox.Text = protectbase64EncodedContent;
LogLine(“Protected buffer: ” + protectbase64EncodedContent);
}
catch (NullReferenceException nrex)
{
LogLine(“PDE not enabled on the device, please enable before proceeding!!”);
LogLine(nrex.ToString());
} }
</code>
Unprotecting files and buffers
The UserDataAvailability enum that sets the protection has three levels:
0: User data is unprotected
1: Data is protected until the first device sign in/unlock and will be unprotected after that
2: Data is protected until first device sign in and when the device screen is locked, and available at other times.
Based on these availability levels, a file can be unprotected by changing the UserDataAvailability value to 0. The unprotection method for buffers is explicit because the buffers are not available when protected, so there is a function in the API for unprotecting the buffer.
<code>
async void UnprotectBuffer(String g_protectbase64EncodedContent)
{
var protectedBuffer = CryptographicBuffer.DecodeFromBase64String(protectbase64EncodedContent);
try
{
var result = await dataProtectionManager.UnprotectBufferAsync(protectedBuffer);
if (result.Status == UserDataBufferUnprotectStatus.Succeeded)
{
String unprotectedText = CryptographicBuffer.ConvertBinaryToString(BinaryStringEncoding.Utf8, result.UnprotectedBuffer);
LogLine(“Result of Unprotecting the buffer:” + unprotectedText
);
bufferOutputTextBox.Text = “”;
bufferOutputTextBox.Text = unprotectedText;
LogLine(“Status of Unprotecting the buffer:” + result.Status);
}
else
{
LogLine(“This protected buffer is currently unavailable for unprotection”);
}
}
catch(NullReferenceException nrex)
{
LogLine(“PDE not enabled on the device, please enable before proceeding!!”);
LogLine(nrex.ToString());
}
catch(Exception ex)
{
LogLine(“Please verify first the input text provided for unprotecting!”);
LogLine(ex.ToString());
} }
</code>
Buffer unprotection statuses
UserDataBufferUnprotectStatus is the enum that carries the status of unprotection performed on the buffer. In the example above, this status is used to log the appropriate status. There are two members in this enum:
Succeeded(0): Unprotecting the provided buffer succeeded and the result buffer is available in UnprotectedBuffer member
Unavailable(1): Unprotecting the provided buffer is not possible as the protected data is currently unavailable.
Below is the code snippet on how to listen to the event. In this example, the event is listened to when the form is loaded.
<code>
private void Form2_load(object sender, EventArgs e)
{
dataProtectionManager = UserDataProtectionManager.TryGetDefault();
if (dataProtectionManager == null)
{
LogLine(“Personal Data Encryption is not supported or enabled. Restart this app to check again.”);
}
else
{
LogLine(“Personal Data Encryption is enabled.”);
dataProtectionManager.DataAvailabilityStateChanged += (s, M_udpm_DataAvailabilityStateChanged) => {
LogCurrentDataAvailability();
LogLine(“Listening to DataAvailabilityStateChanged event”);
};
} }
private void M_udpm_DataAvailabilityStateChanged(UserDataProtectionManager sender, UserDataAvailabilityStateChangedEventArgs args)
{
LogLine(“DataAvailabilityStateChanged event received”);
LogCurrentDataAvailability();
}
</code>
This class is earmarked for future updates of the API.
Continue the conversation. Find best practices. Bookmark the Windows Tech Community, then follow us @MSWindowsITPro on X and on LinkedIn. Looking for support? Visit Windows on Microsoft Q&A.
Microsoft Tech Community – Latest Blogs –Read More