Exploring the Windows Data Protection API (DPAPI) from Zig
This project features a little command line utility which reads encrypted data from stdin and writes to stdout.
The Microsoft Windows Data Protection API (DPAPI) features functions for encrypting/wrapping/protecting and decrypting/unwrapping/unprotecting data, namely CryptProtectData and CryptUnprotectData. These functions are defined in dpapi.h
.
The following command compiles a small CLI application (18kB on Windows):
zig build -Doptimize=ReleaseSmall
Then you pipe the contents of an encrypted file into the executable, and process the output like you wish. For example, on Windows, the Azure CLI stores all management tokens as JSON structure in an encrypted file in my .azure
directory. The following pipeline pipes that file into the decryption utility and uses JQ to pretty-print parts of the JSON.
type %USERPROFILE%\.azure\msal_token_cache.bin | .\zig-out\bin\dpapi.exe unwrap | jq.exe ".RefreshToken"
With the checked-in binaries, you can also do round-trip:
echo Hallo Welt | .\dpapi.exe wrap | .\dpapi.exe unwrap
gives
C:\github\chgeuer\Windows-DPAPI-with-Zig (main -> origin)
λ echo Hallo Welt | .\dpapi.exe wrap | .\dpapi.exe unwrap
Hallo Welt
C:\github\chgeuer\Windows-DPAPI-with-Zig (main -> origin)
λ
The C# version would be along these lines (using System.Security.dll
):
using System;
using System.Security.Cryptography;
using System.Text;
using System.IO;
internal class Program
{
static void Main(string[] args)
{
var input = "Hallo, Welt!";
byte[] wrapped = Encrypt(input);
File.WriteAllBytes(@"C:\Users\chgeuer\Desktop\zig2\dpapi_encrypted.bin", wrapped);
Console.WriteLine(Convert.ToBase64String(wrapped));
Console.WriteLine(Decrypt(wrapped));
}
private static byte[] Encrypt(string userData) =>
ProtectedData.Protect(
userData: Encoding.UTF8.GetBytes(userData),
optionalEntropy: null,
scope: DataProtectionScope.CurrentUser);
private static string Decrypt(byte[] wrapped) =>
Encoding.UTF8.GetString(
ProtectedData.Unprotect(
encryptedData: wrapped,
optionalEntropy: null,
scope: DataProtectionScope.CurrentUser));
}