domingo, 15 de mayo de 2016

From Python to PHP. An example


PHP es poco amigable cuando queremos trabajar con "big integers". Lo que se muestra poco menos que insufrible cuando queremos adaptar códigos de otros lenguajes más potentes en ello como Python. Aquí pongo algunos ejemplos para ayudar a adaptar de uno a otro lenguaje.

1- struct.pack & ord


def pack(data):
    target = []
    for i in data:
        target.extend(struct.pack('>I', i))
    target = [ord(c) for c in target]
    return target


En este caso PHP nos permite realizar el proceso en un solo bucle.
El proceso Python realiza en un primer bucle un struct.pack('>I', i) de cada elemento del array data, y en un segundo bucle convierte cada uno de los elementos (caracteres) anteriores a su valor numérico ASCII. Con PHP tenemos el solo comando 

array_values(unpack("C*",pack("N",$i)))


function packk($data) { $target = array(); foreach ($data as $i) {$target=array_merge($target,array_values(unpack("C*",pack("N",$i))));} return $target; }



2- trabajando con bitwise XOR y shifts.


Ejemplo Python

def tea_encrypt(v, key):
s = 0
key = unpack(key)
v = unpack(v)
for i in range(ROUNDS):
s += DELTA
s &= 0xffffffff
v[0] += (v[1]+s) ^ ((v[1]>>5)+key[1]) ^ ((v[1]<<4)+key[0])
v[0] &= 0xffffffff
v[1] += (v[0]+s) ^ ((v[0]>>5)+key[3]) ^ ((v[0]<<4)+key[2])
v[1] &= 0xffffffff
return pack(v)


En este caso PHP ofrece demasiados problemas y la implementación es más complicada. 
Necesitamos codificar las siguientes funciones por separado:

- la conversión a 32 bits sin signo: to32
- el right shift (>>): _rshift
- el left shift (<<): simplemente usando pow, a << b es a*2^b (aquí ^ es potencia)
- el XOR (^), con la función binxor


function to32($integer) { if (0xffffffff < $integer || -0xffffffff > $integer) $integer = fmod($integer, 0xffffffff + 1); if (0x7fffffff < $integer) $integer -= 0xffffffff + 1.0; elseif (-0x80000000 > $integer) $integer += 0xffffffff + 1.0; return $integer; }

function _rshift($integer, $n) { $integer=to32($integer); if (0 > $integer) { $integer &= 0x7fffffff; $integer >>= $n; $integer |= 1 << (31 - $n); } else $integer >>= $n; return $integer; }
function binxor($w1,$w2) { $x=base_convert($w1, 10, 2); $y=base_convert($w2, 10, 2); if (strlen($y)<strlen($x)) $y=str_repeat(0,strlen($x)-strlen($y)).$y; if (strlen($x)<strlen($y)) $x=str_repeat(0,strlen($y)-strlen($x)).$x; $x=str_split($x);$y=str_split($y); $z=""; for ($k=0;$k<sizeof($x);$k++) { $z.=(int)($x[$k])^(int)($y[$k]); } return base_convert($z,2,10); } function bigint($a) {return bindec(decbin($a));}

function tea_encrypt($v, $key) { $s = 0; $key = unpackk($key); $v = unpackk($v); for ($i=0;$i<16;$i++) { $s+= hexdec("9e3779b9");
/* 
el código python es

v[0] += (v[1]+s) ^ ((v[1]>>5)+key[1]) ^ ((v[1]<<4)+key[0]) v[0] &= 0xffffffff
ejemplos de cómo computan diferente

a- right shift                                    

3688618752 >> 5

python    
115269336
php
  -18948392

b- left shift

3688618752 << 5 
python 118035800064 php
  2071683072

en este caso basta con aplicar 
3688618752*pow(2,5)


c- bitwise XOR                                 

3688618752 ^ 5


python  3688618757
php    
-606348539

usamos binxor, haciendo el xor bit a bit en binario. Pero hay que usar base_convert y no decbin porque éste último corta a 32 bits.
*/ $w1= ($v[1]+$s); $w2= (_rshift($v[1],5)+$key[1]); $w3= ($v[1]*pow(2,4))+$key[0]; $z=binxor(binxor($w1,$w2),$w3); $v[0]+=$z; $v[0]=bigint($v[0]); // equivale a python v[0] &= 0xffffffff $w1= ($v[0]+$s); $w2= (_rshift($v[0],5)+$key[3]); $w3= ($v[0]*pow(2,4))+$key[2]; $z=binxor(binxor($w1,$w2),$w3); $v[1]+=$z; $v[1]=bigint($v[1]); } return packk($v); }