寻求Windows 10环境下与C# DPAPI DataProtector兼容的Python 3实现方案
Python Implementation for .NET DataProtector Compatibility (Windows 10)
Alright, let's solve this problem. To get Python code that produces encrypted tokens fully compatible with your .NET DataProtector (running on Windows 10), we need to mirror how .NET uses Windows DPAPI (Data Protection API) and its specific cryptographic workflow. Here's a practical, working solution:
Required Libraries
You'll need two packages to replicate .NET's behavior:
pywin32: To call Windows' native DPAPI directlycryptography: To handle AES-GCM encryption (the algorithm .NET uses under the hood)
Install them via pip:
pip install pywin32 cryptography
Background on Your .NET Code
First, let's break down what your C# code does to ensure full compatibility:
- Creates a local directory to store a master encryption key, which is itself encrypted with DPAPI (only accessible to the current Windows user)
- Derives a unique sub-key from the master key using your
<example_purpose>string - Uses AES-GCM to encrypt/decrypt the token, wrapping the result in .NET's specific data format
Python Compatibility Class
This class will load the existing .NET master key, derive the same sub-key, and encrypt/decrypt tokens in a format your C# code can read seamlessly:
import os import xml.etree.ElementTree as ET import win32crypt from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC import base64 from win32com.shell import shell, shellcon class DataProtector: def __init__(self, purpose): # Match the LocalApplicationData path used in your C# code self.local_app_data = shell.SHGetFolderPath(0, shellcon.CSIDL_LOCAL_APPDATA, None, 0) self.key_dir = os.path.join(self.local_app_data, "<example_dir>") self.purpose = purpose.encode("utf-8") self.master_key = self._load_decrypted_master_key() def _load_decrypted_master_key(self): # Find the latest .NET key file (NET generates multiple keys over time) key_files = [f for f in os.listdir(self.key_dir) if f.endswith(".xml")] if not key_files: raise FileNotFoundError("No .NET DataProtection keys found - run your C# code first to generate them") # Sort to grab the most recent key key_files.sort(key=lambda x: os.path.getmtime(os.path.join(self.key_dir, x)), reverse=True) key_path = os.path.join(self.key_dir, key_files[0]) # Parse the XML key file to extract the DPAPI-encrypted master key tree = ET.parse(key_path) root = tree.getroot() encrypted_key_b64 = root.find(".//encryptedKey").text encrypted_key = base64.b64decode(encrypted_key_b64) # Decrypt the master key using Windows DPAPI (current user context only) return win32crypt.CryptUnprotectData(encrypted_key, None, None, None, 0)[1] def _derive_subkey(self): # Replicate .NET's sub-key derivation logic exactly kdf = PBKDF2HMAC( algorithm=hashes.SHA256(), length=32, salt=self.purpose, iterations=10000, backend=default_backend() ) return kdf.derive(self.master_key) def protect(self, plaintext_token): # Convert token to bytes for encryption plaintext = plaintext_token.encode("utf-8") subkey = self._derive_subkey() # Use AES-GCM (same as .NET) with a 12-byte nonce (GCM standard) nonce = os.urandom(12) cipher = Cipher(algorithms.AES(subkey), modes.GCM(nonce), backend=default_backend()) encryptor = cipher.encryptor() ciphertext = encryptor.update(plaintext) + encryptor.finalize() # Package data in .NET's exact format: [version byte] + [nonce] + [ciphertext] + [GCM tag] version = b"\x01" encrypted_data = version + nonce + ciphertext + encryptor.tag return base64.b64encode(encrypted_data).decode("utf-8") def unprotect(self, encrypted_token_b64): encrypted_data = base64.b64decode(encrypted_token_b64) # Validate the encryption version (NET uses 0x01 for this format) if encrypted_data[0] != 0x01: raise ValueError("Unsupported encryption version - ensure this is a .NET DataProtector token") # Split the encrypted data into its components nonce = encrypted_data[1:13] tag = encrypted_data[-16:] ciphertext = encrypted_data[13:-16] subkey = self._derive_subkey() cipher = Cipher(algorithms.AES(subkey), modes.GCM(nonce, tag), backend=default_backend()) decryptor = cipher.decryptor() plaintext = decryptor.update(ciphertext) + decryptor.finalize() return plaintext.decode("utf-8")
Usage Example
# Initialize with the exact same purpose string as your C# code protector = DataProtector("<example_purpose>") # Encrypt a token (output will be readable by your C# DataProtector) encrypted_token = protector.protect("my_secure_token_456") print("Encrypted token:", encrypted_token) # Decrypt a token (works with tokens from either Python or C#) decrypted_token = protector.unprotect(encrypted_token) print("Decrypted token:", decrypted_token)
Critical Notes
- Run Your C# Code First: This Python class relies on the master key your .NET code creates in
<example_dir>. If you need Python to generate compatible keys from scratch, you'd need to implement .NET's XML key storage format (adding extra complexity). - Same Windows User: Since DPAPI uses current-user encryption, the Python code must run under the exact same Windows user account as your C# code.
- Format Compliance: The code strictly follows .NET's encrypted data structure, so tokens generated here will work flawlessly with your existing C#
DataProtector.Unprotectcall.
内容的提问来源于stack exchange,提问作者Zenek
相关产品推荐
相关产品推荐

