6

I'm using DPAPI in C++ to encrypt some data that I need to store in a file. The thing is that I need to read that file from C#, so I need to be able to:

C++ encrypt, C++ decrypt (is working good)

C# encrypt, C# decrypt (is working good)

C++ encrypt, C# decrypt and vice-versa (not working)

In C# I'm using DllImport to pInvoke the methods CryptProtectData and CryptUnprotectData, and I implement them as explained here. I know that in C# I can use the methods contained in the ProtectedData class but I'm doing it in this way (using DllImport ) to make sure that both codes (c++ and c#) look and work pretty much the same.

Now the weird thing is that even if both codes looks the same I get different outputs, for example for this text:

"plain text"

in C++ I get:

01 00 00 00 D0 8C 9D DF 01 15 D1 11 8C 7A 00 C0 4F C2 97 EB 01 00 00 00 2E 6F 88 86 E6 16 9B 4F 9B BF 35 DA 9F C6 EC 12 00 00 00 00 02 00 00 00 00 00 03 66 00 00 A8 00 00 00 10 00 00 00 93 06 68 39 DB 58 FE E9 C4 1F B0 3D 7B 0A B7 48 00 00 00 00 04 80 00 00 A0 00 00 00 10 00 00 00 36 4E 84 05 0D 4A 34 15 97 DC 5B 1F 6C A4 19 D9 10 00 00 00 F5 33 9F 55 49 94 26 54 2B C8 CB 70 7B FE EC 96 14 00 00 00 C5 23 DA BA C8 23 6C 0B B3 88 69 06 00 95 29 AE 76 A7 63 E4

and in C# I get:

01 00 00 00 D0 8C 9D DF 01 15 D1 11 8C 7A 00 C0 4F C2 97 EB 01 00 00 00 2E 6F 88 86 E6 16 9B 4F 9B BF 35 DA 9F C6 EC 12 00 00 00 00 02 00 00 00 00 00 03 66 00 00 A8 00 00 00 10 00 00 00 34 C4 40 CD 91 EC 94 66 E5 E9 23 F7 9E 04 9C 83 00 00 00 00 04 80 00 00 A0 00 00 00 10 00 00 00 12 54 1E 26 72 26 0A D1 11 1D 4D EF 13 1D B2 6F 10 00 00 00 81 9D 46 37 D1 68 5D 17 B8 23 78 48 18 ED 06 ED 14 00 00 00 E4 45 07 1C 08 55 99 80 A4 59 D9 33 BC 0B 71 35 39 05 C4 BB

As you can see the first characters are the same but the rest are not, so if anyone has an idea of why this may be happening, I will appreciate the help.

Thanks.

Code in C++:


value = "plain text";
DATA_BLOB DataIn;
DATA_BLOB DataOut;

BYTE *pbDataInput =(BYTE *)(char*)value.c_str();
DWORD cbDataInput = strlen((char *)pbDataInput)+1;
DataIn.pbData = pbDataInput; 
DataIn.cbData = cbDataInput;

CryptProtectData(&DataIn, NULL, NULL, NULL, NULL, 0, &DataOut))

Code in C#:

(you can see how my C# code looks here, since is identical to the one in this Microsoft example )

Vic
  • 2,878
  • 4
  • 36
  • 55
  • 2
    Some formatted hex editor like data example instead of those screenshots would be nice. – schnaader Sep 18 '09 at 22:22
  • Can't make out the screenshots. – Newton Falls Sep 18 '09 at 22:26
  • @Newton: You can open the link to the graphic in a seperate tab to have a better look at it (in Firefox "Show graphic" works, too) – schnaader Sep 18 '09 at 22:29
  • @schnaader - Thanks, not that there is much to see! – Newton Falls Sep 18 '09 at 22:35
  • And if you don't know how to do a hex dump, you can still use the command line: Post the first few lines of "fc /b cred.ini cred2.ini" – schnaader Sep 18 '09 at 22:37
  • thanks, I must apologize I thought that maybe the images where going to be helpful but you are right, the hex text is better. – Vic Sep 18 '09 at 22:55
  • 1
    A block chaining encryption with a different IV every time would create a different blob for the same data. That would not surprise me. – Steve Gilham Sep 18 '09 at 23:08
  • Thanks Steve, you were correct, the algorithm generates different outputs every time, no matter if I only use C++ or C#, I didn't realized this until now. – Vic Sep 21 '09 at 17:29
  • Possibly related: http://stackoverflow.com/questions/24386336/do-i-need-to-store-key-for-cryptunprotectdata-and-cryptprotectdata – Cristian Amarie Apr 11 '16 at 04:59

1 Answers1

5

It would help if you could post your C++ and your C# code. Perhaps there are some subtle parameter differences or something like this. For example, you should make sure that the pOptionalEntropy parameter is the same (or set it to NULL to test if this is the error source). Also, make sure to try to encrypt and decrypt on the same PC:

[...]decryption usually can only be done on the computer where the data was encrypted

(Source: MSDN)

EDIT: Some comments on the code you posted and the C# version from MSDN (parts of it following):

public byte[] Encrypt(byte[] plainText, byte[] optionalEntropy) {
  [...]
  int bytesSize = plainText.Length;
  plainTextBlob.pbData = Marshal.AllocHGlobal(bytesSize);
  plainTextBlob.cbData = bytesSize;
  Marshal.Copy(plainText, 0, plainTextBlob.pbData, bytesSize);
  [...]
  dwFlags = CRYPTPROTECT_LOCAL_MACHINE|CRYPTPROTECT_UI_FORBIDDEN;
  [...]
  if(null == optionalEntropy)
  {//Allocate something
  optionalEntropy = new byte[0]; // Is copied to entropyBlob later
  }
  [...]
  retVal = CryptProtectData(ref plainTextBlob, "", ref entropyBlob,    
    IntPtr.Zero, ref prompt, dwFlags, 
    ref cipherTextBlob);
  [...]
}

And here's your C++ code again to have both in view:

[...]
BYTE *pbDataInput =(BYTE *)(char*)value.c_str();
DWORD cbDataInput = strlen((char *)pbDataInput)+1;
[...]
CryptProtectData(&DataIn, NULL, NULL, NULL, NULL, 0, &DataOut))

The parameters don't match and I think that's the source of the differences.

The first thing is the flags. The C# code uses dwFlags != 0, your C++ code uses dwFlags = 0, so this is clearly a difference.

I'm not sure about the entropy. If you didn't pass optionalEntropy = null it is a difference, but if it is null, there's a "new byte[0]" assignment and I'm not sure about what this will create, but I think you should at least try to pass IntPtr.Zero instead of entropyBlob to CryptProtectData to match with the C++ code.

Last, but not least, your C++ code includes the trailing NUL that delimits the C string, I don't know how the encryption used here works but there are encryptions that will give you very different outputs if one byte is different (or you have one more byte like in this case), so you should either include a terminating NUL in the C# code or remove it in the C++ code.

schnaader
  • 49,103
  • 10
  • 104
  • 136