I'll start by saying that the following link helped me greatly:
Translating Win32 Crypto API calls to C# with System.Security.Cryptography
I'm having the same problem he's having, except his fix didn't fix my code.
Here's my old (vb6) code:
Private Const ALG_CLASS_DATA_ENCRYPT = 24576&
Private Const ALG_CLASS_HASH = 32768
Private Const ALG_SID_3DES = 3&
Private Const ALG_SID_SHA1 = 4&
Private Const ALG_TYPE_ANY = 0&
Private Const ALG_TYPE_BLOCK = 1536&
Private Const CALG_3DES = (ALG_CLASS_DATA_ENCRYPT Or ALG_TYPE_BLOCK Or ALG_SID_3DES)
Private Const CALG_SHA1 = (ALG_CLASS_HASH Or ALG_TYPE_ANY Or ALG_SID_SHA1)
Private Const CRYPT_SILENT = &H40&
Private Const CRYPT_VERIFYCONTEXT = &HF0000000
Private Const MS_ENHANCED_PROV = "Microsoft Enhanced Cryptographic Provider v1.0"
Private Const PROV_RSA_FULL = 1&
Private Declare Function CryptAcquireContextApi Lib "advapi32.dll" Alias "CryptAcquireContextA" (ByRef phProv As Long, ByVal pszContainer As String, ByVal pszProvider As String, ByVal dwProvType As Long, ByVal dwFlags As Long) As Long
Private Declare Function CryptCreateHashApi Lib "advapi32.dll" Alias "CryptCreateHash" (ByVal hProv As Long, ByVal Algid As Long, ByVal hKey As Long, ByVal dwFlags As Long, ByRef phHash As Long) As Long
Private Declare Function CryptDecryptApi Lib "advapi32.dll" Alias "CryptDecrypt" (ByVal hKey As Long, ByVal hHash As Long, ByVal Final As Long, ByVal dwFlags As Long, ByRef pbData As Byte, ByRef pdwDataLen As Long) As Long
Private Declare Function CryptDeriveKeyApi Lib "advapi32.dll" Alias "CryptDeriveKey" (ByVal hProv As Long, ByVal Algid As Long, ByVal hBaseData As Long, ByVal dwFlags As Long, ByRef phKey As Long) As Long
Private Declare Function CryptDestroyHashApi Lib "advapi32.dll" Alias "CryptDestroyHash" (ByVal hHash As Long) As Long
Private Declare Function CryptDestroyKeyApi Lib "advapi32.dll" Alias "CryptDestroyKey" (ByVal hKey As Long) As Long
Private Declare Function CryptEncryptApi Lib "advapi32.dll" Alias "CryptEncrypt" (ByVal hKey As Long, ByVal hHash As Long, ByVal Final As Long, ByVal dwFlags As Long, ByRef pbData As Byte, ByRef pdwDataLen As Long, ByVal dwBufLen As Long) As Long
Private Declare Function CryptGetHashParamApi Lib "advapi32.dll" Alias "CryptGetHashParam" (ByVal hHash As Long, ByVal dwParam As Long, pbData As Any, pdwDataLen As Long, ByVal dwFlags As Long) As Long
Private Declare Function CryptHashDataApi Lib "advapi32.dll" Alias "CryptHashData" (ByVal hHash As Long, ByRef pbData As Byte, ByVal dwDataLen As Long, ByVal dwFlags As Long) As Long
Private Declare Function CryptReleaseContextApi Lib "advapi32.dll" Alias "CryptReleaseContext" (ByVal hProv As Long, ByVal dwFlags As Long) As Long
Private Declare Function CryptGetKeyParamApi Lib "advapi32.dll" Alias "CryptGetKeyParam" (ByVal hKey As Long, ByVal dwParam As Long, ByRef pbData As Byte, ByRef pdwDataLen As Long, dwFlags As Long) As Long
Private Declare Sub CopyMemoryApi Lib "kernel32" Alias "RtlMoveMemory" (pDst As Any, pSrc As Any, ByVal ByteLen As Long)
Function Encrypt(message As String, Password As String) As String
If CryptAcquireContextApi(provider, vbNullString, ProviderName, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT) = 0 Then
Goto Encrypt_Failure
End-If
If CryptCreateHashApi(provider, CALG_SHA1, 0&, 0&, hash) = 0 Then
Goto Encrypt_Failure
End-If
buffer = StrConv(Password, vbFromUnicode)
If CryptHashDataApi(hash, buffer(0), CLng(UBound(buffer) + 1), 0&) = 0 Then
GoTo Encrypt_Failure
End If
If CryptDeriveKeyApi(provider, CALG_3DES, hash, 1&, key) = 0 Then
key = 0
GoTo Encrypt_Failure
End If
length = Len(message)
bfrlen = length
If CryptEncryptApi(key, 0&, 1&, 0&, ByVal 0&, bfrlen, bfrlen) = 0 Then
GoTo Encrypt_Failure
End If
ReDim buffer(bfrlen - 1)
For i = 0 To length - 1
buffer(i) = Asc(Mid(message, i + 1, 1))
Next i
If CryptEncryptApi(key, 0&, 1&, 0&, buffer(0), length, bfrlen) = 0 Then
GoTo Encrypt_Failure
End If
Call CryptDestroyKeyApi(key)
Call CryptReleaseContextApi(provider, 0)
Encrypt = left(StrConv(buffer, vbUnicode), length)
Encrypt_Failure:
If key Then
Call CryptDestroyKeyApi(key)
End If
If hash Then
Call CryptDestroyHashApi(hash)
End If
If provider Then
Call CryptReleaseContextApi(provider, 0)
End If
Exit Function
Here's the new C# version:
public byte[] Encrypt(string source, string pass)
{
byte[] password = Encoding.UTF8.GetBytes(pass);
byte[] resultArray = null;
byte[] streamToEncrypt = Encoding.UTF8.GetBytes(source);
if(streamToEncrypt.Length % 8 != 0)
{
byte[] inputArray = new byte[streamToEncrypt.Length + (8 - (streamToEncrypt.Length % 8))]; //add padding to the end to make the message groups of 8 bytes
int i = 0;
foreach (byte element in streamToEncrypt)
{
inputArray[i] = element;
i++;
}
streamToEncrypt = inputArray;
}
using (TripleDESCryptoServiceProvider prov3des = new TripleDESCryptoServiceProvider())
{
prov3des.Mode = CipherMode.CBC;
prov3des.Padding = PaddingMode.PKCS7;
prov3des.IV = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 }; //8 bytes, zero-ed
using (PasswordDeriveBytes pdb = new PasswordDeriveBytes(password, null)) //No salt needed here
{
prov3des.Key = pdb.CryptDeriveKey("TripleDES", "SHA1", 168, new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 });
}
ICryptoTransform cTransform = prov3des.CreateEncryptor();
resultArray = cTransform.TransformFinalBlock(streamToEncrypt,0,streamToEncrypt.Length);
}
return resultArray;
}
And, like the poster linked above, the hash of the password is the same, and the first 8 bytes of the encrypted byte array are the same. I've got the initialization vector set to 8 bytes of zero, but I figure if the first 8 bytes of the encrypted data are the same, then the initialization vector must match. I just can't figure out why the next 8 bytes differ. Any ideas what I'm doing wrong here?
Thanks!
There's actually a very simple solution, although it's not at all obvious. You have a block of code which says "If this data isn't exactly a multiple of 8, pad it." However, this is changing the value you're encrypting, so you get a different result.
Simply remove the whole if(streamToEncrypt.Length.Dump("Length") % 8 != 0) {}
block and you'll get the expected result.