Python与UE5.1 C++跨语言调用DPAPI加解密报错87咨询
DPAPI跨Python与UE C++加解密错误(错误码87)
环境说明
使用Python 3.11.0与Unreal Engine 5.1的C++开发环境。
问题描述
在Python中用DPAPI加密用户输入后,在UE C中调用CryptUnprotectData解密返回错误码87(无效参数);反过来用C加密、Python解密也会报同样错误。但同语言内的加解密操作完全正常。
Python代码
# DPAPI access library # This file uses code originally created by Crusher Joe: # http://article.gmane.org/gmane.comp.python.ctypes/420 # And modified by Wayne Koorts: # http://stackoverflow.com/questions/463832/using-dpapi-with-python from ctypes import * from ctypes.wintypes import DWORD from getpass import getpass import os ## LocalFree = windll.kernel32.LocalFree memcpy = cdll.msvcrt.memcpy CryptProtectData = windll.crypt32.CryptProtectData CryptUnprotectData = windll.crypt32.CryptUnprotectData my_dir = 'C:\\Users\\User\\Desktop\\PythonCrypt' file_name = 'EncryptedToken.txt' encrypted_file_path = os.path.join(my_dir, file_name) class DATA_BLOB(Structure): _fields_ = [("cbData", DWORD), ("pbData", POINTER(c_char))] def getData(blobOut): cbData = int(blobOut.cbData) pbData = blobOut.pbData buffer = c_buffer(cbData) memcpy(buffer, pbData, cbData) LocalFree(pbData) return buffer.raw def Win32CryptProtectData(plainText): bufferIn = c_buffer(plainText, len(plainText)) blobIn = DATA_BLOB(len(plainText), bufferIn) blobOut = DATA_BLOB() if CryptProtectData(byref(blobIn), None, None, None, None, 0, byref(blobOut)): return getData(blobOut) else: print("CryptProtectData failed, Error: " + str(GetLastError())) print("Returning an empty string") return "" def Win32CryptUnprotectData(cipherText): bufferIn = c_buffer(cipherText, len(cipherText)) blobIn = DATA_BLOB(len(cipherText), bufferIn) blobOut = DATA_BLOB() if CryptUnprotectData(byref(blobIn), None, None, None, None, 0, byref(blobOut)): print("CryptUnprotectData successful") return getData(blobOut) else: print("CryptUnprotectData failed, Error: " + str(GetLastError())) print("Returning an empty string") return "" # Encrypts bytes and saves to file def cryptData(file_path, text_bytes): WriteBytesToFile(file_path, Win32CryptProtectData(text_bytes)) # Reads byte file and returns decrypted bytes def decryptData(file_path): readFile = ReadBytesFromFile(file_path) return Win32CryptUnprotectData(readFile) def WriteBytesToFile(file_path, bytes_to_write): with open(file_path, "wb") as wf: wf.write(bytes_to_write) def ReadBytesFromFile(file_path): with open(file_path, "rb") as rf: return rf.read() if __name__ == '__main__': # Prompt user for string password Password = getpass("Enter your password: ") # Convert string to bytes, and encrypt cryptData(encrypted_file_path, bytes(Password, 'utf-8'))
UE C++代码
- XLOG为替代UE_LOG的日志宏
- 仅保留Encrypt代码作完整性展示,实际仅运行Decrypt()
#define ENCRYPTED_TOKEN_FILE_NAME TEXT("EncryptedToken.txt") bool CryptUtil::Encrypt(FString InString) { // Encrypt data from DATA_BLOB DataIn to DATA_BLOB DataOut DATA_BLOB DataIn, DataOut; TArray<uint8> Data; const int32 Size = InString.Len(); Data.AddUninitialized(Size); // Create entropy data blob DATA_BLOB entropyBlob; // Convert FString to BYTE StringToBytes(*InString, Data.GetData(), Size); // Declare and initialize the DataIn structure BYTE* PbDataInput = Data.GetData(); const DWORD CBDataInput = Size; DataIn.pbData = PbDataInput; DataIn.cbData = CBDataInput; // Begin protect phase if (CryptProtectData( &DataIn, // Unencrypted data in nullptr, // Optional description string nullptr, // Optional entropy nullptr, // Reserved nullptr, // Optional prompt struct 0, // Flags &DataOut)) // Encrypted data out { // If we're saving a new token, delete any existing encrypted files const FString TokenFile = GetEncryptedTokenFile(); if (!TokenFile.IsEmpty()) { if (FPaths::ValidatePath(TokenFile) && FPaths::FileExists(TokenFile)) { IFileManager& FileManager = IFileManager::Get(); FileManager.Delete(*TokenFile); } } // Convert from FString const char* EncryptedTokenFile = StringCast<ANSICHAR>(*TokenFile).Get(); // Write encrypted data out to file std::ofstream EncryptedFile(EncryptedTokenFile, std::ios::out | std::ios::binary); EncryptedFile.write(reinterpret_cast<char*>(&DataOut.cbData), sizeof(DataOut.cbData)); EncryptedFile.write(reinterpret_cast<char*>(DataOut.pbData), DataOut.cbData); EncryptedFile.close(); // Release memory SecureZeroMemory(DataOut.pbData, DataOut.cbData); LocalFree(DataOut.pbData); } else { XLOG(LogConsoleResponse, Error, TEXT("Encryption error using CryptProtectData.")); return false; } return true; } bool CryptUtil::Decrypt(FString& OutString) { // Decrypt data from DATA_BLOB DataIn to DATA_BLOB DataOut DATA_BLOB DataIn, DataOut; // Create entropy data blob DATA_BLOB entropyBlob; // Convert from FString const char* EncryptedTokenFile = StringCast<ANSICHAR>(*GetEncryptedTokenFile()).Get(); std::ifstream ReadEncryptedFile(EncryptedTokenFile, std::ios::in | std::ios::binary); if (!ReadEncryptedFile.is_open()) { XLOG(LogConsoleResponse, Error, TEXT("Cannot open {%s}."), *GetEncryptedTokenFile()); return false; } // Read encrypted data from file ReadEncryptedFile.read(reinterpret_cast<char*>(&DataIn.cbData), sizeof(DataIn.cbData)); DataIn.pbData = new BYTE[DataIn.cbData]; ReadEncryptedFile.read(reinterpret_cast<char*>(DataIn.pbData), DataIn.cbData); // Begin unprotect phase. if (CryptUnprotectData( &DataIn, // Encrypted data in nullptr, // Optional description string nullptr, // Optional entropy nullptr, // Reserved nullptr, // Optional prompt struct 0, // Flags &DataOut)) // Unncrypted data out { // Convert BYTE to FString OutString = BytesToString(DataOut.pbData, DataOut.cbData); XLOG(LogConsoleResponse, Verbose, TEXT("CryptUnprotectData was successful!")); // Release memory SecureZeroMemory(DataOut.pbData, DataOut.cbData); LocalFree(DataOut.pbData); } else { XLOG(LogConsoleResponse, Error, TEXT("CryptUnprotectData failed, Error: %s"), *GetLastError()); return false; } return true; } FString CryptUtil::GetEncryptedTokenFile() { const FString tokenFile = FPaths::Combine("C:\\Users\\User\\Desktop\\PythonCrypt", ENCRYPTED_TOKEN_FILE_NAME); return tokenFile; }
已尝试方案
- 将文件保存为文本文件而非二进制文件,无效
- 确认同语言内加解密正常,仅跨语言操作报错
问题根源与修复方案
核心原因
跨语言加解密失败是因为Python和C++对加密数据的存储/读取格式不一致:
- Python仅存储
DATA_BLOB的pbData(加密后的原始字节流) - C++存储的是
cbData(数据长度) +pbData的组合数据
两边的DATA_BLOB结构不匹配,导致CryptUnprotectData收到无效参数,触发错误87。
修复方案(二选一即可)
方案1:统一存储为「仅加密字节流」
修改C++代码,让加密时只写入pbData,解密时直接读取整个文件作为pbData:
// 修改Encrypt函数的存储部分 std::ofstream EncryptedFile(EncryptedTokenFile, std::ios::out | std::ios::binary); // 去掉写入cbData的代码,仅写入pbData EncryptedFile.write(reinterpret_cast<char*>(DataOut.pbData), DataOut.cbData); EncryptedFile.close(); // 修改Decrypt函数的读取部分 std::vector<BYTE> encryptedData(std::istreambuf_iterator<char>(ReadEncryptedFile), {}); DataIn.cbData = encryptedData.size(); DataIn.pbData = encryptedData.data();
方案2:统一存储为「cbData + pbData」
修改Python代码,加密时先写入数据长度,解密时先读取长度再读取对应字节:
import struct # 修改cryptData函数 def cryptData(file_path, text_bytes): encrypted_bytes = Win32CryptProtectData(text_bytes) with open(file_path, "wb") as wf: # 以小端序写入DWORD类型的长度(和Windows字节序一致) wf.write(struct.pack('<L', len(encrypted_bytes))) wf.write(encrypted_bytes) # 修改decryptData函数 def decryptData(file_path): with open(file_path, "rb") as rf: # 先读取4字节的长度 cbData = struct.unpack('<L', rf.read(4))[0] # 读取对应长度的加密数据 cipherText = rf.read(cbData) return Win32CryptUnprotectData(cipherText)
额外注意事项
- 确保两边DPAPI的调用标志一致(当前均为0,无需修改)
- 加密时的熵参数保持一致(当前均为nullptr,无需修改)
- 字节序必须统一:Windows的
DWORD为小端序,Python打包时需指定<L
内容的提问来源于stack exchange,提问作者MashedPotato6587
相关产品推荐
相关产品推荐

