W poprzednim wpisie pisałem o potędze polimorfizmu w programowaniu obiektowym. W tym postaram się podać bardziej przydatny przykład z życia na podstawie szyfrowania tekstu.

Do stworzenia mechanizmu potrzebuje interfejsu Icrypt, który zawiera przydatne metody dla każdego algorytmu szyfrowania np zaszyfruj, odszyfruj itp.

interface ICrypt
{
   public function encrypt($plainText);
   public function decrypt();
   public function setPlaintext($text);
   public function getCipher();
}

Nastepnie potrzebuje klasy abstrakcyjnej, która będzie zawierała implementacje niektórych metod oraz będzie implementowała interfejs Icrypt

abstract class Crypt implements ICrypt
{
    protected $cipher;
    protected $plain;
    protected $k;
    
    public function setPlaintext($text)
    {
       $this->plain = $text;
    }
 
    public function getCipher()
    {
       return $this->cipher;
    }

    public function toString()
    {
       return $this->decrypt();
    }
}

Mając interfejs i klase abstrakcyjna mogę stworzyć konkretne klasy szyfrujące, jedna z nich to prosty algorytm cezara czyli przesuwanie o K znaków w alfabecie, natomiast druga to zaawansowany algorytm używany w wojsku o nazwie Rijndael, który jest używany jako standard AES(Advanced Encryption Standard).

class Caesar extends Crypt
{
   static $ALPHABET_LENGTH = 254;

   public function Caesar()
   {
       $this->k = rand(1,self::$ALPHABET_LENGTH);
   }
   public function encrypt($plainText)
   {
       $this->plain = $plainText;

       if($this->plain && !empty($this->plain))
       {
           $length = strlen($this->plain);
           $this->cipher = "";
           for($i = 0; $i < $length ; ++$i)
           {
              $this->cipher[$i] = (ord($this->plain[$i]) + $this->k) % self::$ALPHABET_LENGTH;
           }
       }
   }
   
   public function decrypt()
   {
      $length = count($this->cipher);
      $plain = "";
      for($i = 0; $i < $length ; ++$i)
      {
         $plain .= chr($this->cipher[$i] - $this->k % self::$ALPHABET_LENGTH);
      }
      return $plain;
   }

   public function getCipher()
   {
       $str = "";
       print_r($this->cipher);
       foreach($this->cipher as $l) $str += (string)$this->cipher;
       return $str;
   }
}


class Rijndael extends Crypt
{
    private $enc;
    private $mode;
    private $iv;

    public function Rijndael()
    {
         $this->enc  = MCRYPT_RIJNDAEL_128;
         $this->mode = MCRYPT_MODE_CBC;
         $this->k = pack('H*', "00112233445566778899aabbccddee"); // 16 bytes       
    }

    public function encrypt($plainText)
    {
         $this->iv   = mcrypt_create_iv(mcrypt_get_iv_size($this->enc, $this->mode), MCRYPT_DEV_URANDOM);
         $this->cipher = mcrypt_encrypt($this->enc, $this->k, $plainText, $this->mode, $this->iv);
    }
    
    public function decrypt()
    {
         return strtok(mcrypt_decrypt($this->enc, $this->k, $this->cipher, $this->mode, $this->iv), "\0");
    }

   public function getCipher()
   {
      return bin2hex($this->cipher);
   }
}

 

Mając te dwie klasy można przetestować szyfrowanie:

$ceasar = new Caesar();
$ceasar->encrypt('abcdefgh');
echo "Cezar dekrypt:".$ceasar->decrypt()."\n";

//demo Advanced Encryption Standard(AES)
$aes = new Rijndael();
$aes->encrypt('abcdefgh');
echo 'aes szyfrogram:'. $aes->getCipher()."\n";
echo "Aes dekrypt:".$aes->decrypt()."\n";


//resultat:
//Cezar dekrypt:abcdefgh
//aes szyfrogram:80f6dda813ed89af3f1d4b3bbaaee426
//Aes dekrypt:abcdefgh

Teraz przejdzmy do sedna, stworzymy funkcję, która nie wiedząc jakiego algorytmu używa po prostu zaszyfruje nam tekst dostając obiekt. Funkcja ta wie, że ten algorytm będzie posiadał metody szyfruj i deszyfruj ang encrypt, decrypt i to jej wystarcza.

function encrypt_me($string, ICrypt $encryptor)
{
   $encryptor->encrypt($string); //encrypts string using ICrypt object
   echo $encryptor->decrypt($string); //decrypts string
   echo "\n";
}

i test tej funkcji:

encrypt_me("top secret data", $ceasar);
encrypt_me("top secret data", $aes);

Całość kodu można zobaczyć i przetestować tutaj. Jeśli ktoś chce używać kodu w komercyjnych aplikacjach powinien zapewnić losowe generowanie klucza, gdyż w klasie, którą napisałem klucz jest stały i tak nie powinno być. Polecam też użyć innego mechanizmu wiązania bloków w zależności od potrzeb. Nie polecam używać EBC gdyż jest to najłatwiejszy sposób do złamania przez hakerów i nie wykorzystuje mechanizmu wektora inicacji (IV).