Kirjoittaja: Metabolix
Kirjoitettu: 20.12.2012 – 22.06.2013
Tagit: ohjelmointitavat, tietoturva, koodi näytille, vinkki
PHP:hen on vihdoin lisätty helpot funktiot salasanan tiivisteen laskemiseen ja salasanan tarkistamiseen. PHP:n oma menetelmä on turvallisempi kuin monesta vanhasta koodista tuttu yksinkertainen MD5, ja se on myös suunniteltu niin, että sitä on helppo käyttää.
Uudet funktiot lisättiin PHP:hen versiossa 5.5. Vanhempiin versioihin 5.3.7:stä alkaen ne voi ladata GitHubista ja liittää mukaan include-komennolla.
Funktio password_hash on tiivisteen laskemista varten silloin, kun käyttäjä rekisteröityy tai vaihtaa salasanansa. Se huolehtii automaattisesti salasanan turvallisesta suolaamisesta ja sopivasta tiivistealgoritmista. Tietokantaan pitää tallentaa vain tiiviste, ei koskaan salasanaa.
$tiiviste = password_hash($salasana, PASSWORD_DEFAULT);
Funktio password_verify tarkistaa käyttäjän salasanan. Sille annetaan syötetty salasana ja aiempi tiiviste ja se kertoo, onko salasana oikea. Aiempi tiiviste pitää siis ensin hakea tietokannasta.
if (password_verify($salasana, $tiiviste)) { // Salasana on oikein. }
Funktio password_needs_rehash kertoo, pitäisikö salasanan tiiviste laskea tietokantaan uudestaan. Tarkistus pitää tehdä kirjautumisen jälkeen, ja tarvittaessa tiiviste pitää laskea uudestaan ja tallentaa. Tällä on merkitystä tulevaisuudessa, kun PHP:n asetukset muuttuvat ja näitä funktioita vahvistetaan uusilla algoritmeilla tai muilla tavoin.
if (password_verify($salasana, $tiiviste)) { // Salasana on oikein. Tarkistetaan, pitääkö päivittää. if (password_needs_rehash($tiiviste, PASSWORD_DEFAULT)) { $tiiviste = password_hash($salasana, PASSWORD_DEFAULT); // TODO: Tallenna uusi tiiviste tietokantaan! } }
Tässä toteutetaan osa kuvitteellisesta Käyttäjä-luokasta. Käyttäjän hakemisen ja tallentamisen joudut tekemään itse!
class Käyttäjä { public $tunnus; public $tiiviste; public function asetaSalasana($salasana) { // Tiiviste lasketaan funktiolla password_hash. $this->tiiviste = password_hash($salasana, PASSWORD_DEFAULT); } public function tarkistaSalasana($salasana) { // Salasana tarkistetaan funktiolla password_verify. if (!password_verify($salasana, $this->tiiviste)) { return false; } else { // Kirjautuminen ok, päivitetään vielä tiiviste tarvittaessa. if (password_needs_rehash($this->tiiviste, PASSWORD_DEFAULT)) { $this->asetaSalasana($_POST["salasana"]); $this->tallenna(); } return true; } } // Nämä toiminnot joudut tekemään itse: public static function hae($tunnus) { throw new Exception("Käyttäjä::hae puuttuu!"); } public function tallenna() { throw new Exception("Käyttäjä::tallenna puuttuu!"); } }
Rekisteröityminen tapahtuisi näillä funktioilla suunnilleen näin:
$käyttäjä = new Käyttäjä(); $käyttäjä->tunnus = $_POST["tunnus"]; $käyttäjä->asetaSalasana($_POST["salasana"]); $käyttäjä->tallenna();
Käyttäjän kirjautuminen tarkistettaisiin tähän tapaan:
Pieni sivukommentti tuohon käyttäjän kirjautumisen tarkistamiseen. Käyttäjälle itselleen ei kannata ilmoittaa erikseen, että salasana on väärä. Tämä yleensä paljastaa sen, että käyttäjätunnus on kuitenkin ollut oikea. Sen jälkeen voidaan kyseisen käyttäjätunnuksen salasanaa ruveta kaivamaan esille omilla konsteillaan. Parasta on aina ilmoittaa, että esim. että "kirjautuminen epäonnistui". Näin saadaan edes vähän vaikeutettua epämääräisten tunkeutujien tehtävää.
Tässä esimerkissä ei nyt tietenkään edes vihjattu, että näitä virheilmoituksia näytettäisiin käyttäjälle. Aloitteleva koodari voi kuitenkin tuosta viimeisestä koodipätkästä saada sellaisen kuvan.
Kiitos Metabolixille ansiokkaasta koodivinkistä :).
Arttut02 kirjoitti:
Käyttäjälle itselleen ei kannata ilmoittaa erikseen, että salasana on väärä. Tämä yleensä paljastaa sen, että käyttäjätunnus on kuitenkin ollut oikea.
Toisaalta monilla sivustoilla (kuten täälläkin) käyttäjätunnuksia saa helposti selville keskustelusta tai käyttäjien profiileja selaamalla, jolloin ei ole paljon hyötyä piilotella niitä muuallakaan. Myös käyttäjän kannalta on mukavampi saada selvä ilmoitus, ja käytännön haitta on olematon.
Arttut02 kirjoitti:
Sen jälkeen voidaan kyseisen käyttäjätunnuksen salasanaa ruveta kaivamaan esille omilla konsteillaan.
Mitähän ne konstit ovat? Netin yli kokeileminen on hidasta, tässä esitellyillä funktioilla siitä on tehty tarkoituksella vielä hitaampaa, ja joka tapauksessa on järkevää blokata väliaikaisesti osoitteet, joista tulee brute force -hyökkäyksiä.
Metabolix kirjoitti:
Arttut02 kirjoitti:
Käyttäjälle itselleen ei kannata ilmoittaa erikseen, että salasana on väärä. Tämä yleensä paljastaa sen, että käyttäjätunnus on kuitenkin ollut oikea.
Toisaalta monilla sivustoilla (kuten täälläkin) käyttäjätunnuksia saa helposti selville keskustelusta tai käyttäjien profiileja selaamalla, jolloin ei ole paljon hyötyä piilotella niitä muuallakaan. Myös käyttäjän kannalta on mukavampi saada selvä ilmoitus, ja käytännön haitta on olematon.
Löytyy kuitenkin myös sivuja, missä ei käyttäjätunnuksia "jaeta" kaikkien nähtäville, esim. joku nettisivujen hallintajärjestelmä.
Hyökkääjä löytää tiensä tuolle hallintajärjestelmän sivulle. Hän käyttää jotain automaattista ohjelmaa, mikä ajaa läpi pitkän listan yleisistä käyttäjätunnuksista. Hyökkääjän ohjelma tunnistaa "väärä salasana"-ilmoituksen ja tekee listan kaikista oikeista käyttäjätunnuksista. Tämän jälkeen pystytään ajamaan läpi näille käyttäjätunnuksille lista tunnetuista salasanoista. Tämä nopeuttaa ja vähentää hyökkääjän työtä huomattavasti, kun ei tarvitse kokeilla jokaiselle yleiselle käyttäjätunnukselle jokaista yleistä salasanaa.
Tietysti käyttäjätunnus on harvoin mikään salaisuus (sehän näkyy yleensä kirjautuessa normaali tekstinä), mutta jos hyökkääjällä on halua, taitoa ja aikaa, niin miksi antaa ylimääräistä lisäetua, kun sen saa helposti estettyä.
Metabolix kirjoitti:
Arttut02 kirjoitti:
Sen jälkeen voidaan kyseisen käyttäjätunnuksen salasanaa ruveta kaivamaan esille omilla konsteillaan.
Mitähän ne konstit ovat? Netin yli kokeileminen on hidasta, tässä esitellyillä funktioilla siitä on tehty tarkoituksella vielä hitaampaa, ja joka tapauksessa on järkevää blokata väliaikaisesti osoitteet, joista tulee brute force -hyökkäyksiä.
Brute force, mahdolliset hyökkäykset sessioniin, social engineering. Voihan noita varmoja käyttäjätunnuksia jakaa muille kiinnostuneille ja varmasti monia muitakin tapoja löytyy. Kyllä näillä henkilöillä yleensä mielikuvitusta riittää. :)
Nämähän nyt ovat tietysti painotuskysymyksiä...
Kiitos!
Metabolix kirjoitti:
Mitähän ne konstit ovat? Netin yli kokeileminen on hidasta, tässä esitellyillä funktioilla siitä on tehty tarkoituksella vielä hitaampaa, ja joka tapauksessa on järkevää blokata väliaikaisesti osoitteet, joista tulee brute force -hyökkäyksiä.
Kyllä, brute force on hidasta netin yli, mikäli sitä ei tehdä hajautetusti, jolloin myöskään osoitteiden estämistä ei järkevästi voi toteuttaa.
Mutta itse opas vaikuttaa hyödylliseltä ja hyvältä. Onko tämä PHP:n oma tiivistealgoritmi tehokkaampi ja parempi kuin esimerkiksi SHA-256 tai BCrypt?
brute forcea toki voi hankaloittaa bannaamalla vaikka käyttäjätili määräaikaisesti, kasvattaen bannin pituutta aina kun käyttäjätunnuksen salasana laitetaan väärin (vaikka aloittaen ekan viiden väärän salasanan jälkeen). vähän samaan tyyliin kuin puhelimen suojakoodi toimii.
dartvaneri kirjoitti:
Mutta itse opas vaikuttaa hyödylliseltä ja hyvältä. Onko tämä PHP:n oma tiivistealgoritmi tehokkaampi ja parempi kuin esimerkiksi SHA-256 tai BCrypt?
Riippuu käytetystä algoritmista. PHP 5.5 alkaen oletusalgoritmi on BCrypt, joten näillä esimerkkikoodeilla se on yhtä hyvä kuin BCrypt ja parempi kuin SHA-256. Toki BCryptin vaikeustasoa voi säätää. Se on suunniteltu sellaiseksi, että sitä voidaan vaikeuttaa sitä mukaa kun laskentateho kasvaa.