DPAPI On Windows

Hello,

Did anyone used DPAPI on Windows ?

I did tried to use declares and to try to get it work but so far it fails with error code 0 and nothing happens.

I guess Here you can find the details

Here is the code i tried

Function ProtectData(data As String) As MemoryBlock
  #If TargetWindows
    Declare Function CryptProtectData Lib "Crypt32.dll" (ByRef pDataIn As DATA_BLOB, szDataDescr As WString, pOptionalEntropy As Ptr, pvReserved As Ptr, pPromptStruct As Ptr, dwFlags As UInt32, ByRef pDataOut As DATA_BLOB) As Boolean
    Declare Sub MemoryCopy Lib "kernel32" Alias "RtlMoveMemory" (dest As Ptr, src As Ptr, length As UInt32)
    Declare Sub CoTaskMemFree Lib "ole32.dll" (ByVal pv As Ptr)
    Declare Function GetLastError Lib "kernel32" () As UInt32
    
    // Convert the string to a MemoryBlock
    Var dataMB As MemoryBlock = data
    // Prepare the input DATA_BLOB
    Var inBlob As DATA_BLOB
    inBlob.cbData = dataMB.Size
    inBlob.pbData = dataMB
    
    // Prepare the output DATA_BLOB
    Var outBlob As DATA_BLOB
    
    // Call CryptProtectData
    Var result As Boolean = CryptProtectData(inBlob, "", Nil, Nil, Nil, 0, outBlob)
    
    If result = False Then
      Var errorCode As UInt32 = GetLastError()
      // Use errorCode to determine the cause of the failure
      MessageBox("CryptProtectData failed with error code: " + errorCode.ToString)
      Return Nil
      
    Else
      // Ensure you copy the encrypted data from outBlob.pbData to encryptedData
      Var encryptedData As New MemoryBlock(outBlob.cbData)
      MemoryCopy(encryptedData, outBlob.pbData, outBlob.cbData)
      
      // Now you can free the allocated memory
      CoTaskMemFree(outBlob.pbData)
      
      Return encryptedData
      
    End If
  #EndIf
  
  
  
  
End Function

As structures i have

Struct DATA_BLOB
cbData As UInt32
pbData As Ptr
End Struct

I guess here could be a complete example but in C# that shows both functions .

Thanks

This seems strange to me. With functions that I’m familiar with, that return error codes, zero means success. Sorry, that’s all I can offer.

The code works for me on 32 bit and fails on 64 bit. I can get 64 bit to work if I set the StructureAlignment attribute of the DATA_BLOB structure to 8

Note how the members of the structure are no longer contiguous.

The usual 64 bit issue, Thanks a lot Andrew

Hello Gentlemen,

Andrew answered the question and this is a version converted from C++ to Xojo.

Here is an example which works in 32-bit and 64-bit mode:

Sub Pressed() Handles Pressed
  //Works in 32-bit and 64-bit phase
  TextArea1.Text = ""
  
  Var pbDataInput as string = "Hello world of data protection."
  Var mbDataInput as New MemoryBlock(pbDataInput.Length+1)
  mbDataInput.CString(0) = pbDataInput
  
  #If Target32Bit
    Var DataIn as DATA_BLOB_32
    Var DataOut as DATA_BLOB_32
    Var DataVerify as DATA_BLOB_32
  #Else //Target 64 bit
    Var DataIn as DATA_BLOB_64
    Var DataOut as DATA_BLOB_64
    Var DataVerify as DATA_BLOB_64
  #EndIf
  
  TextArea1.Text = TextArea1.Text + "DataIn: " + DataIn.Size.ToString + " bytes" + EndOfLine
  Var dbDataInput as UInt32 
  dbDataInput = pbDataInput.Length + 1
  TextArea1.Text = TextArea1.Text + "pbDataInput: " + dbDataInput.ToString + EndOfLine
  TextArea1.Text = TextArea1.Text + "mbDataInput: " + mbDataInput.Size.ToString + EndOfLine
  DataIn.pbData = mbDataInput
  DataIn.cbData = mbDataInput.Size
  
  Var PromptStruct as CRYPTPROTECT_PROMPTSTRUCT
  PromptStruct.cbSize = PromptStruct.Size
  PromptStruct.dwPromptFlags = &h02
  PromptStruct.szPrompt = "This is a user prompt."
  
  //Begin protect phase
  #If Target32Bit
    Declare Function CryptProtectData Lib "Crypt32.dll" (ByRef pDataIn As Data_Blob_32, szDataDescr As WString, pOptionalEntropy As Ptr, pvReserved As Ptr, ByRef pPromptStruct As CRYPTPROTECT_PROMPTSTRUCT, dwFlags As UInt32, ByRef pDataOut As Data_Blob_32) As Boolean
  #Else //64 bit
    Declare Function CryptProtectData Lib "Crypt32.dll" (ByRef pDataIn As Data_Blob_64, szDataDescr As WString, pOptionalEntropy As Ptr, pvReserved As Ptr, ByRef pPromptStruct As CRYPTPROTECT_PROMPTSTRUCT, dwFlags As UInt32, ByRef pDataOut As Data_Blob_64) As Boolean
  #EndIf
  
  If (CryptProtectData(DataIn, "This is the description string.", Nil, Nil, PromptStruct, 0, DataOut) = True) Then
    TextArea1.Text = TextArea1.Text + "The encryption phase worked." + EndOfLine
    TextArea1.Text = TextArea1.Text + "DataOut " + DataOut.cbData.ToString + EndOfLine
  Else
    TextArea1.Text = TextArea1.Text + "Encryption Error"
  End If
  
  //Begin the unprotect phase
  TextArea1.Text = TextArea1.Text + "Begin the unprotect phase" + EndOfLine
  #If Target32Bit
    Declare Function CryptUnprotectData Lib "Crypt32.dll" (ByRef pDataIn As Data_Blob_32, ppszDataDescr As WString, pOptionalEntropy As Ptr, pvReserved As Ptr, ByRef pPromptStruct As CRYPTPROTECT_PROMPTSTRUCT, dwFlags As UInt32, ByRef pDataOut As Data_Blob_32) As Boolean
  #Else //Target 64 bit
    Declare Function CryptUnprotectData Lib "Crypt32.dll" (ByRef pDataIn As Data_Blob_64, ppszDataDescr As WString, pOptionalEntropy As Ptr, pvReserved As Ptr, ByRef pPromptStruct As CRYPTPROTECT_PROMPTSTRUCT, dwFlags As UInt32, ByRef pDataOut As Data_Blob_64) As Boolean
  #EndIf
  
  Var pDescrOut as WString
  If (CryptUnprotectData(DataOut, pDescrOut, Nil, Nil, PromptStruct, 0, DataVerify) = True) Then
    Var mbOut as New MemoryBlock(DataVerify.Size)
    mbOut = DataVerify.pbData
    TextArea1.Text = TextArea1.Text + mbOut.CString(0) + EndOfLine
    System.DebugLog("Success")
  Else
    TextArea1.Text = TextArea1.Text + "Decryption Error"
    System.DebugLog("Error")
  End If
End Sub
Attributes( StructureAlignment = 4 ) Structure DATA_BLOB_32
  cbData as UInt32
  pbData as Ptr
End Structure
Attributes( StructureAlignment = 8 ) Structure DATA_BLOB_64
  cbData as UInt32
  pbData as Ptr
End Structure
Structure CRYPTPROTECT_PROMPTSTRUCT
  cbSize as UInt32
  dwPromptFlags as UInt32
  hwndApp as Integer
  szPrompt as WString
End Structure
1 Like

Makes no sense to me how things are being handled.

I would assume that if you set StructureAlignment = 0, the current ABI would assume 4 or 8 automatically (depending on the target platform being compiled), so one structure and one declare should do the job for both 32 and 64 bit targets.

If it doesn’t, for me there’s a bug.

And that brings me another question, what’s the current alignment default? For me it should be 0 instead of 4.

Yes, you are correct that setting the StructureAlignment to zero does presume the correct length, and one declare does work for both 32-bit and 64-bit targets. Here is the revised code:

Sub Pressed() Handles Pressed
  //Works in 32-bit and 64-bit phase
  TextArea1.Text = ""
  
  Var pbDataInput as string = "Hello world of data protection."
  Var mbDataInput as New MemoryBlock(pbDataInput.Length+1)
  mbDataInput.CString(0) = pbDataInput
  
  Var DataIn as DATA_BLOB
  Var DataOut as DATA_BLOB
  Var DataVerify as DATA_BLOB
  
  TextArea1.Text = TextArea1.Text + "DataIn: " + DataIn.Size.ToString + " bytes" + EndOfLine
  Var dbDataInput as UInt32 
  dbDataInput = pbDataInput.Length + 1
  TextArea1.Text = TextArea1.Text + "pbDataInput: " + dbDataInput.ToString + EndOfLine
  TextArea1.Text = TextArea1.Text + "mbDataInput: " + mbDataInput.Size.ToString + EndOfLine
  DataIn.pbData = mbDataInput
  DataIn.cbData = mbDataInput.Size
  
  Var PromptStruct as CRYPTPROTECT_PROMPTSTRUCT
  PromptStruct.cbSize = PromptStruct.Size
  PromptStruct.dwPromptFlags = &h02
  PromptStruct.szPrompt = "This is a user prompt."
  
  //Begin protect phase
  Declare Function CryptProtectData Lib "Crypt32.dll" (ByRef pDataIn As Data_Blob, szDataDescr As WString, pOptionalEntropy As Ptr, pvReserved As Ptr, ByRef pPromptStruct As CRYPTPROTECT_PROMPTSTRUCT, dwFlags As UInt32, ByRef pDataOut As Data_Blob) As Boolean
  If (CryptProtectData(DataIn, "This is the description string.", Nil, Nil, PromptStruct, 0, DataOut) = True) Then
    TextArea1.Text = TextArea1.Text + "The encryption phase worked." + EndOfLine
    TextArea1.Text = TextArea1.Text + "DataOut " + DataOut.cbData.ToString + EndOfLine
  Else
    TextArea1.Text = TextArea1.Text + "Encryption Error"
  End If
  
  //Begin the unprotect phase
  TextArea1.Text = TextArea1.Text + "Begin the unprotect phase" + EndOfLine
  Declare Function CryptUnprotectData Lib "Crypt32.dll" (ByRef pDataIn As Data_Blob, ppszDataDescr As WString, pOptionalEntropy As Ptr, pvReserved As Ptr, ByRef pPromptStruct As CRYPTPROTECT_PROMPTSTRUCT, dwFlags As UInt32, ByRef pDataOut As Data_Blob) As Boolean
  
  Var pDescrOut as WString
  If (CryptUnprotectData(DataOut, pDescrOut, Nil, Nil, PromptStruct, 0, DataVerify) = True) Then
    Var mbOut as New MemoryBlock(DataVerify.Size)
    mbOut = DataVerify.pbData
    TextArea1.Text = TextArea1.Text + mbOut.CString(0) + EndOfLine
    System.DebugLog("Success")
  Else
    TextArea1.Text = TextArea1.Text + "Decryption Error"
    System.DebugLog("Error")
  End If
End Sub
Attributes( StructureAlignment = 0 ) Structure DATA_BLOB
  cbData as UInt32
  pbData as Ptr
End Structure
Structure CRYPTPROTECT_PROMPTSTRUCT
  cbSize as UInt32
  dwPromptFlags as UInt32
  hwndApp as Integer
  szPrompt as WString
End Structure

I agree that the alignment default should be zero. If I remove the StructureAlignment value of 0, then I get an error.

Edit: Spelling

1 Like

Curiously, what error? I would expect it acting wrong as before assuming 32 bit in a target 64.

Or am I bad interpreting what you intended to say?

The error only occurs in 64-bit mode. The error is:

ERROR_ACCESS_DENIED
5 (0x5)
Access is denied.

Runtime error, Correct? Not compiler error.

Yes, you are correct that this is a runtime error.

1 Like

The 64 bit version got a smaller than expected structure (default as 32 bit we could infer), and trying to read regions outside the defined area raised the protection error. In the past it would just get some garbage and do random things. :rofl:

1 Like

Wouldn’t be:

Attributes( StructureAlignment = 0 ) Structure DATA_BLOB
  cbData as UInt32
  pbData as Ptr
End Structure

Attributes( StructureAlignment = 0 ) Structure CRYPTPROTECT_PROMPTSTRUCT
  cbSize as UInt32
  dwPromptFlags as UInt32
  hwndApp as Integer
  szPrompt as WString
End Structure

?

I see where you are going with the structure by adding StructureAlignment = 0 for CRYPTPROTECT_PROMPTSTRUCT. The program works in 32-bit and 64-bit mode with StructureAlignment = 0 or with it completely missing …

Edit: This is what makes structures confusing - In one instance StructureAlignment matters, and in another instance, it doesn’t matter.

It always matters, but sometimes, due to a coincidence, a structure just fits into another alignment.

Let it assume the proper target ABI alignment and don’t run the risk of it not fitting.

Observe the size and offsets changing with the alignment of the StrucDemo.

Aligned for 32 bit ABI:

Same structure aligned for 64 bit ABI:

But this one, aligned to 32 or 64 bit, does not need paddings and fits without changes in both requirements, both contain 16 bytes in the same places:

So just use align = 0 and let the compiler do it for you just changing the target.

Hello Eugene, That is super nice, it seems to be a complete example.

I do have one question or request, i don’t want any notification for the user but i need this to be done on that pc only, so is there a way to do that ?

The idea is to Create a needed password with DPAPI, on that pc, store it in a file with the restriction that it can only be used on that PC, i guess that is the idea with DPAPI, and if i need it , i just pass the encrypted password to CryptUnprotectData and get it , i guess i could store it on the drive in a file as HexEncoded.

Thanks

From what i read it seems that i need to set 2 constants :

Const CRYPTPROTECT_UI_FORBIDDEN = &h01
Const CRYPTPROTECT_LOCAL_MACHINE = &h04

and then i guess call the 2 methods with those 2 in .

I guess this would do.


//Works in 32-bit and 64-bit phase
TextArea1.Text = ""

Var pbDataInput As String = "Hello world of data protection."
Var mbDataInput As New MemoryBlock(pbDataInput.Length+1)
mbDataInput.CString(0) = pbDataInput

#If Target32Bit
  Var DataIn As DATA_BLOB_32
  Var DataOut As DATA_BLOB_32
  Var DataVerify As DATA_BLOB_32
#Else //Target 64 bit
  Var DataIn As DATA_BLOB_64
  Var DataOut As DATA_BLOB_64
  Var DataVerify As DATA_BLOB_64
#EndIf

TextArea1.Text = TextArea1.Text + "DataIn: " + DataIn.Size.ToString + " bytes" + EndOfLine
Var dbDataInput As UInt32 
dbDataInput = pbDataInput.Length + 1
TextArea1.Text = TextArea1.Text + "pbDataInput: " + dbDataInput.ToString + EndOfLine
TextArea1.Text = TextArea1.Text + "mbDataInput: " + mbDataInput.Size.ToString + EndOfLine
DataIn.pbData = mbDataInput
DataIn.cbData = mbDataInput.Size

Var PromptStruct As CRYPTPROTECT_PROMPTSTRUCT
PromptStruct.cbSize = PromptStruct.Size
'PromptStruct.dwPromptFlags = &h01
PromptStruct.dwPromptFlags = &h02
PromptStruct.szPrompt = "This is a user prompt."

Var flags As UInt32 = CRYPTPROTECT_UI_FORBIDDEN + CRYPTPROTECT_LOCAL_MACHINE

//Begin protect phase
#If Target32Bit
  Declare Function CryptProtectData Lib "Crypt32.dll" (ByRef pDataIn As Data_Blob_32, szDataDescr As WString, pOptionalEntropy As Ptr, pvReserved As Ptr, pPromptStruct As Ptr, dwFlags As UInt32, ByRef pDataOut As Data_Blob_32) As Boolean
#Else //64 bit
  Declare Function CryptProtectData Lib "Crypt32.dll" (ByRef pDataIn As Data_Blob_64, szDataDescr As WString, pOptionalEntropy As Ptr, pvReserved As Ptr, pPromptStruct As Ptr, dwFlags As UInt32, ByRef pDataOut As Data_Blob_64) As Boolean
#EndIf


If (CryptProtectData(DataIn, "This is the description string.", Nil, Nil, Nil, flags, DataOut) = True) Then
  TextArea1.Text = TextArea1.Text + "The encryption phase worked." + EndOfLine
  TextArea1.Text = TextArea1.Text + "DataOut " + DataOut.cbData.ToString + EndOfLine
Else
  TextArea1.Text = TextArea1.Text + "Encryption Error"
End If



//Begin the unprotect phase
TextArea1.Text = TextArea1.Text + "Begin the unprotect phase" + EndOfLine

#If Target32Bit
  Declare Function CryptUnprotectData Lib "Crypt32.dll" (ByRef pDataIn As Data_Blob_32, ppszDataDescr As WString, pOptionalEntropy As Ptr, pvReserved As Ptr, pPromptStruct As Ptr, dwFlags As UInt32, ByRef pDataOut As Data_Blob_32) As Boolean
#Else //Target 64 bit
  Declare Function CryptUnprotectData Lib "Crypt32.dll" (ByRef pDataIn As Data_Blob_64, ppszDataDescr As WString, pOptionalEntropy As Ptr, pvReserved As Ptr, pPromptStruct As Ptr, dwFlags As UInt32, ByRef pDataOut As Data_Blob_64) As Boolean
#EndIf

Var pDescrOut As WString
If (CryptUnprotectData(DataOut, pDescrOut, Nil, Nil, Nil, flags, DataVerify) = True) Then
  Var mbOut As New MemoryBlock(DataVerify.Size)
  mbOut = DataVerify.pbData
  TextArea1.Text = TextArea1.Text + mbOut.CString(0) + EndOfLine
  System.DebugLog("Success")
Else
  TextArea1.Text = TextArea1.Text + "Decryption Error"
  System.DebugLog("Error")
End If

Thanks again

The double approach is unnecessary as discussed. :wink: