How to get the size of encrypted data?
I have tricky question and I hope I can explain it well, I want to read value from windows registry which is saved by another program that I don’t have its source, but I already know the type of this value and it’s lik开发者_开发问答e this:
_MyData = record
byteType: Byte;
encData: PByte;
end;
byteType indicates the type of this data as integer (1,2,3…) you can forget about this parameter, while encData is an encrypted data using the windows crypt32.dll function (CryptProtectData) I use the next code to read the value from registry:
procedure TForm1.Button2Click(Sender: TObject);
var
myData: _MyData;
reg: TRegistry;
valueSize: Integer;
begin
reg := TRegistry.Create;
try
if reg.OpenKey(KEY_PATH,false) then
Begin
valueSize := reg.GetDataSize(VALUE_NAME);
reg.ReadBinaryData(VALUE_NAME, myData, valueSize);
End;
finally
reg.Free;
end;
end;
// KEY_PATH, VALUE_NAME are string Consts.
So, now I have the encrypted data in myData.encData and now I want decrypt it by passing it the CryptUnprotectData function which has this signature:
function CryptUnprotectData(pDataIn: PDATA_BLOB; ppszDataDescr: PLPWSTR; pOptionalEntropy: PDATA_BLOB; pvReserved: Pointer; pPromptStruct: PCRYPTPROTECT_PROMPTSTRUCT; dwFlags: DWORD; pDataOut: PDATA_BLOB): BOOL; stdcall;
First I need to put the encrypted data in a variable of type DATA_BLOB which has this structure:
_CRYPTOAPI_BLOB = record
cbData: DWORD;
pbData: PBYTE;
end;
DATA_BLOB = _CRYPTOAPI_BLOB;
PDATA_BLOB = ^DATA_BLOB;
pbData is the pointer to the encrypted data (I read it from registry), and cbData is the size of the encrypted data, and here is my problem I have the pointer to encrypted data (I already read it from registry ) in myData.encData which is PByte, but I don’t know how to get the size of this data? and if i don't give the function CryptUnprotectData the right size it always gives nil in the outpout, Any idea how to do this?
Thanks for your help.
Edit:the solution, thanks to Ken Bourassa
_MyData = packed record
byteType: Byte;
encData: array of byte;
end;
procedure TForm1.Button2Click(Sender: TObject);
var
myData: ^_MyData;
reg: TRegistry;
valueSize: Integer;
dataIn, dataOut: DATA_BLOB;
begin
reg := TRegistry.Create;
try
if reg.OpenKey(KEY_PATH,false) then
Begin
valueSize := reg.GetDataSize(VALUE_NAME);
GetMem(myData, ValueSize);
try
reg.ReadBinaryData(VALUE_NAME, myData^, valueSize);
dataOut.cbData := 0;
dataOut.pbData := nil;
dataIn.cbData := Valuesize - SizeOf(Byte);
dataIn.pbData := @myData.encData;
CryptUnprotectData(@dataIn,nil,nil,nil,nil,CRYPTPROTECT_UI_FORBIDDEN,@dataOut);
//yes, it works, Thank you very much Ken Bourassa
finally
FreeMem(myData);
End;
End;
finally
reg.Free;
end;
end;
The size of the data is reg.GetDataSize
- SizeOf(Byte)
But that is now your only problem,
Your _MyData structure is 8 bytes long. So when you call
reg.ReadBinaryData(VALUE_NAME, myData, valueSize);
for any key value longer than 8 bytes, you have some buffer overflow happening. Even if you read a key shorter than 8 bytes, EncData will contain garbage.
I'd rather go this way:
_MyData = packed record
byteType: Byte;
encData: array[0..MaxInt] of byte;
end
procedure TForm1.Button2Click(Sender: TObject);
var
myData: ^_MyData;
reg: TRegistry;
valueSize: Integer;
begin
reg := TRegistry.Create;
try
if reg.OpenKey(KEY_PATH,false) then
Begin
valueSize := reg.GetDataSize(VALUE_NAME);
GetMem(MyData, ValueSize);
try
reg.ReadBinaryData(VALUE_NAME, myData^, valueSize);
//Do what is needed with MyData. The size of MyData.EncData = Valuesize - SizeOf(Byte)
finally
FreeMem(MyData);
end;
End;
finally
reg.Free;
end;
end;
I added the packed
keyword to the record definition as I would believe it's the most likely way it should be declared... But then, it's all up to the specifications of the application writting the values. I also declared EncData as Array[0..MaxInt] of byte
. That's makes it a VERY bad idea to declare a variable of type _MyData, but is the only way I know of allowing a variable array in a record without forcing to disable range checking. If always running with Range Checking = False in your project is not a concerned (Or you don't mind switching it on/off where needed in your code), you could declare it as Array[0..0] of byte
. I know it should work, I don't know the specifics of that method as I never really used it.
EDIT (After being accepted):
Actually, declaring EncData as Array of byte
is just as bad as declaring it as PByte. Array of byte
is a reference type (effectively a 4 bytes pointer). If you try to access EncByte[0] in your code, you'll most likely get an AV. The only reason it works is that you only use @myData.encData
(and the fact the record is allocated using GetMem instead). But used like this, you could declare EncData like this :
_MyData = packed record
byteType: Byte;
encData: Record end;
end
and this would still work, as, in your example, you don't really care about the type EncData is declared, you just care about the memory address where it is.
It looks strange to me the second field is a "PByte" value. Is it storing a pointer? A pointer is valid as long as a process is running, unless it is storing just a temporary value while the program is running (if so, strange choice). Or is it storing an arbitrary length buffer pointed to by that value? If it's storing first the type value, then the buffer, the buffer size is simply ValueSize - SizeOf(Byte), unless the record is not packed and it is "dumped" directly from memory, then there could be some filler bytes.
精彩评论