PasswordDeriveBytes en PHP

Hola tyros, yo de nuevo; en esta ocasión con un tema un poco avanzado.

En C# existe una clase denominada PasswordDeriveBytes. Es muy utilizada para generar los bytes de una cadena de texto, y de esta manera lograr tener una encriptación de mejor calidad.

En cierta ocasión me vi en la necesidad de migrar mi rutina de encriptación a Java. Me encontré con el problema de que en Java no existe esta clase. Investigando en internet, me encontré Changgun Lee, quien se dió a la tarea de crear una versión en Java para esta clase. Le estoy muy agradecido por el aporte.

Tiempo después, cuando incursiono en PHP me veo en la necesidad de migrar nuevamente mi código para encriptar, pero ahora a PHP. Esto me impulsó a crear una versión de PasswordDeriveBytes en PHP. Nuevamente, gracias a Changgun Lee por su aporte.


Aquí les dejo el código resultante de mi versión PasswordDeriveBytes en PHP:


<?php

/**
 * The MIT License (MIT)
 * Copyright (c) 2017 Juan Gabriel Castillo Turrubiates <tyrodeveloper@gmail.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software
 * and associated documentation files (the "Software"), to deal in the Software without restriction,
 * including without limitation the rights to use, copy, modify, merge, publish, distribute,
 * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or
 * substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
 * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
include ("functions.php");

class PasswordDeriveBytes {

    private $hashAlgorithm;
    private $saltValue;
    private $iterationsValue;
    private $hash;
    private $state;
    private $password;
    private $initial;
    private $output;
    private $firstBaseOutput;
    private $position;
    private $hashNumber;
    private $skip;

    function __construct() {
        $a = func_get_args();
        $i = func_num_args();
        if (method_exists($this, $f = '__construct' . $i)) {
            call_user_func_array(array(
                $this,
                $f
                    ), $a);
        }
    }

    function __construct2($prmPassword, $prmRgbSalt) {
        $this->Prepare($prmPassword, $prmRgbSalt, "SHA1", 100);
    }

    function __construct4($prmPassword, $prmRgbSalt, $prmHashAlgorithm, $prmIterations) {
        $this->Prepare($prmPassword, $prmRgbSalt, $prmHashAlgorithm, $prmIterations);
    }

    function Prepare($prmPassword, $prmRgbSalt, $prmHashAlgorithm, $prmIterations) {
        if (is_null($prmPassword)) {
            throw new Exception("prmPassword");
        }
        $this->password = $prmPassword;

        $this->state = 0;
        $this->setSalt($prmRgbSalt);
        $this->setHashName($prmHashAlgorithm);
        $this->setIterationCount($prmIterations);

        $this->initial = array_fill(0, $this->getDigetsLenght($prmHashAlgorithm), 0);
    }

    public function getSalt() {
        if (is_null($this->saltValue)) {
            return null;
        } else {
            return $this->saltValue;
        }
    }

    public function setSalt($prmSalt) {
        if ($this->state != 0) {
            throw new Exception("Can't change this property at this stage");
        }
        $this->saltValue = $prmSalt;
    }

    public function getHashName() {
        return $this->hashAlgorithm;
    }

    public function setHashName($prmHashAlgorithm) {
        if (is_null($prmHashAlgorithm)) {
            throw new Exception("prmHashAlgorithm");
        }
        if ($this->state != 0) {
            throw new Exception("Can't change this property at this stage");
        }
        try {
            $this->hashAlgorithm = $prmHashAlgorithm;
            $this->hash = hash_init($prmHashAlgorithm);
        } catch (Exception $e) {
            throw new Exception($e->getMessage());
        }
    }

    public function getIterationCount() {
        return $this->IterationsValue;
    }

    public function setIterationCount($prmIterationCount) {
        if ($prmIterationCount < 1) {
            throw new Exception("HashName");
        }
        if ($this->state != 0) {
            throw new Exception("Can't change this property at this stage");
        }
        $this->iterationsValue = $prmIterationCount;
    }

    public function GetBytes($prmCb) {

        if ($prmCb < 1) {
            throw new Exception("cb");
        }

        if ($this->state == 0) {
            $this->Reset();
            $this->state = 1;
        }
        
        //Declarar variables
        $secondBaseOutput = null;
        $output_len = 0;
        $str_output = "";
        
        
        $result = array_fill(0, $prmCb, 0);

        $cpos = 0;
        // the initial hash (in reset) + at least one iteration
        $iter = max(1, $this->iterationsValue - 1);

        // start with the PKCS5 key
        if (is_null($this->output)) {
            // calculate the PKCS5 key
            $this->output = $this->initial;
            // generate new key material
            for ($i = 0; $i < $iter - 1; $i ++) {
                $this->hash = hash_init($this->hashAlgorithm);
                hash_update($this->hash, hexToStr(vsprintf(str_repeat('%c', count($this->output)), $this->output)));
                $this->output = getBytesASCII(hash_final(hash_copy($this - hash)));
            }
        }

        while ($cpos < $prmCb) {
            $output2 = null;
            if ($this->hashNumber == 0) {
                // last iteration on output
                $this->hash = hash_init($this->hashAlgorithm);
                hash_update($this->hash, hexToStr(vsprintf(str_repeat('%c', count($this->output)), $this->output)));
                $output2 = getBytesASCII(hash_final(hash_copy($this->hash)));
           
            } else if ($this->hashNumber < 1000) {

                $output_len = sizeof(getBytesASCII(hexToStr(vsprintf(str_repeat('%c', count($this->output)), $this->output))));
                $n = getBytesASCII(strval($this->hashNumber));

                $output2 = array_fill(0, $output_len + sizeof($n), 0);

                for ($j = 0; $j < sizeof($n); $j ++) {
                    $output2 [$j] = $n [$j];
                }

                $str_output = vsprintf(str_repeat('%c', count($this->output)), $this->output);
                $array_output = getBytesASCII(hexToStr($str_output));

                $output2 = arrayCopy($array_output, 0, $output2, sizeof($n), $output_len);

                $this->hash = hash_init($this->hashAlgorithm);
                hash_update($this->hash, vsprintf(str_repeat('%c', count($output2)), $output2));
                $output2 = getBytesASCII(hash_final(hash_copy($this->hash)));
            } else {
                throw new Exception("too long");
            }

            $output2_len = sizeof(getBytesASCII(hexToStr(vsprintf(str_repeat('%c', count($this->output)), $this->output))));

            $rem = $output2_len - $this->position;

            $l = min($prmCb - $cpos, $rem);

            $array_output2 = getBytesASCII(hexToStr(vsprintf(str_repeat('%c', count($output2)), $output2)));

            $result = arrayCopy($array_output2, $this->position, $result, $cpos, $l);

            $cpos += $l;
            $this->position += $l;
            $output2_len = sizeof(getBytesASCII(hexToStr(vsprintf(str_repeat('%c', count($this->output)), $this->output))));
            while ($this->position >= sizeof($array_output2)) {
                $this->position -= sizeof($array_output2);
                $this->hashNumber ++;
            }
        }

        // saving first output length
        if ($this->state == 1) {

            if ($prmCb > 20) {
                $this->skip = 40 - sizeof($result);
            } else {
                $this->skip = 20 - sizeof($result);
            }
            $this->firstBaseOutput = array_fill(0, sizeof($result), 0);

            $this->firstBaseOutput = arrayCopy($result, 0, $this->firstBaseOutput, 0, sizeof($result));

            $this->state = 2;
        } // processing second output
        else if ($this->skip > 0) {
            //ALERTA!!!, código falta de probar
            echo "skip > 0 <br/>";
           
            $secondBaseOutput = array_fill(0, sizeof($this->firstBaseOutput) + sizeof($result), 0);
            $secondBaseOutput = arrayCopy($this->firstBaseOutput, 0, $secondBaseOutput, 0, sizeof($this->firstBaseOutput));
            $secondBaseOutput = arrayCopy($result, 0, $secondBaseOutput, sizeof($this->firstBaseOutput), sizeof($result));
            $result = arrayCopy($secondBaseOutput, $this->skip, $result, 0, $this->skip);
            $this->skip = 0;
        }

        return $result;
    }

    public function Reset() {
        $this->state = 0;
        $this->position = 0;
        $this->hashNumber = 0;
        $this->skip = 0;

        if ($this->saltValue != null) {
            hash_update($this->hash, $this->password);
            hash_update($this->hash, $this->saltValue);
            $this->initial = getBytesASCII(hash_final($this->hash));
        } else {
            hash_update($this->hash, $this->password);
            $this->initial = getBytesASCII(hash_final($this->hash));
        }
    }

    private function getDigetsLenght($hashAlgorithm) {
        $result = 0;
        switch (strtoupper($hashAlgorithm)) {
            case "MD5" :
                $result = 16;
                break;
            case "SHA1" :
                $result = 20;
                break;
            case "SHA256" :
                $result = 32;
                break;
            default :
                $result = 0;
                break;
        }
        return $result;
    }

}

?>


Aquí les dejo las funciones necesarias ("functions.php"):

<?php
/**
 * Get the ASCII bytes of string
 * @param $string - source string
 * @return array
 */
function getBytesASCII($string) {
 /* Da los mismos resultados que en Java */
 $results = array ();
 for($i = 0; $i < mb_strlen ( $string, "ASCII" ); $i ++) {
  $results [] = ord ( $string [$i] );
 }
 return $results;
}

/**
 * Get the Utf8 bytes of string
 * @param $string - source string
 * @return array
 */
function getBytesUtf8($string) {
 /* Da los mismos resultados que en Java */
 $results = array ();
 for($i = 0; $i < mb_strlen ( $string, "utf-8" ); $i ++) {
  $results [] = ord ( $string [$i] );
 }
 return $results;
}

/**
 * Convert string to hexadecimal
 * @param $string - source string
 * @return String
 */
function strToHex($string) {
 $hex = '';
 for($i = 0; $i < strlen ( $string ); $i ++) {
  $ord = ord ( $string [$i] );
  $hexCode = dechex ( $ord );
  $hex .= substr ( '0' . $hexCode, - 2 );
 }
 return strToUpper ( $hex );
}

/**
 * Convert hexadecimal to string
 * @param unknown $hex
 * @return string
 */
function hexToStr($hex) {
 $string = '';
 for($i = 0; $i < strlen ( $hex ) - 1; $i += 2) {
  $string .= chr ( hexdec ( $hex [$i] . $hex [$i + 1] ) );
 }
 return $string;
}

/**
 * Copies an array from the specified source array, beginning at the specified position, 
 * to the specified position of the destination array. A subsequence of array components are copied 
 * from the source array referenced by src to the destination array referenced by dest.
 * The number of components copied is equal to the length argument.
 * @param array $arrSrc - Source array to be copied
 * @param number $arrSrcPos - Start position in the source array
 * @param array $arrDes - Destination array
 * @param number $arrDesPos - Start position in the destination array
 * @param number $lenght - Number of array elements to be copied
 * @throws Exception when source or destination are not array
 * @return array with the copied information
 */
function arrayCopy($arrSrc, $arrSrcPos, $arrDes, $arrDesPos, $lenght) {
 if (! is_array ( $arrSrc )) {
  throw new Exception ( 'arrSrc is not array.' );
 }
 if (! is_array ( $arrDes )) {
  throw new Exception ( 'arrDes is not array.' );
 }
 $result = $arrDes;
 for($i = $arrSrcPos; $i < $lenght; $i ++) {
  $result [$arrDesPos + $i] = $arrSrc [$i];
 }
 return $result;
}
?>


Nota: Existen diferencias en el contenido del Array resultante. Sin embargo, el resultado final base 64 es similar en Java y en PHP.

Imágenes de las pruebas:

PHP

Java

No hay comentarios:

Publicar un comentario