tags:

views:

153

answers:

2

Hello!

I've the task to convert a crypt function someone made in perl into php code. Everything works okay except this:

Perl:

$wert = Encode::encode( "utf8", $wert );
$len=length $wert;
$pad = ($len % 16)?"0".chr(16 - ($len % 16)):"10";
$fuell = pack( "H*", $pad x (16 - $len % 16));

PHP:

$wert = utf8_encode($wert);
$len = mb_strlen($wert);
$pad = ( $len%16 ) ? '0'.chr(16 - ($len%16)) : '10';
$fuell = pack("H*", str_repeat($pad, (16 - $len % 16)));

The php version works okay for some strings. But when I have something like '2010-01-01T00:00:00.000' the perl version works without any error and the php version prints "PHP Warning: pack(): Type H: illegal hex digit".

I'm very grateful if someone can spot the error in the php version.

Edit:

This is the complete function I've to convert into php. It was made by a programmer of a company which doesn't work for us anymore so I can't really tell what the original intention was.

sub crypt
{
 my $self = shift;
 my ($wert,$pw)= @_;
 $wert = Encode::encode( "utf8", $wert );
 $pw = Encode::encode( "utf8", $pw );
 $len=length $wert;
 $pad = ($len % 16)?"0".chr(16 - ($len % 16)):"10";
 $fuell = pack( "H*", $pad  x (16 - $len % 16));
 $wert=$wert.$fuell;
 $lenpw=length $pw;
 $fuell = ($lenpw % 16)? pack ("H*", "00" x (16 - $lenpw % 16)):"";
 $pw=$pw.$fuell;
 $cipher = new Crypt::Rijndael $pw, Crypt::Rijndael::MODE_CBC;
 $cipher->set_iv($pw);
 $crypted = encode_base64($cipher->encrypt($wert),"");

 return $crypted;
}
+5  A: 

It seems that the Perl implementation of pack() is tolerant of invalid hex digits in the input string, and the PHP version is decidedly not.

Consider:

print pack("H*", "ZZ");

This prints 3 in Perl (for some reason), but results in the error you mentioned in PHP.

I'm not sure exactly what Perl is doing with these 'digits', but it's definitely not the same as PHP.

EDIT: It looks like, Perl actually will "roll" the hex digit domain forward into the character set. That is:

0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ  #-- Give this to Perl...
0123456789ABCDEF0123456789ABCDEF0123  #-- .. and it's treated as this hex digit

Thus, "ZZ" is the same as "33", which is why it prints 3. Note that this behavior is not well-defined according to the documentation. Thus the original implementation in Perl can be considered buggy, since it relies on behavior that isn't well-defined.

Adam Bellaire
Is this a known bug/limitation in Perl? It seems like it should be possible to fix, or add an additional error condition to the `pack()` implementation.
Ether
@Ether: I think it was just a simple implementation that worked well for valid hex digits and happened to spill out onto the rest of the character set. Rather than having it complain loudly on invalid hex digits, they just documented that the behavior was not well-defined. The fact that they said that implies that the behavior could change one day. I have no idea why it hasn't.
Adam Bellaire
+6  A: 

It looks like the error is actually in both versions. The format code H looks for a hex digit, and as noted in the PHP error, it isn't finding (a legal) one. The culprit appears to be this expression:

chr(16 - ($len % 16))

The Perl version isn't complaining because Perl's version of pack will convert the character regardless of whether or not it is a hex digit (which may not be what you want). The documentation goes into more detail about what actually happens.

To prevent the error, try this instead:

sprintf('%x', 16 - ($len % 16))

Note: While this should fix the error you are getting, I don't know if it's an acceptable solution because I don't know the exact intent of the original author of the Perl code.

bish
+1 for pointing out that the invalid characters are coming from the call to `chr`, though I'm not sure that the `sprintf` will do what he's trying to do, since as you say, we don't know the Perl code's author's intent.
Adam Bellaire
@Adam I waffled for a bit about whether I should include the sprintf or not. It really looks like the original author meant for the expression to return a hex digit, so I figured I'd include it. Edited to put less emphasis on the fix and more on the cautionary note.
bish
@bish: Actually, looking at the behavior of Perl in my tests, the 'sprintf()' solution should work. The reason is that Perl would treat each of the characters `chr(1)` through `chr(15)` as having the smaller hex digit value of their characters, which is of course the same. The behavior switches for characters coded above 'A', but the example code can't reach that high. I suggest that the OP should try it and see if it indeed gives identical output to the Perl.
Adam Bellaire
@Adam Good point. I didn't quite realize that. Upon reading the code, my mind saw chr(0) - chr(15) combined with pack('H*') and immediately flagged it as an issue since those calls to chr won't produce hex characters. If sprintf does work for the OP in this case, I'd suggest using it in the PHP and Perl version, or at least adding a comment to the Perl version to make clear the intent to use the non-intuitive behavior of pack. I guess my mind doesn't work like Larry's. =)
bish
@bish: Thanks a lot! sprintf seems to be doing fine!
Carsten
@Carsten: Awesome! Glad to hear that worked out okay.
bish