richard_mortimer has asked for the wisdom of the Perl Monks concerning the following question:

Greetings people,

I have a sticky problem with Crypt::Blowfish that has been turning my brain into jelly.

According to the documentation at Crypt::Blowfish at CPAN, when I'm decrypting the encrypted string, I need to have a "chuck" of the data which is exactly 8 bytes in length, and further suggests that using "\0" is an acceptable padding character.

I've written this small snippet, which checks for the length and adds the appopriate characters, then chops it back to an 8 byte chunk, thus:

if (length($in) >= 8) { $in = substr($in,8); } else { $x = "\x00\x00\x00\x00\x00\x00\x00\x00"; $y = $in.$x; $in = substr($y,0,8); print "\nGot to the final part ... length is ".length($in)."\n +"; }
However at run - if the string to be decrypted is not divisable by 8, I get some weird characters appearing on the end, thus:
50 1 Yell=?H?Phf?!O"
is there some other character I should be using here?

Complete code, with three examples of the encrypted data follows:

#!/usr/bin/perl use Crypt::Blowfish; $servername="Richard"; #$tmp="IPPQY%FF%B3M/%u2030%u2014s%FE%07%8D%1E%u201A%C2%D2%FC%09F%u0152 +%07%CF%u2026nO%5B%D3nZ%5B%u02C6%u0152%01%u201D%CDn%7D%7D%u2018F%FB%BA +%F12Fm%CB%CF%E9%F1%7E%u2013%u2026%u201E%1B%7E%04h%18b%1EZ%u0192H%11%1 +1%A6%FEd%0D%60%EB%u0160%23%216F%0C%23%u2020qs%EA%3A%7B%DC%D6W%AF7Y%FE +%B8%0D%60%EB%u0160%23%216Fg%0A%BAL0%06%u203Aa%A0H%F9%u017E%A8%D6UBp%D +B%5DJ%B3%C5%24%BD%E6%25%F0%u203A%14%15O%u017D%u01925h%3F%CE%u2019%15% +AC%90%FAq8p%19%EA%16%DDJf+%u2039%u2122%A6%0A%F8%u20AC%C8%BE%A0%F1%E7% +A0x%7F%A6%C1%E9%CE%0C%C9%EC%06M%u203A%FF%CC%0FU%F0%u017D%07E%04%7E%CD +oK1IGm%0Fvd%DA%8F%F6%u0178%A3%u0178%D3%F8/%A7zc%u02C6q%EFL%15A%27K*%A +4%u201CT%u0178%B2I%04%u0152%8D%02j%B4%B7%u201A%C3%5B%3C%7B%CE%u0152%C +2B%C3%u2039Y%5C%E2pU3F%3B%CA%C7%7DU%D7%B84%A8%A9%u2018%CB%2Cn%3F1%D2% +1E5%00%u203A%26%CC8jwPy%7F%u201D%A1%F8%u2019z%E8_I%D1DW%u201A%08%15C% +u201D%u2019%D9+%EF%04%u203A%B7%ADx%3AK%u0160%F5%ECg9%E8_1%C7%u203AY%0 +1%B2%7BED%29%B9%BB%u20AC"; #$tmp="IPPQY%FF%B3M/%u2030%u2014s%FE%07%8D%1E%D3%27j%C6%CD%3A%u02DC%1B +%1F%3E%FA%28m%DA%07B%F1%7F%03%D3%B78%0C%u2021%D4%A8%E2%AD%0C%17%8FJ%E +A%0E%B5j%u201Cy%u201Ed%F3%u2019%E1%BD%u2013%CF8c5%EA%E8B%B8%A9%17g%0C +%23%u2020qs%EA%3A%7B%C4%D3%3E%0E%12%0ClnT%3F%1B%24%FC%B9c%C7%14%F5%F0 +%BF%29%A3%C6%u0160"; $tmp="IPPQY%FF%B3M/%u2030%u2014s%FE%07%8D%1E%u201A%C2%D2%FC%09F%u0152% +07%CF%u2026nO%5B%D3nZ%5B%u02C6%u0152%01%u201D%CDn%7D%7D%u2018F%FB%BA% +F12Fm%CB%CF%E9%F1%7E%u2013%u2026%u201E%1B%7E%04h%18b%1EZ%u0192H%11%11 +%A6%FEd%0D%60%EB%u0160%23%216F%0C%23%u2020qs%EA%3A%7B%DC%D6W%AF7Y%FE% +B8%0D%60%EB%u0160%23%216Fg%0A%BAL0%06%u203Aa%A0H%F9%u017E%A8%D6UBt%F4 +%FAT1%A0%u0178%FC%F0%0El%00%DA%D2%5C%DD%AA%26%7B%20%26%DC%3D%13%07%DE +1%26%CD%CF%B5%EC%AF%09%10%20LkU%CC%05%E6AgD-N%FB%D9%B0%5B%11o%7D%B4%E +4%11%F8%13%EAUN%10%u02DC%u201E%u2122i%A2U%0B@%08I%24%C6%04%BB%8Dh%D1% +0A%F4%7C%AF%3A%FD%B7l%D6%u2026%CDt%D5%5B%B4%u201EW%B6NX%C8%0A%A7%F6%D +4%B2%07C%0A%3C8%B1%26%D2%EF%E19%17%5D%AF%05%u2122%F1bE%23%u2019%u2020 +%E0%E8%A8%B3%3B%u2026h%E8%5B%u0152%C2%25%B8%DA%27%B5%07%02R%DAC%B8E%u +2039%D6%01D%u0160%BC%0Au%1F%u2013p%F75%8F%01%1A%E81h%B9%90%u017D%A1%9 +0%FAR%11%EA%u2122%12%DF@FP%7DYY%B2%217V%14%F5%F0%BF%29%A3%C6%u0160"; &mime_hex_swap; &key; $cipher = new Crypt::Blowfish $key; &unencrypt; open (FILE,">file.txt"); print FILE "\n\nText is now \"".$tmp."\"\n"; close(FILE); print "\n\nText is now \"".$tmp."\"\n"; exit(); sub mime_hex_swap { # wierd shite characters (ISO Latin-1) $tmp =~ s/\%u20AC/\%80/gi; $tmp =~ s/\%u201A/\%82/gi; $tmp =~ s/\%u0192/\%83/gi; $tmp =~ s/\%u201E/\%84/gi; $tmp =~ s/\%u2026/\%85/gi; $tmp =~ s/\%u2020/\%86/gi; $tmp =~ s/\%u2021/\%87/gi; $tmp =~ s/\%u02C6/\%88/gi; $tmp =~ s/\%u2030/\%89/gi; $tmp =~ s/\%u0160/\%8A/gi; $tmp =~ s/\%u2039/\%8B/gi; $tmp =~ s/\%u0152/\%8C/gi; $tmp =~ s/\%u017D/\%8E/gi; $tmp =~ s/\%u2018/\%91/gi; $tmp =~ s/\%u2019/\%92/gi; $tmp =~ s/\%u201C/\%93/gi; $tmp =~ s/\%u201D/\%94/gi; $tmp =~ s/\%u2022/\%95/gi; $tmp =~ s/\%u2013/\%96/gi; $tmp =~ s/\%u2014/\%97/gi; $tmp =~ s/\%u02DC/\%98/gi; $tmp =~ s/\%u2122/\%99/gi; $tmp =~ s/\%u0161/\%9A/gi; $tmp =~ s/\%u203A/\%9B/gi; $tmp =~ s/\%u0153/\%9C/gi; $tmp =~ s/\%u017E/\%9E/gi; $tmp =~ s/\%u0178/\%9F/gi; $tmp =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg; print "length is now ".length($tmp)."\n"; } sub key { $tmp2 = $key = $len = $tmpkey = ""; $tmpkey = "AMIPS".$servername."".length($servername); for ($keykeeper=length($tmpkey);$keykeeper>-1;$keykeeper--) { $tmp2 .= substr($tmpkey, $keykeeper, 1); } $key = $tmpkey.$tmp2; } sub unencrypt { $in = $tmp; $newlength = length($in); print $newlength."\n"; while (length($in) > 0) { $temp2_data = substr($in,0,8); if (length $temp2_data == 8) { $temp3_data = $cipher->decrypt($temp2_data); $decrypt_data .= $temp3_data; print "."; } if (length($in) >= 8) { $in = substr($in,8); } else { $x = "\x00\x00\x00\x00\x00\x00\x00\x00"; $y = $in.$x; $in = substr($y,0,8); print "\nGot to the final part ... length is ".length($in)."\n +"; } } $tmp = $decrypt_data; #if ($DIAG) { print LOG "\nDecrypted data MIME[".$local_counter."] ( +".$newlength."bytes) : ".$decrypt_data; } }
Thanks

Richard

Edit by tye, formatting, add READMORE tags

Replies are listed 'Best First'.
Re: Blowfish decryption problem
by crouchingpenguin (Priest) on May 06, 2003 at 03:36 UTC

    Maybe not exactly what you are looking for (or maybe it is), but if you use Crypt::CBC in conjunction with Crypt::Blowfish, the whole 8 char length issue goes away.


    cp
    ----
    "Never be afraid to try something new. Remember, amateurs built the ark. Professionals built the Titanic."
Re: Blowfish decryption problem
by Thelonius (Priest) on May 06, 2003 at 07:23 UTC
    I'm going to second what crouchingpenguin said, and recommend that you use Crypt::CBC. Not only will it deal with block length, it also makes your encryption more secure.

    It's hard to tell what's wrong with your decryption without looking at the encryption. It seems extrememly dubious that you are doing substitutions such as $tmp =~ s/\%u20AC/\%80/gi; on the cryptext. Cryptext is pure binary. It should not be treated as character data.

Re: Blowfish decryption problem
by tedrek (Pilgrim) on May 07, 2003 at 05:36 UTC
    Actually this looks like a problem with decoding your cipher text.

    usually what happens is the plain text is padded with \x00 THEN encrypted, therefore the cipher text length is ALWAYS a multiple of 8 (or whatever the block size is)

    In the other post you made about decoding MIME characters, you said that it was MIME encoded UTF-16, but that uses 2 bytes per character, here you are decoding the multi-byte characters into a single byte. eg %u0160 should be something like "\x00\xBF"(note. NOT THE CORRECT SEQUENCE).

    Looking at your sample decrypted string which I read as "50\t1\tYell=" the '=' looks like the location where the decryption starts going wrong, it is ALSO the location of the first encoded utf-16 character ('%u2030') which it's too much of a coincidence.

    Solution: decode the encoded strings to the correct byte sequence, not the characters. :)

    as a side note, if that is the correct first few characters then the encryption is running in ECB mode and so Crypt::CBC won't help you. Tedrek

      Thanks for the post, you are helping to shed more light onto my problem.

      The character string being generated is formed from a VB application, which the VB programmer has used this page as the source for the compiled code.

      >the cipher text length is ALWAYS a multiple of 8

      Although he acknowledges that he hasn't initially padded the blocks out to be in exact 8 bytes blocks - using the same function to reverse the data out works; I wonder if perhaps we have inherited someone else's bugs by including this code?

      >you are decoding the multi-byte characters into a single byte

      When encoding these characters, some single-byte characters are transformed into multi-byte characters, as per the example below.

      126 %7E \x7E 127 %7F \x7F 128 %u20AC \x80 129 %81 \x81 130 %u201A \x82 131 %u0192 \x83 132 %u201E \x84 133 %u2026 \x85 134 %u2020 \x86 135 %u2021 \x87

      In the example here, the character is transformed as a single byte character, so surely it must go back to a single (not multi) byte character?

      >eg %u0160 should be something like "\x00\xBF"

      I am not sure I fully understand what you have written here, is this the information I should be looking at?

      >correct first few characters then the encryption is running in ECB mode and so Crypt::CBC won't help you

      Yes, your statement is correct - I tried the Crypt::CBC module, but the outcoming data looked nothing like the original string, whereas the Crypt::Blowfish module was giving me the majority of it, just causing problems in the trailing area.

      Richard (still mostly confused ...!)

        Oh now I feel bad... I didn't actually try running your example code last time. (I didn't really have access to perl at the time).

        So I just tried your code, and found the problem. when you assign to $tmp at the top you use double quotes which causes a interpolation of @FP near the end of the string.

        changing the double quotes to single quotes fixes that. and if you had used warnings it would have told you.

        looking at the demonstration VB it says that if the string isn't a multiple of 8 bytes long it will be truncated to a multiple of 8 bytes so it will need to be padded to guarantee you don't lose anything.

        As for the translation. You must be doing it right given that you are getting what you want back. However you should be aware that it isn't UTF-16, and it would be ideal to just store the encrypted logs as binary unless there is a real good reason not to.

        so anyway heres full working code, cleaned up a bit.

        #!/usr/bin/perl use warnings; use strict; use Crypt::Blowfish; my $string = 'IPPQY%FF%B3M/%u2030%u2014s%FE%07%8D%1E%u201A%C2%D2%FC%09 +F%u0152%07%CF%u2026nO%5B%D3nZ%5B%u02C6%u0152%01%u201D%CDn%7D%7D%u2018 +F%FB%BA%F12Fm%CB%CF%E9%F1%7E%u2013%u2026%u201E%1B%7E%04h%18b%1EZ%u019 +2H%11%11%A6%FEd%0D%60%EB%u0160%23%216F%0C%23%u2020qs%EA%3A%7B%DC%D6W% +AF7Y%FE%B8%0D%60%EB%u0160%23%216Fg%0A%BAL0%06%u203Aa%A0H%F9%u017E%A8% +D6UBt%F4%FAT1%A0%u0178%FC%F0%0El%00%DA%D2%5C%DD%AA%26%7B%20%26%DC%3D% +13%07%DE1%26%CD%CF%B5%EC%AF%09%10%20LkU%CC%05%E6AgD-N%FB%D9%B0%5B%11o +%7D%B4%E4%11%F8%13%EAUN%10%u02DC%u201E%u2122i%A2U%0B@%08I%24%C6%04%BB +%8Dh%D1%0A%F4%7C%AF%3A%FD%B7l%D6%u2026%CDt%D5%5B%B4%u201EW%B6NX%C8%0A +%A7%F6%D4%B2%07C%0A%3C8%B1%26%D2%EF%E19%17%5D%AF%05%u2122%F1bE%23%u20 +19%u2020%E0%E8%A8%B3%3B%u2026h%E8%5B%u0152%C2%25%B8%DA%27%B5%07%02R%D +AC%B8E%u2039%D6%01D%u0160%BC%0Au%1F%u2013p%F75%8F%01%1A%E81h%B9%90%u0 +17D%A1%90%FAR%11%EA%u2122%12%DF@FP%7DYY%B2%217V%14%F5%F0%BF%29%A3%C6% +u0160'; $string = &mime_hex_swap($string); my $key = &key('Richard'); my $cipher = new Crypt::Blowfish $key; $string = &unencrypt($cipher, $string); open (FILE,">file.txt"); print FILE "\n\nText is now \"".$string."\"\n"; close(FILE); print "\n\nText is now \"".$string."\"\n"; exit(); sub mime_hex_swap { my $tmp = shift; # wierd shite characters (ISO Latin-1) $tmp =~ s/\%u20AC/\%80/gi; $tmp =~ s/\%u201A/\%82/gi; $tmp =~ s/\%u0192/\%83/gi; $tmp =~ s/\%u201E/\%84/gi; $tmp =~ s/\%u2026/\%85/gi; $tmp =~ s/\%u2020/\%86/gi; $tmp =~ s/\%u2021/\%87/gi; $tmp =~ s/\%u02C6/\%88/gi; $tmp =~ s/\%u2030/\%89/gi; $tmp =~ s/\%u0160/\%8A/gi; $tmp =~ s/\%u2039/\%8B/gi; $tmp =~ s/\%u0152/\%8C/gi; $tmp =~ s/\%u017D/\%8E/gi; $tmp =~ s/\%u2018/\%91/gi; $tmp =~ s/\%u2019/\%92/gi; $tmp =~ s/\%u201C/\%93/gi; $tmp =~ s/\%u201D/\%94/gi; $tmp =~ s/\%u2022/\%95/gi; $tmp =~ s/\%u2013/\%96/gi; $tmp =~ s/\%u2014/\%97/gi; $tmp =~ s/\%u02DC/\%98/gi; $tmp =~ s/\%u2122/\%99/gi; $tmp =~ s/\%u0161/\%9A/gi; $tmp =~ s/\%u203A/\%9B/gi; $tmp =~ s/\%u0153/\%9C/gi; $tmp =~ s/\%u017E/\%9E/gi; $tmp =~ s/\%u0178/\%9F/gi; $tmp =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg; print "length is now ".length($tmp)."\n"; return $tmp; } sub key { my $servername = shift; my $key = "AMIPS".$servername.length($servername); return $key.reverse($key); } sub unencrypt { my $cipher = shift; my $string = shift; print length($string)."\n"; my $decrypt_data = ''; die "Incorrect Length" if (length($string) % 8); #Ciphertext MUST +be a #multiple of 8 while (my $block = substr($string, 0, 8, '')) { $decrypt_data .= $cipher->decrypt($block); print "."; } return $decrypt_data; }