Learn how to a build a cloud-first strategyRegister Now

x
  • Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 1228
  • Last Modified:

Windows Cryptography migration to .NET from VB6

I am trying to migrate some code from VB6 to .NET where the VB6 was calling Windows Crypto API.  .NET libraries dont match up 1 for 1, and i am not very experienced with cryptography.  Here are the functions that need to get converted:

CryptAcquireContext(hcsp, "strAppName", MS_DEF_PROV, PROV_RSA_FULL, 0)
CryptCreateHash(hcsp, CALG_MD5, 0, 0, hhash)
CryptHashDataString(hhash, pwd,Len(pwd), 0)
CryptDeriveKey(hcsp, CALG_RC2, hhash, CRYPT_EXPORTABLE, hkey)
CryptDestroyHash(hhash)
CryptDecrypt(hkey, 0, 1, 0, VarPtr(vbyteData(0)), lngLenDataBuf)
CryptDestroyKey hkey

How do i get from this, to .NET?  
or in worse case, get the VB6 code to function properly in .NET... the CryptDecrypt call always fails if i try this method.  CryptDecrypt(hkey, 0, 1, 0, vbytData, lngLenDataBuf),  vbytData() As Byte

Thanks in advance.
0
bowser17
Asked:
bowser17
  • 14
  • 10
1 Solution
 
DabasCommented:
Hello bowser17,

Have a look at the System.Security.Cryptography namespace

For example, System.Security.Cryptography.HashAlgorithm.CreateHash("MD5") will probbably replace your second line.

Regards,

Dabas
0
 
drichardsCommented:
The .NET wrapper for CryptDeriveKey is PasswordDeriveBytes.CryptDeriveKey.  Docs are at:

http://msdn2.microsoft.com/en-us/library/system.security.cryptography.passwordderivebytes.cryptderivekey.aspx

Then you can use RC2 class to encrypt/decrypt using the key.

There seem to be more options in the .NET version, so getting the two algorithms to be interchangeable might take some work.  You'll have to determine what values are used by default in the Crypto API version (key length, salt, IV, etc.) so you can set them correctly in the .NET version.
0
 
bowser17Author Commented:
I have gottent that far.  Its those details you mention that i need.  I have looked at the doc for .net.  I would really like to get some code.  I think i have provided enough info for someone who knows this stuff to do it.  I am not experienced enough with the crypto stuff in general, so this is why i am having difficulties.
0
What does it mean to be "Always On"?

Is your cloud always on? With an Always On cloud you won't have to worry about downtime for maintenance or software application code updates, ensuring that your bottom line isn't affected.

 
drichardsCommented:
>> I think i have provided enough info for someone who knows this stuff to do it
Yes, you have.  If I get some time I will try to put together some code for you.
0
 
bowser17Author Commented:
Here is what i have, that doesn't work...meaning it doesn't produce the correct decrypted data

        'setup CSP
        Dim csp As New System.Security.Cryptography.CspParameters(Providertype.PROV_RSA_FULL, MS_DEF_PROV, "strAppName")

        'Generate the Key
        Dim cdk As New System.Security.Cryptography.PasswordDeriveBytes(mstrPwd, Nothing, csp)
        Dim ivNull() As Byte = {0, 0, 0, 0, 0, 0, 0, 0}
        Dim key As Byte()
        key = cdk.CryptDeriveKey("RC2", "MD5", 0, ivNull)

        'Decrypt
        Dim rc2CSP As New System.Security.Cryptography.RC2CryptoServiceProvider
        Dim transform As System.Security.Cryptography.ICryptoTransform
        transform = rc2CSP.CreateDecryptor(key, ivNull)
        Dim vbytDataOut(vintLen - 1) As Byte
        transform.TransformBlock(vbytData, 0, vintLen, vbytDataOut, 0)

A few things i am unsure about.... what IV should i use?  I dont see anything about IV's in the Crypto API (original code)... how do i determine the key length?  
0
 
drichardsCommented:
The trouble seems to be with the salt.  I got the result to match (I changed your original code to do CryptEncrypt at the end since I didn't have anything to decode, but the encrypted strings match) with this:

            byte[] data = ASCIIEncoding.ASCII.GetBytes("password"); // Just for testing...
            PasswordDeriveBytes pdb = new PasswordDeriveBytes(data, null);
            byte[] iv = new byte[8]; // Auto-initialized to zeros
            byte[] key = pdb.CryptDeriveKey("RC2", "MD5", 40, iv);
            RC2CryptoServiceProvider rcsp = new RC2CryptoServiceProvider();
            rcsp.UseSalt = true;
            // Change these last two lines to do decryption instead.
            ICryptoTransform ict = rcsp.CreateDecryptor(key, iv);
            byte[] encodedData = ict.TransformFinalBlock(data, 0, 8);

Sorry about the C# code since you're working in VB.  Let me know if you need a translation.
0
 
drichardsCommented:
I also got the results to match by adding the CRYPT_NO_SALT flag to the CryptDeriveKey call in your otiginal code and leaving out the "UseSalt = true;" in my C# code.  That probably isn't an option for you if you already have things encrypted under the old code.
0
 
bowser17Author Commented:
I am getting Bad Data error with this.  I am going to verify the password.
0
 
bowser17Author Commented:
Also, i dont see UserSalt as a property of the crypto provider... what .net ver are you using?
0
 
drichardsCommented:
>>what .net ver are you using?
2.0
0
 
drichardsCommented:
I pulled up VS 2003 and sure enough there is no UseSalt option there.  I don't know how to get rid of the salt in the older .NET version.  You could put that question out there.  Otherwise you need to write something to go through and re-encode the data with the CRYPT_NO_SALT option or you will have to figure out how to ruin the old code in new VB.  As a C# guy I am not the one to help you with that part of things.
0
 
bowser17Author Commented:
Im a C# guy myself....  anyways, i believe the problem is with the password.
In the VB6 ver, the password is sent as a VB String to the API to create the hash.
So, i wrote the password to a binary file from VB6.
In .NET, i read in this file, but the data looks way different.  I guess its boiling down to data byte storage and how the API "sees" the string vs how .NET sees the string in its library.  THe problem is the password used to encode all of the data is not really a string type of data.  Its got all sorts of funky characters.  Any idea on how to make the two match up properly?

0
 
drichardsCommented:
Can you post an example of how you get the password and pass it?

The password MIGHT be part of the problem, but I can tell you it is not the whole problem.  I used the exact same password (with ASCII encoding as you see from my code) and passed it to the crypto API CryptDeriveKey and the .NET function CryptDeriveKey and got the same key.  You can verify by calling CryptoExportKey with the PLAINTEXT option to see what your key is.  The key is a direct output of CryptDeriveKey in the .NET code.  That is NOT the problem.

The problem is that if you pass that same key to CryptDecode and to CreateDecryptor, the result is different because of the salt.  .NET does not use salt by default and the crypto API does.  One or the other must be changed for the two to be compatible.
0
 
bowser17Author Commented:
Ok, but i understand what you are saying but do you not think i may have to do with the way VB passes the string to the API and the way .NET passes the string?  I will try the CryptoExport key...
0
 
drichardsCommented:
You should definitely check the generated key bytes since if the password usage is different then you will have to address that problem as well.  Even if that turns out to be an issue, fixing it will stil not get you to the final solution.  

I'm sorry, but I haven't had a chance to look at the .NET 1.1 framework in detail to see if there is a way past this salt issue.  Hopefully tomorrow.  As I said, you may want to post a more specific question about that to see if someone already knows how to deal with it.
0
 
bowser17Author Commented:
My .NET key and API key do not match.  Unless the key i am getting back from the API is encrypted...
I use this code:
    res = CryptExportKey(hkey, 0, PLAINTEXTKEYBLOB, 0, 0, keyLen)
    keystr = String(keyLen, 0)
    res = CryptExportKey(hkey, 0, PLAINTEXTKEYBLOB, 0, keystr, keyLen)
not that anyone would grab this data and use it, but i dont want to supply the key bytes its returning.  All i know is i took those bytes, and created a byte() in the .NET code, so basically bypassing the CryptDeriveKey function.  This still will not decrypt properly.  Acording to http://msdn2.microsoft.com/en-US/library/aa387695.aspx
the PasswordDeriveBytes should use an 11 byte long 0 val array as the salt.  Outside of this, i believe i am out of ideas and luck.
0
 
drichardsCommented:
Did you check the actual key value?  he last 5 bytes of the array returned by CryptExportKey are the key bytes.  These should compare to the bytes returned by the .NET CryptDeriveKey.

I have my work laptop with VB6 on it, so I will try it myelf tonight.

As for that link you posted, that is what I inferred from my testing.  The PasswordDerivedBytes uses the salt, as does the Encrypt/Decrypt functions unless you tell them not to.  So any data you currently have encrypted has used the salt.  Unfortunately, I cannot see a way to turn enable it in the .NET 1.1 code.  There salt is not used by default.
0
 
drichardsCommented:
If the keys DO turn out to be different, it may be that the VB code uses the unicode BSTR value.  The .NET code uses the ASCII encoding by default if you use the overload of CryptDeriveKey that takes the string directly.  To use the unicode string data in .NET you need to change:

          byte[] data = ASCIIEncoding.ASCII.GetBytes("password"); // Just for testing...

to

          byte[] data = UnicodeEncoding.Unicode.GetBytes("password"); // Just for testing...

in the code I posted.

Once you have verified the key, try setting the CRYPT_NO_SALT flag on the VB Decrypt.  It won't give you back the original data, but it should match the .NET output.  At that point you are correct in that you may be out of luck unless you can change to .NET 2.0.
0
 
drichardsCommented:
How were you calling the Crypto API from VB6, by the way?  I don't see a way to do it short of doing lots of Declare stuff.
0
 
bowser17Author Commented:
The follow is what is used, however im sure whoever wrote this code originally just copied this from somewhere.

Private Declare Function CryptAcquireContext Lib "advapi32.dll" Alias "CryptAcquireContextA" ( _
    phProv As Long, ByVal pszContainer As String, _
    ByVal pszProvider As String, _
    ByVal dwProvType As Long, ByVal dwFlags As Long) As Long

Private Declare Function CryptReleaseContext Lib "advapi32.dll" ( _
   ByVal hProv As Long, ByVal dwFlags As Long) As Long

Private Declare Function CryptGetProvParamString Lib "advapi32.dll" Alias "CryptGetProvParam" ( _
    ByVal hProv As Long, ByVal dwParam As Long, _
    ByVal pbData As String, pdwDataLen As Long, _
    ByVal dwFlags As Long) As Long

Private Declare Function CryptGetProvParam Lib "advapi32.dll" ( _
    ByVal hProv As Long, ByVal dwParam As Long, _
    pbData As Any, pdwDataLen As Long, _
    ByVal dwFlags As Long) As Long

Private Declare Function CryptGetUserKey Lib "advapi32.dll" ( _
    ByVal hProv As Long, ByVal dwKeySpec As Long, _
    phUserKey As Long) As Long

Private Declare Function CryptGenKey Lib "advapi32.dll" ( _
    ByVal hProv As Long, ByVal algid As Long, _
    ByVal dwFlags As Long, phKey As Long) As Long

Private Declare Function CryptDestroyKey Lib "advapi32.dll" (ByVal hkey As Long) As Long

Private Declare Function CryptCreateHash Lib "advapi32.dll" (ByVal hProv As Long, _
    ByVal algid As Long, _
    ByVal hkey As Long, _
    ByVal dwFlags As Long, _
    phHash As Long) As Long

' CryptHashData has both string and byte versions
Private Declare Function CryptHashDataString Lib "advapi32.dll" Alias "CryptHashData" ( _
    ByVal hhash As Long, _
    ByVal bData As String, _
    ByVal dwDataLen As Long, _
    ByVal dwFlags As Long) As Long

Private Declare Function CryptHashDataBytes Lib "advapi32.dll" Alias "CryptHashData" ( _
    ByVal hhash As Long, _
    bData As Byte, _
    ByVal dwDataLen As Long, _
    ByVal dwFlags As Long) As Long

' Note, this variation is only intended for use with HP_HASHVAL
Private Declare Function CryptGetHashParam Lib "advapi32.dll" ( _
    ByVal hhash As Long, _
    ByVal dwParam As Long, _
    ByVal pbData As String, _
    dwlngLenData As Long, _
    ByVal dwFlags As Long) As Long

Private Declare Function CryptGetHashParamSize Lib "advapi32.dll" Alias "CryptGetHashParam" ( _
    ByVal hhash As Long, _
    ByVal dwParam As Long, _
    pbData As Long, _
    dwlngLenData As Long, _
    ByVal dwFlags As Long) As Long

Private Declare Function CryptDeriveKey Lib "advapi32.dll" ( _
    ByVal hProv As Long, _
    ByVal algid As Long, _
    ByVal hBaseData As Long, _
    ByVal dwFlags As Long, _
    phKey As Long) As Long

Private Declare Function CryptDestroyHash Lib "advapi32.dll" (ByVal hhash As Long) As Long

Private Declare Function CryptGetKeyParam Lib "advapi32.dll" ( _
    ByVal hkey As Long, _
    ByVal dwParam As Long, _
    ByVal pbData As Long, _
    pdwDataLen As Long, _
    ByVal dwFlags As Long) As Long

Private Declare Function CryptSetKeyParam Lib "advapi32.dll" ( _
    ByVal hkey As Long, _
    ByVal dwParam As Long, _
    ByVal pbData As Long, _
    ByVal dwFlags As Long) As Long

Private Declare Function CryptExportKey Lib "advapi32.dll" ( _
    ByVal hkey As Long, ByVal hExpKey As Long, _
    ByVal dwBlobType As Long, ByVal dwFlags As Long, _
    ByVal pbData As String, pdwDataLen As Long) As Long

Private Declare Function CryptImportKey Lib "advapi32.dll" ( _
    ByVal hProv As Long, ByVal pbData As Long, _
    ByVal dwlngLenData As Long, ByVal hPubKey As Long, _
    ByVal dwFlags As Long, pKeyval As Long) As Long

Private Declare Function CryptEncrypt Lib "advapi32.dll" ( _
   ByVal hkey As Long, ByVal hhash As Long, _
   ByVal Final As Long, ByVal dwFlags As Long, _
   ByVal pData As Any, dwlngLenData As Long, ByVal dwBufLength As Long) As Long
   
Private Declare Function CryptDecrypt Lib "advapi32.dll" ( _
   ByVal hkey As Long, ByVal hhash As Long, _
   ByVal Final As Long, ByVal dwFlags As Long, _
   ByVal pData As Any, dwlngLenData As Long) As Long
   
Private Declare Function CryptSignHash Lib "advapi32.dll" Alias "CryptSignHashA" ( _
   ByVal hhash As Long, ByVal hkey As Long, _
   ByVal Description As Long, ByVal dwFlags As Long, _
   ByVal pData As Long, dwlngLenData As Long) As Long

Private Declare Function CryptVerifySignature Lib "advapi32.dll" Alias "CryptVerifySignatureA" ( _
   ByVal hhash As Long, ByVal pData As Long, _
   ByVal lngLenData As Long, ByVal PublicKey As Long, _
   ByVal Description As Long, ByVal dwFlags As Long) As Long

Private Declare Function CryptGenRandom Lib "advapi32.dll" (ByVal hProv As Long, _
    ByVal lngLenData As Long, _
    ByVal RandomData As String) As Long
0
 
drichardsCommented:
Using your Declare's (thanks - it would have taken me a while to duplicate that!) I was able to verify that the VB code generates the same key as the .NET code and can encrypt a string with the same result assuming .NET 2.0 with 'UseSalt = true;'.  I assume decrypt would behave similarly.

So the conclusion is that you need to do one of:

1) Switch to .NET 2.0 and set 'UseSalt=true;'
2) Re-encrypt any existing data with the no-salt option (allows .NET 1.1 or 2.0)
3) Figure out a way in .NET 1.x to turn on salt option.  I have not been able to figure out how.
0
 
bowser17Author Commented:
I thought 1.1 used salt by default?

Also, what password are you using?  have you tried it with a bunch of random chars?
0
 
drichardsCommented:
.NET (all versions I tested) does not use salt by default.  The basic Crypto API does.

I was using password "password" as a string.  I did not try random characters.  By random characters do you mean bytes or actual characters?  I ask because using a simple string like "password", both .NET and VB ultimately pass an ASCII encoding (8 bytes) as I confirmed by using the .NET overload which takes a byte array directly - see the code I posted earlier - and passing both the ASCII and Unicode versions.

You would have to be careful about anything else because I don't know what VB will do with it.  As long as consistency is maintained, the two answers should be the same.  How do you build this random character string in VB?  I can test if you have an example.
0
 
bowser17Author Commented:
Well, i never got it to work.  I know you spent alot of time, or at least appear to with prompt responses.  I'll give you the points, but I would still like to figure out if my theory is correct about the password string with non-standard bytes and how VB handles it versus .NET being passed into the same underlying API.
0
 
drichardsCommented:
If you can send me a couple of lines of VB code showing how you are filling a VB string with non-string data I can test your theory for you.

It's really irrelevant, though, since .NET and crypto API use incompatible defaults for the encryption functions and you will not be able to get the results to match no matter what password you are using unless you either a) Change the defaults for the crypto API (which will render any previously encrypted data useless) or b) figure out how to change the default for the .NET 1.1 functions (I have not looked too much at that since I have switched to .NET 2.0 some time ago).
0

Featured Post

Restore individual SQL databases with ease

Veeam Explorer for Microsoft SQL Server delivers an easy-to-use, wizard-driven interface for restoring your databases from a backup. No expert SQL background required. Web interface provides a complete view of all available SQL databases to simplify the recovery of lost database

  • 14
  • 10
Tackle projects and never again get stuck behind a technical roadblock.
Join Now