<?php

// возвращает md5 hex конкатеированных строк как десятичное число.
function H(array $args) {
    $string = '';
    foreach($args as $argument) {
        $string .= $argument . ':';
    }
    return bchexdec(md5($string));
}

// hexdex для больших чисел (взято из комментариев на php.net).
function bchexdec($hex) {
    if(strlen($hex) == 1) {
        return hexdec($hex);
    } else {
        $remain = substr($hex, 0, -1);
        $last = substr($hex, -1);
        return bcadd(bcmul(16, bchexdec($remain)), hexdec($last));
    }
}

// шаг 1: подготовка.

  # логин, пароль
$I = 'Filli';
$p = 'Vanilli';

  # жуткое число — safe prime
$N ='00:c0:37:c3:75:88:b4:32:98:87:e6:1c:2d:a3:32:4b:1b:a4:b8:1a:63:f9:74:8f:ed:2d:8a:41:0c:2f:c2:1b:12:32:f0:d3:bf:a0:24:27:6c:fd:88:44:81:97:aa:e4:86:a6:3b:fc:a7:b8:bf:77:54:df:b3:27:c7:20:1f:6f:d1:7f:d7:fd:74:15:8b:d3:1c:e7:72:c9:f5:f8:ab:58:45:48:a9:9a:75:9b:5a:2c:05:32:16:2b:7b:62:18:e8:f1:42:bc:e2:c3:0d:77:84:68:9a:48:3e:09:5e:70:16:18:43:79:13:a8:c3:9c:3d:d0:d4:ca:3c:50:0b:88:5f:e3';
$N = bchexdec($N);

  # генератор
$g = 2;

  # соль
$s = bcmod(bchexdec(md5(rand(1000000000, 2147483647))), $N);

  # "защита"
$k = H([$N, $g]);

  # секрет из пароля, хранимый на сервере
$x = H([$I, $p, $s]);
$v = bcpowmod($g, $x, $N);

// шаг 2: обмен значениями

  # клиент
$a = bcmod(bchexdec(md5(rand(1000000000, 2147483647))), $N);
$A = bcpowmod($g, $a, $N);

  # сервер
$b = bcmod(bchexdec(md5(rand(1000000000, 2147483647))), $N);
$B = bcmod((bcadd(bcmul($k, $v), bcpowmod($g, $b, $N))), $N);

  # это вычисляют оба
$u = H([$A, $B]);

  # клиент
$Sc = bcpowmod(bcsub($B, bcmul($k, bcpowmod($g, $x, $N))), bcadd($a, bcmul($u, $x)), $N);
$Kc = H([$Sc]);

  # сервер
$Ss = bcpowmod(bcmul($A, bcpowmod($v, $u, $N)), $b, $N);
$Ks = H([$Ss]);

// результаты

var_dump([
  'N' => $N,
  'g' => $g,
  's' => $s,
  'k' => $k,
  'x' => $x,
  'v' => $v,
  'a' => $a,
  'A' => $A,
  'b' => $b,
  'B' => $B,
  'u' => $u,
  'Sc' => $Sc,
  'Ss' => $Ss,
  'Kc' => $Kc,
  'Ks' => $Ks
]);