2006 06 RSA w PHP chronimy nasze dane przy użyciu kryptografii asymetrycznej [Kryptografia]
Bezpieczeństwo RSA w PHP: chronimy nasze dane przy użyciu kryptografii asymetrycznej Stopień trudności: lll Kamil Karczmarczyk Zabezpieczenie aplikacji PHP przed włamaniami to nie wsystko: jeżeli przesyłamy dane czystym tekstem, zawsze może się znależć ktoś, kto je przechwyci podsłuchując transmisję w Internecie. Aby zadbać o bezpieczeństwo danych, musimy więc sięgnąc po kryptografię asymetryczną RSA, która daje obecnie największą pewność jego ochrony i zastosować ją zarówno po stronie serwera, jak i klienta. ieodłączną częścią każdego me- haśle. Algorytm ten (w naszym przy- chanizmu logowania użytkowni- padku zaimplementowany w języku Ja- Nków jest przesyłanie hasła wpi- vaScript i działający na przeglądarce sanego na komputerze klienta (w przy- internetowej) zamienia hasło na spe- padku aplikacji webowych w przeglą- cjalny hash, podczas generowania któ- darce internetowej) na serwer, na którym rego używany jest również tajny klucz. (przeważnie w bazie danych) przechowy- Następnie, otrzymany skrót jest wysy- wane są skróty MD5 haseł (zob. Ramka łany do serwera, gdzie podobnie jak Podstawy MD5). Ponieważ te skróty są przy sprawdzaniu hasła przesyłanego jednokierunkowe i odtworzenie haseł na czystym tekstem jest porównywany ich podstawie nie jest możliwe, więc kra- z hashami zapisanymi w bazie danych. W SIECI dzież bazy nie da sprawcy wielu możli- Korzystając z tej metody wyeliminuje- wości włamania. Poważnym problemem my ryzyko podsłuchu (nie przesyłamy jest natomiast wspomniane już przesyła- 1. http://pear.php.net/package/ Crypt_RSA/ klasa nie hasła jako czystego tekstu: wystarczy, Crypt_RSA (PHP) Co należy wiedzieć... aby intruz podsłuchał transmisję pomiędzy 2. http://pear.php.net/package/ Należy znać podstawy programowania Crypt_Blowfish/ klasa klientem a serwerem (np. za pomocą snif- w PHP oraz AJAX. Przydatna będzie też Crypt_Blowfish (PHP) fera), a będzie mógł bez problemu doko- podstawowa wiedza na temat kryptografii. 3. http://ohdave.com/rsa/ Im- nać agresji. plementacja RSA (JS) 4. http://en.wikipedia.org/wiki/ W poprzednim numerze PHP So- Co obiecujemy... RSA Opis algorytmu RSA Pokażemy zasadę działania algorytmu lutions, w artykule Kryptografia w PHP 5. http://en.wikipedia.org/wiki/ asymetrycznego RSA i zademonstruje- Blowfish_(cipher) Opis prezentowaliśmy rozwiązanie tego pro- my, jak przy jego użyciu stworzyć system algorytmu Blowfish blemu przy użyciu algorytmu HMAC- bezpiecznego logowania. 6. http://advajax.anakin.us/ Obiekt advancedAJAX MD5 po stronie klienta na wpisywanym www.phpsolmag.org PHP Solutions Nr 6/2006 32 RSA w PHP Bezpieczeństwo samego hasła, lecz jego skrót), ale mo- żemy jej użyć wyłącznie wobec już ist- RSA: Zasada działania RSA to pierwszy (wynaleziony w 1977 roku) i obecnie najpopularniejszy algorytm szyfro- niejących haseł, których skróty zostały wania asymetrycznego, używany powszechnie np. w handlu elektronicznym czy w celu utworzone i zapisane w bazie na ser- podpisywania emaili. Zasadę działania algorytmu RSA przedstawiamy na Rysunku 1. Je- werze. W jaki sposób więc przeprowa- go idea (jak każdego szyfru asymetrycznego) polega na użyciu dwóch k luczy: publiczne- dzimy proces rejestracji nowego kon- go i prywatnego. Oba z nich są generowane przez odbiorcę wiadomości, po czym klucz publiczny jest jawnie przesyłany nadawcy wiadomości, może też być zamieszczony np. na ta na serwerze? Musimy w końcu prze- stronie WWW do pobrania. Nadawca szyfruje tekst za pomocą klucza publicznego i wysy- słać na serwer informacje o nowym ha- ła go odbiorcy. Zaszyfrowany tekst można z kolei odszyfrować tylko kluczem prywatnym, śle: z oczywistych powodów nie chce- który posiada odbiorca. Nie wnikając w teorię matematyczną, która leży u podstaw RSA, warto zapamiętać, że klucz publiczny składa się z dwóch części: wykładnika publicznego my tego robić przy użyciu czystego (ang. public exponent) i modułu (ang. modulus), a klucz prywatny z wykładnika prywat- tekstu; przesyłanie skrótu takiego ha- nego (ang. private exponent) i tego samego modułu te informacje będą nam potrzebne sła również nie uchroni nas przed nie- pózniej, gdy będziemy korzystali z klas PEAR-owych do obsługi RSA. bezpieczeństwem podsłuchu: ktoś, kto w tym momencie przechwyci ten skrót (rozpozna, że chodzi o dodawanie no- Tworzymy system JavaScript). W pierwszym przypadku, wego konta np. po danych wysyłanych bezpiecznego logowania do szyfrowania za pomocą RSA posłuży z formularza), będzie mógł go potem dla aplikacji Notatki nam klasa Crypt_RSA z repozytorium zwyczajnie wykorzystać przy logowa- osobiste PEAR (pear.php.net). Po stronie klienta niu się na serwerze. Szyfrowanie da- Pokażemy teraz, jak wykorzystać algorytm użyjemy natomiast implementacji algo- nych za pomocą klucza symetryczne- szyfrujący RSA w PHP, tworząc system rytmu RSA w języku JavaScript umiesz- go (np.3DES lub twofish) również nie bezpiecznego logowania dla aplikacji czonej na stronie http://ohdave.com/rsa/. wchodzi w grę, gdyż taki klucz musiał- Notatki osobiste, która będzie służyła jako Musimy z niej pobrać pliki: BigInt.js, Bar- by być znany zarówno klientowi, jak i nasz osobisty notatnik online. rett.js oraz RSA.js. Wymianę danych serwerowi, co oznacza konieczność je- będziemy obsługiwać przy pomocy go transmisji przez Internet oraz czy- Założenia technologii AJAX, a konkretnie projektu ni go podatnym na podsłuch. Jedy- Chcemy, aby nasza aplikacja posiadała advAJAX (zarówno o tym projekcie, jak nym sensownym rozwiązaniem proble- system logowania poszczególnych użyt- i o technologii AJAX pisaliśmy w nume- mu zabezpieczenia hasła będzie uży- kowników oraz możliwość dodawania no- rze 1/2006). Pobieramy więc plik adva- cie asymetrycznego algorytmu szy- wych kont. Każdy posiadacz konta będzie jax.js ze strony http://advajax.anakin.us frującego RSA. Algorytm ten działa w mógł wyświetlać swoje notatki, a także do- i zabieramy się do pracy. oparciu o dwa klucze: publiczny (który dawać nowe. Cała komunikacja pomiędzy jest ogólnie dostępny) i prywatny (do- klientem a serwerem będzie szyfrowana. Rejestracja kont stępny wyłącznie na serwerze). Wię- Obsługą procesu rejestracji nowych kont cej informacji na temat algorytmu RSA Przygotowania zajmą się trzy pliki: zamieściliśmy w Ramce RSA: Zasada Tworząc nasz system bezpiecznego działania. logowania skorzystamy z gotowych ź register.php będzie odpowiadał za implementacji algorytmów szyfrowania, wyświetlenie formularza zakładania zarówno tych działających po stronie konta wraz z wygenerowanym wcze- Podstawy MD5 serwera (w PHP), jak i klienta (w języku śniej kluczem publicznym, MD5 jest algorytmem haszującym, zwa- nym również jednokierunkową funkcją skrótu. W wyniku jego działania, w oparciu o podstawione dane powstaje unikalny 128-bitowy skrót. Odtworze- nie danych na jego podstawie nie jest możliwe. Unikalność i jednokierunko- wość działania MD5 umożliwia jego za- stosowanie np. przy sprawdzaniu haseł (na serwerze zamieszczane są jedynie skróty haseł, aby uniemożliwić prze- chwycenie samych haseł w razie kra- dzieży danych), podpisywaniu plików umieszczanym w wielu repozytoriach plikom towarzyszą skróty MD5: oblicza- jąc skrót pobranego pliku i porównując go z tym zamieszczonym na serwerze możemy sprawdzić, czy archiwum nie jest uszkodzone (to samo da się zasto- sować np. przy porównywaniu zawar- tości wypalonej płyty CD lub DVD z jej obrazem, choć trwa to dość długo). Rysunek 1. Schemat działania RSA 6/2006 PHP Solutions Nr 6/2006 www.phpsolmag.org 33 Bezpieczeństwo RSA w PHP ź register.js będzie w nim następo- Listing 1. Plik register.php generowanie kluczy i wyswietlenie formularza wało szyfrowanie danych zawartych w formularzu oraz ich wysyłanie na serwer, session_start(); ź register2.php w tym pliku nastą- pi odszyfrowanie danych i założenie // generowanie pary kluczy konta. require_once 'Crypt/RSA.php'; $key_pair = new Crypt_RSA_KeyPair(512); Generowanie kluczy $public_key = $key_pair->getPublicKey(); $private_key = $key_pair->getPrivateKey(); Spójrzmy na Listing 1, na którym za- mieściliśmy plik register.php. Zacznie- $enc_exp = $public_key->getExponent(); my od wygenerowania pary kluczy $dec_exp = $private_key->getExponent(); (publicznego i prywatnego) poprzez $modulus = $public_key->getModulus(); utworzenie obiektu $key_pair klasy // zapamiętanie danych do pózniejszego dekodowania Crypt_RSA_KeyPair oraz użycie jej me- $_SESSION['rsa']=serialize($key_pair); tod getPublicKey() i getPrivateKey(). Oba wygenerowane klucze stanowią // konwersja danych do szyfrowania na kod szesnastkowy (heksadecymalny) osobne obiekty, z których następnie $enc_exponent = bin2hex($enc_exp); wydobywamy oba wspomniane wcze- $mod = bin2hex($modulus); śniej (zob. Ramka RSA: zasada dzia- ?> łania) wykładniki (getExponent()) oraz
moduł (getModulus()). Następnie se-
rializujemy (serialize()) i zapisujemy w sesji obiekt $key_pair do pózniejszego MyNotes - registration wykorzystania oraz konwertujemy klucz
publiczny (a właściwie jego wykładnik i
moduł) z postaci binarnej na wartość
szesnastkową (bin2hex()), ponieważ
algorytm RSA, z którego będziemy ko- rzystać po stronie przeglądarki, wyma-
ga podania danych dotyczących klucza
ta (kod HTML). Jedynym dynamicznym
elementem, który umieścimy w tym ko-
dzie, będzie osadzony w prezentowa- MyNotes - Registration
nym pliku fragment skryptu JavaScript, który odpowiada za dołączenie plików:
nia onload zdefiniowanego w znacz- niku , czyli po każdym załado- waniu strony WWW umieszczonej po-
między a . Wewnątrz
updateObjects() tworzymy parę kluczy RSA, czyli obiekt RSAKeyPair, na pod- www.phpsolmag.org PHP Solutions Nr 6/2006 34 RSA w PHP Bezpieczeństwo stawie publicznego wykładnika i modu- szyfrowanie przesyłanych danych, czyli Deszyfrowanie i tworzenie konta łu. Prywatny wykładnik nie jest nam po- wartości wszystkich pól formularza (na- Przejdzmy do omówienia zawartości trzebny, więc wpisujemy 0. Następnie zwy użytkownika, hasła, adresu ema- wspomnianego już pliku register2.php wdrażamy obsługę formularza za po- il, imienia i nazwiska) za pomocą al- (Listing 3). Otrzymuje on przekazane za mocą obiektu advAJAX. Metoda assign gorytmu RSA. Funkcja onSuccess wy- pomocą advAJAX dane z formularza, pobiera (jako parametr) nazwę formula- świetla wewnątrz znacznika
(o które musimy teraz odszyfrować. Posłu- rza oraz obiekt zawierający metody je- id= response ) dane zwrócone przez żymy się do tego zapisanym wcześniej go obsługi. Funkcja OnInitialization plik register2.php, do którego wysłali- w sesji jako rsa obiektem $key_pair, jest wykonywana jeszcze przed wysła- śmy wprowadzone w formularzu war- który nazwiemy $rsa_keys i zdeseria- niem formularza. To w niej następuje tości. lizujemy (unserialize()). Następnie wydobędziemy z tego obiektu klucz prywatny, który także będzie obiektem Listing 2. Plik register.js szyfrowanie i wysyłanie danych o nazwie $priv. Potem utworzymy nowy function updateObjects() { obiekt $rsa_obj klasy Crypt_RSA i wy- var key; wołujemy jego metodę DecryptBinary() key = new RSAKeyPair(enc_exp,0,modulus); odpowiedzialną za deszyfrowanie function $(id) { return document.getElementById(id); } danych (każdego z przesłanych pól for- advAJAX.assign($("registerForm"), { mularza z osobna). Metoda ta przyjmuje onInitialization : function(obj) { // szyfrowanie danych z formularza dwa parametry: zaszyfrowany tekst oraz obj.parameters["username"]=encryptedString( utworzony przez nas wcześniej klucz key,obj.parameters["username"]); prywatny $priv (obiekt klasy Crypt_RSA_ obj.parameters["password"]=encryptedString( KeyPair). Ponieważ dane pochodzące key,obj.parameters["password"]); z formularza zostały po zaszyfrowaniu obj.parameters["email"]=encryptedString(key,obj.parameters["email"]); obj.parameters["name"]=encryptedString(key,obj.parameters["name"]); przekonwertowane na system szesnast- obj.parameters["surname"]=encryptedString(key,obj.parameters["surname"]); kowy, więc musimy je przywrócić do }, formy binarnej przy użyciu funkcji he- onSuccess : function(obj) { x2bin(), którą napiszemy sami (Listing 4), $("response").innerHTML=obj.responseText; gdyż nie została ona zaimplementowana }, onError : function() { w PHP. alert("Can't connect to server."); Mając odszyfrowane dane użyt- } kownika, możemy przystąpić do jego }); rejestracji w serwisie. Robimy to np. } przy pomocy klasy user, której imple- Listing 3. Plik register2.php deszyfrowanie i zapisanie danych nowego usera mentację pominęliśmy, gdyż nie jest ona istotna dla ukazania technik kryp- tograficznych. session_start(); require_once 'Crypt/RSA.php'; // odczytanie przechowywanego obiektu (kluczy) Kryptografia hybrydowa $rsa_keys = unserialize($_SESSION['rsa']); Jako kryptografia hybrydowa rozumiane $priv = $rsa_keys->getPrivateKey(); jest jednoczesne użycie kryptografii sy- $rsa_obj = new Crypt_RSA; metrycznej i asymetrycznej. Dotychczas // deszyfrowanie szyfrowaliśmy dane tylko w jednym kie- $username=$rsa_obj->decryptBinary(hex2bin($_POST['username']),$priv); $password=$rsa_obj->decryptBinary(hex2bin($_POST['password']),$priv); runku: od użytkownika do serwera. Aby $email = $rsa_obj->decryptBinary(hex2bin($_POST['email']),$priv); zrealizować nasz projekt, będziemy jed- $name = $rsa_obj->decryptBinary(hex2bin($_POST['name']),$priv); nak potrzebowali szyfrowania dwukierun- $surname = $rsa_obj->decryptBinary(hex2bin($_POST['surname']),$priv); kowego, gdyż w jedną stronę będziemy // tworzenie nowego konta wysyłali nasz login i hasło oraz dodawali $user = new user; $result = $user->register($username,$password,$email,$name,$surname); nowe notatki, a w drugą wyświetlali ist- if ($result) { echo "Your account are successfully created."; niejące notatki. Zabezpieczenie danych }else { echo "error: your account can't created!"; } przy tych czynnościach za pomoca sa- ?> mego RSA byłoby trudne, wykorzystamy więc dodatkowo symetryczny algorytm Listing 4. Funkcja hex2bin blowfish. function hex2bin($hex) { $str=""; Przygotowania for($i=0;$iBędziemy więc potrzebowali implemen- $str.=chr(hexdec(substr($hex,$i,2))); tacji algorytmu blowfish. W PHP posłu- } return $str; } żymy się do tego klasą Crypt_Blowfish z repozytorium PEAR. Po stronie prze- PHP Solutions Nr 6/2006 www.phpsolmag.org 35 Bezpieczeństwo RSA w PHP glądarki internetowej, obsługą blowfisha Listing 5. Formularz logowania zajmie się plik encrypt.js, który możemy pobrać ze strony www.farfarfar.com lub // ... bezpośrednio z http://limak.blg.pl/crypto/
//... encrypt.js. Cała nasza aplikacja, podob-
nie jak to było w przypadku rejestracji, MyNotes - Your own on-line notices
będzie się składała z trzech plików:
index.php, index.js i index2.php, których
w miejscu, w którym różni się on Listing 6. index.js obsługa AJAX od register.php. Dołączamy w nim też skrypt encrypt.js w języku JavaScript, function updateObjects() { który odpowiada za szyfrowanie i de- szyfrowanie algorytmem blowfish. Tym // tworzenie klucza publicznego var key; razem zagniezdzimy nasz formularz key = new RSAKeyPair(enc_exp,0,modulus); wewnątrz znacznika
, któremu // tworzenie klucza symetrycznego nadamy id html: pózniej będziemy dy- var blowfishKey = ""; namicznie zastępowali ten fragment var chars="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" kodu inną treścią. var i; for (i=0; i<25; i++) { blowfishKey += chars.charAt(Math.floor(Math.random()*62)) Obsługa AJAX } Różnice między plikiem index.js a regi- document.forms["loginForm"].elements["blowfish"].value=blowfishKey; ster.js są znacznie większe, niż pomię- function $(id) { return document.getElementById(id); } dzy index.php a register.php. Na Listin- advAJAX.assign($("loginForm"), { onInitialization : function(obj) { gu 6 prezentujemy obsługę praktycznie // szyfrowanie danych z formularza (RSA)) całej szyfrowanej wymiany pomiędzy obj.parameters["username"] = encryptedString(key, obj.parameters[ plikami index.php i index2.php. Tworzy- "username"]); my w nim zarówno klucz asymetrycz- obj.parameters["password"] = encryptedString(key, obj.parameters[ ny dla algorytmu RSA, jak i losowy ciąg "password"]); obj.parameters["blowfish"] = encryptedString(key, blowfishKey); znaków będący kluczem symetrycznym }, blowfish. onSuccess : function(obj) { Następnie korzystamy ze znanego // deszyfrowanie blowfish już nam obiektu advAJAX, który wystę- $("html").innerHTML = secureDecrypt(obj.responseText, blowfishKey); puje trzykrotnie. Pierwszy raz odwołu- } }); je się do formularza loginForm (advA- advAJAX.assign($("addNoteForm"), { JAX.assign) i za pomocą RSA szyfru- onInitialization : function(obj) { je login, hasło oraz dodatkowo wyge- // szyfrowanie blowfish nerowany klucz blowfish, a następnie obj.parameters["title"] = secureEncrypt(obj.parameters["title"], przekazuje je do pliku login2.php. Od blowfishKey); obj.parameters["note"] = secureEncrypt(obj.parameters["note"], tej pory, po otrzymaniu klucza blow- blowfishKey); fish przez serwer, wymiana szyfrowa- }, nych danych odbywa się przy pomo- onSuccess : function(obj) { cy algorytmu symetrycznego. RSA było $("html").innerHTML = obj.responseText; potrzebne jedynie do przesłania syme- } }); trycznego klucza. } Gdy teraz będziemy chcieli wyświe- function goto(page, pageid=0) { tlić wszystkie nasze notatki, wywoła- advAJAX.post({ my funkcję goto() z parametrem note, url : "index2.php?page="+page+"&pageid="+pageid, aby w odpowiedzi z pliku index2.php onSuccess : function(obj) { $("html").innerHTML = secureDecrypt(obj.responseText, blowfishKey); otrzymać zaszyfrowany fragment ko- } du odpowiedzialny za ich wyświetle- }); nie. To, w jaki sposób plik index2.php } szyfruje dane, widzimy na Listingu 7. Aby odpowiednio zaszyfrować i odszy- www.phpsolmag.org PHP Solutions Nr 6/2006 36 RSA w PHP Bezpieczeństwo frować dane, po stronie przeglądarki Listing 7. Zawartość pliku index2.php używamy w skrypcie JavaScript funk- cji secureEncrypt() i secureDecrypt(), natomiast w PHP wykorzystujemy PE- session_start(); AR-ową klasę Crypt_Blowfish, która require_once 'Crypt/RSA.php'; jest bardzo intuicyjna i prosta w uży- require_once 'Crypt/Blowfish.php'; ciu. Z listingów możemy wywniosko- wać, że sam proces przesyłania nowej echo "my notes | "; notatki w formie zaszyfrowanej (obiekt echo "add note | "; echo "logout
"; advAJAX z parametrem odwołującym się do formularza AddNoteForm) oraz if(!isset($_GET['page'])) { // logowanie jej deszyfrowanie z poziomu PHP jest $rsa_keys = unserialize($_SESSION['rsa']); praktycznie identyczny, jak w przypad- $priv = $rsa_keys->getPrivateKey(); ku pobierania istniejącej notatki z ser- $rsa_obj = new Crypt_RSA; $username = $rsa_obj->decryptBinary(hex2bin($_POST['username']),$priv); wera, tyle że kolejność wykonywania $password = $rsa_obj->decryptBinary(hex2bin($_POST['password']),$priv); czynności jest odwrotna. $blowfishKey = $rsa_obj->decryptBinary(hex2bin($_POST['blowfish']),$priv); Podsumowanie $_SESSION['username'] = $username; W przedstawionej aplikacji pokazali- $_SESSION['password'] = $password; $_SESSION['blowfishKey'] = $blowfishKey; śmy, w jaki sposób można skorzystać z dwóch metod kryptograficznych (asy- $user = new user($username, $password); metrycznej i symetrycznej) oraz poda- $notes = $user->getNotes(); liśmy przykładowe zastosowanie tych $html=""; technik. Potęga RSA w połączeniu z foreach ($notes as $note) { prostotą języka PHP jak też z całkiem $html.=""; niezłą funkcjonalnością JavaScrip- $html.=$note['title']." "; tu umożliwia tworzenie naprawdę bez- } piecznych aplikacji, które będą odpor- ne na podsłuch i przechwytywanie da- $blowfish = new Crypt_Blowfish($blowfishKey); echo(base64_encode($blowfish->Encrypt($html))); nych. } Zachęcamy do korzystania z krypto- else if ($_GET['page']=="addnote") { // wyświetlanie formularza grafii we własnych projektach zwłasz- $html = <<cza tam, gdzie poufność przesyłanych i
stych, poprzez narzędzia używane we- heredochtml; wnątrz firmy (np. do zarządzania pro- $blowfish = new Crypt_Blowfish($_SESSION['blowfishKey']); echo(base64_encode($blowfish->Encrypt($html))); jektem, danymi księgowymi czy biz- } else if ($_GET['page']=="addnote2") { // dodawanie notatki nesplanem), po dostępne dla tysię- $user = new user($_SESSION['username'], $_SESSION['password']); cy użytkowników jednocześnie syste- my e-commerce, takie jak sklepy inter- // ustawienie klucza blowfish netowe czy pasaże aukcyjne. Na pohy- $blowfish = new Crypt_Blowfish($_SESSION['blowfishKey']); //deszyfrowanie blowfish bel intruzom! n $title = $blowfish->decrypt(base64_decode($_POST['title'])); $noteText = $blowfish->decrypt(base64_decode($_POST['note'])); //dodawanie notki $user->addNote($title,$noteText); echo "new note added"; } else if (($_GET['page']=="note")&&(isset($_GET['pageid']))) { O autorze // wyświetlanie notki $user = new user($_SESSION['username'], $_SESSION['password']); Kamil Karczmarczyk jest uczniem Li- $note = $user->getNoteById($_GET['pageid']); ceum Ogólnokształcącego. Od kilku lat $html = "".$note['title']." "; hobbystycznie zajmuje się programowa- $html .= "
".$note['text']."
"; niem, między innymi w PHP. Interesuje $blowfish = new Crypt_Blowfish($_SESSION['blowfishKey']); się bezpieczeństwem sieci, kryptografią echo(base64_encode($blowfish->Encrypt($html))); oraz matematyką. } ?> Kontakt z autorem: limak@mmj.pl PHP Solutions Nr 6/2006 www.phpsolmag.org 37