I need to encrypt a value in C# and then decrypt it in classic ASP. The closest I've come to getting this to work at all is with Blowfish. But the problem is that the two implementations produce slightly different results and I'm not sure why.
Implementations in use:
C#: https://defuse.ca/blowfish.htm
vbscript: http://www.di-mgt.com.au/cryptoBlowfishASP.html
C# Code:
var input = "Hello World";
var key = "04B915BA43FEB5B6";
BlowFish b = new BlowFish(key);
string enc, dec;
enc = b.Encrypt_ECB(input);
dec = b.Decrypt_ECB(enc);
vbscript:
Dim aKey()
Dim nKeyLen, szTxtKey, szTxtPlain, szTxtKeyAsString, szTxtCipher, szTxtCipherHex, szTxtCipher64, szTxtDecrypt
szTxtKey = "04B915BA43FEB5B6"
szTxtPlain = "Hello World"
ReDim aKey((Len(szTxtKey) \ 2) - 1)
nKeyLen = bu_HexStr2Bytes(szTxtKey, aKey)
Call blf_Key(aKey, nKeyLen)
szTxtKeyAsString = bu_Bytes2HexStr(aKey, nKeyLen)
szTxtCipher = blf_StringEnc(szTxtPlain)
szTxtCipherHex = bu_Str2Hex(szTxtCipher)
C# Output:
819dd50a925a5eb83ed723bea6d84984
VBScript Output:
819DD50A925A5EB8CABE974A654A18A8
The first half of the output is the same: "819DD50A925A5EB8"
An the funny thing is, if I decrypt the vbscript output with the C# library I get this: Hello World♣♣♣♣♣
So...it almost works but there's some sort of padding or something going on. I don't know how to fix this though.
As @artjom-b has already mentioned in the comments, the culprit is different padding.
There is a good explanation of different padding methods here.
Analysing the blowfish.cs
file shows it's using NULL padding (note this snippet from the file);
/// <summary>
/// Decrypts a string (ECB)
/// </summary>
/// <param name="ct">hHex string of the ciphertext</param>
/// <returns>Plaintext ascii string</returns>
public string Decrypt_ECB(string ct)
{
return Encoding.ASCII.GetString(Decrypt_ECB(HexToByte(ct))).Replace("\0", "");
}
In contrast the Classic ASP implementation uses PKCS5 padding (snippet from basBlowfishFns.asp
shows PKCS5 method)
From Using Padding in Encryption
Pad with bytes all of the same value as the number of padding bytes (PKCS5 padding)
' Get # of padding bytes from last char
nPad = Asc(Right(strData, 1))
If nPad > 8 Then nPad = 0 ' In case invalid
strData = Left(strData, nLen - nPad)
The fix is to apply a workaround for the NULL padding used by the c# library.
Here is the modified basBlowfishFns.asp
(just showing the modified functions);
Public Function blf_StringEnc(strData, padMethod)
' Encrypts plaintext strData after adding RFC 2630 padding
' Returns encrypted string.
' Requires key and boxes to be already set up.
' Version 5. Completely revised.
' The speed improvement here is due to Robert Garofalo.
Dim strIn
Dim strOut
Dim nLen
Dim sPad
Dim nPad
Dim nBlocks
Dim i
Dim j
Dim aBytes(7)
Dim sBlock
Dim iIndex
' Pad data string to multiple of 8 bytes
strIn = PadString(strData, padMethod)
' Calc number of 8-byte blocks
nLen = Len(strIn)
nBlocks = nLen \ 8
' Allocate output string here so we can use Mid($ below
' strOut = String(nLen, " ")
strOut = "" ' Fix for VBScript
' Work through string in blocks of 8 bytes
iIndex = 0
For i = 1 To nBlocks
sBlock = Mid(strIn, iIndex + 1, 8)
' Convert to bytes
' aBytes() = StrConv(sBlock, vbFromUnicode)
Call bu_String2Bytes(sBlock, aBytes)
' Encrypt the block
Call blf_EncryptBytes(aBytes)
' Convert back to a string
' sBlock = StrConv(aBytes(), vbUnicode)
sBlock = bu_Bytes2String(aBytes, 8)
' Copy to output string
' Mid(strOut, iIndex + 1, 8) = sBlock
strOut = strOut & sBlock
iIndex = iIndex + 8
Next
blf_StringEnc = strOut
End Function
Public Function blf_StringDec(strData, padMethod)
' Decrypts ciphertext strData and removes RFC 2630 padding
' Returns decrypted string.
' Requires key and boxes to be already set up.
' Version 5. Completely revised.
' The speed improvement here is due to Robert Garofalo.
Dim strIn
Dim strOut
Dim nLen
Dim sPad
Dim nPad
Dim nBlocks
Dim i
Dim j
Dim aBytes(7)
Dim sBlock
Dim iIndex
strIn = strData
' Calc number of 8-byte blocks
nLen = Len(strIn)
nBlocks = nLen \ 8
' Allocate output string here so we can use Mid($ below
'strOut = String(nLen, " ")
strOut = ""
' Work through string in blocks of 8 bytes
iIndex = 0
For i = 1 To nBlocks
sBlock = Mid(strIn, iIndex + 1, 8)
' Convert to bytes
' aBytes() = StrConv(sBlock, vbFromUnicode)
Call bu_String2Bytes(sBlock, aBytes)
' Encrypt the block
Call blf_DecryptBytes(aBytes)
' Convert back to a string
'sBlock = StrConv(aBytes(), vbUnicode)
sBlock = bu_Bytes2String(aBytes, 8)
' Copy to output string
' Mid(strOut, iIndex + 1, 8) = sBlock
strOut = strOut & sBlock
iIndex = iIndex + 8
Next
' Strip padding, if valid
strOut = UnpadString(strOut, padMethod)
blf_StringDec = strOut
End Function
Public Function PadString(strData, method)
' Pad data string to next multiple of 8 bytes as per RFC 2630
Dim nLen
Dim sPad
Dim nPad
nLen = Len(strData)
nPad = ((nLen \ 8) + 1) * 8 - nLen
Select Case method
Case "PKCS5"
sPad = String(nPad, Chr(nPad)) ' Pad with # of pads (1-8)
Case "NULL"
sPad = String(nPad, Chr(0)) ' Pad with # of NULL characters
End Select
PadString = strData & sPad
End Function
Public Function UnpadString(strData, method)
' Strip RFC 2630-style padding
Dim nLen
Dim nPad
nLen = Len(strData)
If nLen = 0 Then Exit Function
Select Case method
Case "PKCS5"
' Get # of padding bytes from last char
nPad = Asc(Right(strData, 1))
If nPad > 8 Then nPad = 0 ' In case invalid
strData = Left(strData, nLen - nPad)
Case "NULL"
'Remove any NULL characters, obviously, this method isn't ideal if
'the data contains valid NULLs. This shouldn't be an issue with
'ASCII text.
strData = Replace(strData, Chr(0), "")
End Select
UnpadString = strData
End Function
the key modifications are to the PadString()
and UnpadString()
functions. I've added a parameter method
allowing you to pass an identifier NULL
or PKCS5
to determine how we pad / unpad the data. These functions already existed but for some reason were not used by the blf_StringEnc()
and blf_StringDec()
functions so in the interests of the DRY principle I've modified them so they are used.
With those modifications (which is only a quick stab at making the code more flexible) using the following code;
Dim aKey()
Dim nKeyLen, szTxtKey, szTxtPlain, szTxtKeyAsString, szTxtCipher, szTxtCipherHex, szTxtCipher64, szTxtDecrypt
szTxtKey = "04B915BA43FEB5B6"
szTxtPlain = "Hello World"
ReDim aKey((Len(szTxtKey) \ 2) - 1)
nKeyLen = bu_HexStr2Bytes(szTxtKey, aKey)
Call blf_Key(aKey, nKeyLen)
szTxtKeyAsString = bu_Bytes2HexStr(aKey, nKeyLen)
'Encrypt using NULL padding method.
szTxtCipher = blf_StringEnc(szTxtPlain, "NULL")
szTxtCipherHex = bu_Str2Hex(szTxtCipher)
Call Response.Write(szTxtCipherHex)
Will result in;
819DD50A925A5EB83ED723BEA6D84984
as originally expected.