Search code examples
c#encryptionvbscriptblowfish

Blowfish Results different between C# and Classic ASP


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.


Solution

  • 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.