> 4) & 0xf]; $encoded .= ModHex::$TRANSKEY[ (int)$bin & 0xf]; } return $encoded; } // ModHex decodes the string $token. Returns the decoded string if successful, // or zero if an encoding error was found. static function decode($token) { $tokLen = strlen($token); // length of the token $decoded = ""; // decoded string to be returned // strings must have an even length if ( $tokLen % 2 != 0 ) { return FALSE; } for ($i = 0; $i < $tokLen; $i=$i+2 ) { $high = strpos(ModHex::$TRANSKEY, $token[$i]); $low = strpos(ModHex::$TRANSKEY, $token[$i+1]); // if there's an invalid character in the encoded $token, fail here. if ( $high === FALSE || $low === FALSE ) return FALSE; $decoded .= chr(($high << 4) | $low); } return $decoded; } } /* * Class Yubikey * This class does most of the hard work to give you useable data. * * Please refer to the documentation for further information on usage. * */ class Yubikey { // Some magic numbers for processing the keys const OTP_STRING_LENGTH = 32; // # of characters in the encrypted token of the OTP const UID_SIZE = 12; // # of characters in the private ID const CRC_OK_RESIDUE = 0xf0b8; // Error codes const ERROR_TOO_SHORT = 1; const ERROR_BAD_CRC = 2; const ERROR_MODHEX_FAILED = 3; // Decrypts a ModHexed one-time-password $ModHexOTP with the given $key static function decrypt_otp($ModHexOTP, $key) { $aes = new AES128(true); $decoded = ModHex::Decode($ModHexOTP); if ( $decoded === FALSE ) return FALSE; return $aes->blockDecrypt($decoded, $aes->makeKey($key)); } static function calculate_crc($token) { $crc = 0xffff; for ($i = 0; $i < 16; $i++ ) { $b = hexdec($token[$i*2].$token[($i*2)+1]); $crc = $crc ^ ($b & 0xff); for ($j = 0; $j < 8; $j++) { $n = $crc & 1; $crc = $crc >> 1; if ( $n != 0) { $crc = $crc ^ 0x8408; } } } return $crc; } static function crc_is_good($token) { global $trace; $crc = Yubikey::calculate_crc($token); if ($trace) echo 'Calculated CRC='.$crc."\n"; return $crc == Yubikey::CRC_OK_RESIDUE; } /** * Returns the public ID of the given ModHexed one-time-password */ static function get_public_id($otp) { return substr($otp, 0, strlen($otp) - Yubikey::OTP_STRING_LENGTH); } /** * Decode */ static function decode( $otp, $key ) { global $trace; $otpLen = strlen($otp); $decoded = array(); // hold our return values // if the $otp is longer than 32, assume the extra characters // are the yubikey's unchanging public ID. If the $otp is too short, // return immediately. if ($otpLen > Yubikey::OTP_STRING_LENGTH) { $decoded["public_id"] = substr($otp, 0, $otpLen - Yubikey::OTP_STRING_LENGTH); } elseif ( $otpLen < Yubikey::OTP_STRING_LENGTH ) { if ($trace) echo 'OTP too short'; return Yubikey::ERROR_TOO_SHORT; } // Decrypt the token (the last 32 characters of the $otp) $decoded["token"] = Yubikey::decrypt_otp(substr($otp, 0-Yubikey::OTP_STRING_LENGTH), $key); if ( $decoded["token"] === FALSE ) { if ($trace) echo 'Decryption failure'; return Yubikey::ERROR_MODHEX_FAILED; } //// Raw values extracted from the decoded OTP // // Private ID $start = 0; $decoded["private_id"] = substr($decoded["token"], $start, Yubikey::UID_SIZE); $start += Yubikey::UID_SIZE; // Session Counter $scounter = hexdec(substr($decoded["token"], $start+2, 2).substr($decoded["token"], $start, 2)) & 0xffff; $start += 4; $decoded['session_counter'] = $scounter; if ($trace) echo 'Sess ctr='.$scounter."\n"; // Time stamp LOW $timelow = hexdec(substr($decoded["token"], $start+2, 2).substr($decoded["token"], $start, 2)) & 0xffff; $start += 4; $decoded["low"] = $timelow; if ($trace) echo 'TS lo='.$timelow."\n"; // Time stamp HIGH $timehigh = hexdec(substr($decoded["token"], $start, 2)) & 0xff; $start += 2; $decoded["high"] = $timehigh; if ($trace) echo 'TS hi='.$timehigh."\n"; // Session Use $session_use = hexdec(substr($decoded["token"], $start, 2)) & 0xff; $start += 2; $decoded["session_use"] = $session_use; if ($trace) echo 'Use='.$session_use."\n"; // Randomness - No need to return this $decoded["random"] = hexdec(substr($decoded["token"], $start+2, 2).substr($decoded["token"], $start, 2)); $start += 4; if ($trace) echo 'Rand='.$decoded["random"]."\n"; // CRC $decoded["crc"] = hexdec(substr($decoded["token"], $start+2, 2).substr($decoded["token"], $start, 2)); if ($trace) echo 'CRC='.$decoded["crc"]."\n"; //// Derived values for convenient use // // Full time stamp $decoded["timestamp"] = ($timehigh << 16) + $timelow; if ($trace) echo 'Timestamp='.$decoded["timestamp"]."\n"; // Session Counter $decoded["counter"] = ($scounter<<8) + $session_use; if ( Yubikey::crc_is_good($decoded["token"]) ) { if ($trace) echo "Good CRC\n"; return $decoded; } if ($trace) echo "Bad CRC\n"; return Yubikey::ERROR_BAD_CRC; } } ?>