Пример #1
0
/**
 * HMAC-Based One-Time Password Algorithm
 * @see RFC 4226
 * @param $key shared secret, treated as binary
 * @param $counter 8-byte counter
 * [@param $digits = 6] Length of the output code
 * [@param $algorithm = 'sha1'] HMAC algorithm - sha1, sha256, and sha512 permitted
 * @return string n-character numeric code
 */
function HOTP(Secret $key, int $counter, int $digits = 6, string $algorithm = 'sha1') : string
{
    if (!in_array($algorithm, ['sha1', 'sha256', 'sha512'])) {
        throw new OutOfRangeException('Unexpected algorithm');
    }
    if ($digits < 6 || $digits > 8) {
        // "Implementations MUST extract a 6-digit code at a minimum and
        // possibly 7 and 8-digit code."
        throw new LengthException('RFC4226 requires a 6 to 8-digit output');
    }
    if (strlen($key->reveal()) < 128 / 8) {
        throw new LengthException('Key must be at least 128 bits long (160+ recommended)');
    }
    $counter = pack('J', $counter);
    // Convert to 8-byte string
    $hash = hash_hmac($algorithm, $counter, $key->reveal(), true);
    // Determine the offset: get the last nibble of the hash output
    $offset = ord(substr($hash, -1)) & 0xf;
    // Index into the hash output by $offset bytes, take four bytes
    $dbc1 = unpack('N', substr($hash, $offset, 4))[1];
    // Mask out the high bit (per the spec, avoids signed/unsigned issues)
    $dbc2 = $dbc1 & 0x7fffffff;
    // Use the last $digits by using modulo 10^digits
    $code = (string) ($dbc2 % pow(10, $digits));
    // Finally, prepend zeroes to match the string length
    return str_pad($code, $digits, '0', \STR_PAD_LEFT);
}
Пример #2
0
 private function sign(Secret $key)
 {
     $alg = $this->headers['alg'];
     // DEFAULT?
     $payload = self::b64encode($this->headers) . '.' . self::b64encode($this->claims);
     switch ($alg) {
         case Algorithm::NONE:
             $data = '';
             break;
         case Algorithm::HMAC_SHA_256:
             $data = hash_hmac('SHA256', $payload, $key->reveal(), true);
             break;
         case Algorithm::HMAC_SHA_384:
             $data = hash_hmac('SHA384', $payload, $key->reveal(), true);
             break;
         case Algorithm::HMAC_SHA_512:
             $data = hash_hmac('SHA512', $payload, $key->reveal(), true);
             break;
         default:
             throw new Exception("Unsupported algorithm");
             // use openssl_sign and friends to do the signing
     }
     return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
 }