Hirdetés

TOTP generátor PHP-ra

Egyik hitelesítő alkalmazás sem szimpatikus? Írd meg magad ~20 sorban!

Az Ügyfélkapu+ új, két faktoros hitelesítése témájában számos írás és fórum topic született már a PH!-n (Logout-on), azonban nagyjából mindegyik cikk és hozzászóló egy harmadik fél által gyártott program használatát vagy telepítését javasolta (PC-re, mobilra, böngészőbe stb.). Amikor beleástam magam a témába, az eredeti célom az volt, hogy megismerjem a TOTP (RFC 6238) szabványt és hogy kiderítsem, mennyire rocket science egy ilyet saját kezűleg lefejleszteni. Nos, kiderült, hogy nem az, PHP-ban néhány sorból összedobható:

<?php

require_once("Base2n.php");

date_default_timezone_set('Europe/Budapest');
$base32 = new Base2n(5, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567', false, true, true);

function h(string $key, int $pad, string $text)
{
$key = str_pad($key, 64, chr(0x00), STR_PAD_RIGHT);
for ($i = strlen($key) - 1; $i >= 0; $i--) {
$key[$i] = chr(ord($key[$i]) ^ $pad);
}
return hash('sha1', $key . $text, true);
}

$t = time();
$t30 = (int)floor($t / 30);
$c = hex2bin(str_pad(dechex($t30), 16, '0', STR_PAD_LEFT));
$key = $base32->decode('IDEJONAZUGYFELKAPUTOLKAPOTT16JEGYUKULCSOD');

$h = h($key, 0x5c, h($key, 0x36, $c));
$o = ord($h[strlen($h) - 1]) & 0x0f;
$r = (ord($h[$o]) & 0x7f) << 24
| ord($h[$o + 1]) << 16
| ord($h[$o + 2]) << 8
| ord($h[$o + 3]);

echo str_pad($r % 1000000, 6, '0', STR_PAD_LEFT) . "\n";

Az igazsághoz persze hozzátartozik, hogy ez a program is behúz egy 3. fél által gyártott kódot, ami a Base32 dekódoláshoz szükséges. Ez azonban egyetlen osztály, aminek forráskódját átnézve könnyen meggyőződhetünk annak ártalmatlanságáról. Valójában erre azért van szükségünk, mert az a "kulcs", amit az ügyfélkapu+ igénylésekor kapunk, nem maga a kulcs, hanem a kulcsnak egy base32 algoritmussal elkódolt változata, amit felhasználás előtt érdemes lehet dekódolnunk (amennyiben azt a TOTP implementációnk igényli).

Egyébként annak, hogy ezidáig (2025. január végéig) nem született ebből a tényleg végtelenül egyszerű kódból blog bejegyzés, pontosan az volt az oka, hogy én magam is benéztem az ún. "kulcs" mibenlétét és nem mint base32-es kódolt tartalomként tekintettem rá, hanem nyers, kódolatlan tartalomra. Ezért az én 6 jegyű kódjaim mindig eltértek a többi implementáció (pl. Google Authenticator, TOTP.APP, FreeOTP) által generált kódoktól. A "kulcs" base32 kódoltságnak megkésett felismerése után jött helyre az implementációm és lettek pontosak a 6 jegyű kódjaim.

Ha gondolod, próbáld ki Te is, vagy csak szimplán köpködd meg a kódot. Mindenféle visszajelzést örömmel veszek. :R Ha még hasznát is veszed, az külön öröm.

  • ekkold

    őstag

    válasz cinemazealot #4 üzenetére

    Csak javaslat, de szerintem építsd be a base32 dekódoló függvényt a php fájlodba, így nem kellene külső 3. fél által gyártott kódot meghívnod.

    http://skory.gylcomp.hu/ http://www.skory.z-net.hu/ https://skori.hu/ https://skori.spacetechnology.net/

  • cinemazealot

    addikt

    LOGOUT blog

    válasz cinemazealot #8 üzenetére

    "Mert ha jól sejtem, tegnap 18:23:30-kor futtattad."

    Bocs, 18:23:30 és 18:23:59 között. :D

  • cinemazealot

    addikt

    LOGOUT blog

    válasz Inhouse #7 üzenetére

    Még egyszer: a hex2bin() kimenete nem egy hétköznapi (olvasható) string, hanem egy (jelen pillanatban 8 karakter, azaz 8 bájt hosszú) 64 bites integer string-ként tárolva. Pusztán azért, mert így kényelmesebb vele dolgozni. :) Ne próbáld megfejteni "00|00|00|00|03|73|f2|a7" bájtsorozat (string) szöveges jelentését, mert nincs neki olyan. Ez nem más, mint az 57930407 értékű egész szám big-endian ábrázolása, ami a 1737912210/30 matematikai művelet eredményeként született. Mert ha jól sejtem, tegnap 18:23:30-kor futtattad.

    Online szerintem ne akarj PHP-t futtatni, inkább telepíts fel egyet magadnak, futtasd a programot parancssorból és itt-ott állítsd meg, hogy "kiechózd" egyik-másik változót. Viszont kiechózni sem lehet mindent, pl. a bináris tartalom parancssorban is szarul mutatna. Ahhoz előbb érdemes beletenni a bináris tartalmat egy bin2hex()-be, csak hogy hexadecimálisan olvasható legyen. ;)

    Még egy gondolat a stringekhez: a PHP-ban minden bináris tartalom (pl. hang, kép, videó, Excel tábla, Word vagy PDF dokumentum) betölthető stringbe. Nyilvánvaló, hogy egy ilyet kiírni szövegként teljességgel értelmetlen volna, mert az csak egy bájtsor, aminek elemei bőven túlmutathatnak az ASCII kódtáblán.

    Szerk.: Most melózom, de ha igény van rá, este leírom egy hsz-ben az egész program működését sorról-sorra.

    [ Szerkesztve ]

  • Inhouse

    őstag

    válasz cinemazealot #6 üzenetére

    Szia!

    Köszi, hogy válaszoltál. Láttam ezeket, amiket linkeltél, de a te gyakorlati megvalósításod nagyon megtetszett és soronként szerettem volna megérteni és átültetni. De valószínűleg ez mégsem ilyen egyszerű, meghaladja az én képességeim.
    Engem az zavar a hex2bin()-ben, hogy nem értem az eredményét, miért csak (látszólag?) azt a 2 értéket alakítja át?
    00|00|00|00|03|73|f2|a7 -> s�
    Vagy az echo nem ír ki olyat, ami nem olvasható string? De akkor miért nincs 0xA7, alias § nálam, ž netes környezetben? Csak az angol ABC és számok?
    A másik problémám, hogy megnéztem a hash részt, a HB-s változat hexaban ugyanazt az eredményt adja, a RAW opcióval meg csak talán, az értelmesen olvasható ASCII része stimmel, a többit egyelőre nem tudom...lehet, hogy csak megjelenítési gond, ugyanúgy megenné és kiköpné az eredményt... az online PHP futtatós oldal ebben nem barátom.

    [ Szerkesztve ]

    Üdv Inhouse

  • cinemazealot

    addikt

    LOGOUT blog

    válasz Inhouse #5 üzenetére

    Szia Inhouse!

    A hex2bin() pontosan azt teszi, amit a neve sugall, illetve amit a manual ír róla: egy bináris stringet hoz létre. Ennek azonban nem feltétele, hogy a string ember által olvasható is legyen. Ha megfigyeled, az eredménye csupán egy 64 bites (8 bájtos) "big-endian" integer, aminek alapja a 30 másodpercenkénti számláló, kiegészítve balról nulla karakterekkel (!). Mivel az alapja integer, amúgy sem lenne string formában olvasható, de nem is ez a cél, hanem hogy bekerüljön a h() függvénybe, ahol a jobbról nulla bájtokkal (!) kiegészített és aztán jól "meg-scramble-zött" kulcshoz hozzáfűzve ráeresztjük az SHA1-et... aztán mégegyszer egy másik pad bájttal.

    Olvasd el a linkelt szabványt... illetve az abban hivatkozott RFC 4226 és RFC 2104 szabványokat, utóbbiban lesz jobban kifejtve ez a fenti eljárás. :)

  • Inhouse

    őstag

    Szia cinemazealot !

    Engem is foglalkoztatott, hogy megcsinálom. Nem vagyok PHP programozó, a legfejlettebb nyelv a Harbour, amiben dolgozok, ez egy Clippert magában foglaló modernizált C alapú nyelv.
    Gondoltam elegendő lesz, vannak fejlett szövegfeldolgozó függvények, számrendszer konvertálás, sha1, stb...
    Néztem a kódod, elakadtam a hex2bin() függvénynél. Azt látom, hogy ez nem az aminek a neve mutatná, hanem a hexa adatból ASCII karakterek lesznek. De a netes példákban nem kezdődik 9 nullával, mint itt. Van egy ilyen függvény HB-ban, HB_HexToStr(), de az fogja és végigmegy, és minden pár byte-ból próbál karaktert gyártani. Ezért az őeredménye a 0x00-k miatt üresekkel kezdődik, aztán a 0x03 egy szivecske 1250-es kódkészletnél, stb...
    Példa:
    PHP:
    000000000373f2a7 -> s�
    HB:
    000000000373f2a7 ->     ♥sň§
    Weben próbáltam https://onecompiler.com/php/-el. Miért csak a 0x73(s) és az 0xF2(ň) van látszólag benne. Gondolom a 0xF2 azért más, mert erre nincs felkészítve a webes felület megjelenítésben, vagy más a kódlap...

    [ Szerkesztve ]

    Üdv Inhouse

  • cinemazealot

    addikt

    LOGOUT blog

    válasz PSti #1 üzenetére

    Szuper, örülök. :R

    #2 ekkold
    Olvastam a megoldásodról, irigykedtem is, hogy megelőztél. :D De azon túl, hogy egy bagatell problémán akadtam fenn, én kizárólag a TOTP magjára fókuszáltam. Aztán ha valaki olyan összkomfortos megoldást akar, mint a Tiéd, az legfeljebb körülbástyázza az áhított védelmi és kényelmi funkciókkal. :R

    #3 Doky586
    Nem beszélve arról a pár ezer forintos kínai switch-ről, amit a minap tettem a hálózatra. ;)

  • Doky586

    nagyúr

    LOGOUT blog

    "Az igazsághoz persze hozzátartozik, hogy ez a program is behúz egy 3. fél által gyártott kódot, ami"...
    A teljes bizonyossághoz még ellenőrizni kellene a
    - windows forráskódját
    - a böngésző forráskódját
    - a hálókártya driver forráskódját
    - minden mást is ami futhat...
    Hogy teljes biztonságban legyünk. :DD :DD :DD

  • ekkold

    őstag

    válasz PSti #1 üzenetére

    Hasonlót gyártottam én is, persze jelszavas védelemmel kiegészítve.
    Több kulcsot kezel, és egy táblázatban jeleníti meg, valahogy így:

    Hogy ne kelljen külső dekódert használni azt is beemeltem a php kódba:
    $map = array(
    'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', // 7
    'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', // 15
    'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', // 23
    'Y', 'Z', '2', '3', '4', '5', '6', '7', // 31
    '=' // padding char
    );

    $flippedMap = array(
    'A'=>'0', 'B'=>'1', 'C'=>'2', 'D'=>'3', 'E'=>'4', 'F'=>'5', 'G'=>'6', 'H'=>'7',
    'I'=>'8', 'J'=>'9', 'K'=>'10', 'L'=>'11', 'M'=>'12', 'N'=>'13', 'O'=>'14', 'P'=>'15',
    'Q'=>'16', 'R'=>'17', 'S'=>'18', 'T'=>'19', 'U'=>'20', 'V'=>'21', 'W'=>'22', 'X'=>'23',
    'Y'=>'24', 'Z'=>'25', '2'=>'26', '3'=>'27', '4'=>'28', '5'=>'29', '6'=>'30', '7'=>'31'
    );
    function decode($input) {
    global $map;
    global $flippedMap;

    if(empty($input)) return;
    $paddingCharCount = substr_count($input, $map[32]);
    $allowedValues = array(6,4,3,1,0);
    if(!in_array($paddingCharCount, $allowedValues)) return false;
    for($i=0; $i<4; $i++){
    if($paddingCharCount == $allowedValues[$i] &&
    substr($input, -($allowedValues[$i])) != str_repeat($map[32], $allowedValues[$i])) return false;
    }
    $input = str_replace('=','', $input);
    $input = str_split($input);
    $binaryString = "";
    for($i=0; $i < count($input); $i = $i+8) {
    $x = "";
    if(!in_array($input[$i], $map)) return false;
    for($j=0; $j < 8; $j++) {
    $x .= str_pad(base_convert($flippedMap[@$input[$i + $j]], 10, 2), 5, '0', STR_PAD_LEFT);
    }
    $eightBits = str_split($x, 8);
    for($z = 0; $z < count($eightBits); $z++) {
    $binaryString .= ( ($y = chr(base_convert($eightBits[$z], 2, 10))) || ord($y) == 48 ) ? $y:"";
    }
    }

    return $binaryString;
    }//fv
    /*
    function encode($input, $padding = true) {
    global $map;
    global $flippedMap;
    if(empty($input)) return "";
    $input = str_split($input);
    $binaryString = "";
    for($i = 0; $i < count($input); $i++) {
    $binaryString .= str_pad(base_convert(ord($input[$i]), 10, 2), 8, '0', STR_PAD_LEFT);
    }
    $fiveBitBinaryArray = str_split($binaryString, 5);
    $base32 = "";
    $i=0;
    while($i < count($fiveBitBinaryArray)) {
    $base32 .= $map[base_convert(str_pad($fiveBitBinaryArray[$i], 5,'0'), 2, 10)];
    $i++;
    }
    if($padding && ($x = strlen($binaryString) % 40) != 0) {
    if($x == 8) $base32 .= str_repeat($map[32], 6);
    else if($x == 16) $base32 .= str_repeat($map[32], 4);
    else if($x == 24) $base32 .= str_repeat($map[32], 3);
    else if($x == 32) $base32 .= $map[32];
    }
    return $base32;
    }
    */

    [ Szerkesztve ]

    http://skory.gylcomp.hu/ http://www.skory.z-net.hu/ https://skori.hu/ https://skori.spacetechnology.net/

Még van hozzászólás! Tovább