/core/OneTimePassword/OneTimePasswordGenerator.class.php
PHP | 414 lines | 89 code | 41 blank | 284 comment | 9 complexity | 75cb72406323d31ab1a8b503f24ebf9f MD5 | raw file
Possible License(s): LGPL-2.0
- <?php
- /**
- * M_OneTimePasswordGenerator
- *
- * @package Core
- */
- class M_OneTimePasswordGenerator extends M_OneTimePasswordSystem {
- /* -- PROPERTIES -- */
- /**
- * Seed
- *
- * The seed is provided by the One-Time Password Server, as part of the
- * challenge.
- *
- * @see M_OneTimePasswordGenerator::setChallenge()
- * @access private
- * @var string
- */
- private $_seed;
- /**
- * Algorithm
- *
- * The algorithm, or hash function, is provided by the One-Time Password
- * Server, as part of the challenge.
- *
- * @see M_OneTimePasswordGenerator::setChallenge()
- * @see M_OneTimePasswordHelper::getSupportedHashFunctions()
- * @access private
- * @var string
- */
- private $_hashFunction;
- /**
- * Sequence number
- *
- * The sequence number is provided by the One-Time Password Server, as part
- * of the challenge.
- *
- * @see M_OneTimePasswordGenerator::setChallenge()
- * @access private
- * @var integer
- */
- private $_sequenceNumber;
- /**
- * Pass-phrase
- *
- * The secret pass-phrase is known only to this object. For more information,
- * read the docs on {@link M_OneTimePasswordGenerator::setPassPhrase()}
- *
- * @see M_OneTimePasswordGenerator::setPassPhrase()
- * @access private
- * @var string
- */
- private $_passPhrase;
- /**
- * One-Time Password
- *
- * This property stores the one-time password that is generated by this object,
- * with {@link M_OneTimePasswordGenerator::getOneTimePassword()}.
- *
- * @see M_OneTimePasswordGenerator::getOneTimePassword()
- * @see M_OneTimePasswordGenerator::getOneTimePasswordHexadecimal()
- * @see M_OneTimePasswordGenerator::getOneTimePasswordInDictionaryWords()
- * @access private
- * @var string
- */
- private $_oneTimePassword;
- /* -- CONSTRUCTOR -- */
- /**
- * Constructor
- *
- * @uses M_OneTimePasswordGenerator::setChallenge()
- * @access public
- * @param string $otpServerChallenge
- * The challenge, provided by the server
- * @return M_OneTimePasswordGenerator
- */
- public function __construct($otpServerChallenge = NULL) {
- // If a challenge has been provided to the constructor:
- if($otpServerChallenge) {
- // Then, we set the challenge in the generator object:
- $this->setChallenge($otpServerChallenge);
- }
- }
- /* -- SETTERS -- */
- /**
- * Set the challenge, provided by the One-Time Password Server
- *
- * The seed is provided by the One-Time Password Server, as part of the
- * challenge. The pass-phrase, known only to the One-Time Password Generator,
- * is concatenated with a seed that is transmitted from the server in clear
- * text. This non-secret seed allows clients to use the same secret pass-phrase
- * on multiple machines (using different seeds) and to safely recycle their
- * secret pass-phrases by changing the seed.
- *
- * The sequence number and seed together constitute a larger unit of data
- * called the challenge. The challenge gives the generator the parameters it
- * needs to calculate the correct one-time password from the secret
- * pass-phrase. The challenge MUST be in a standard syntax so that automated
- * generators can recognize the challenge in context and extract these
- * parameters. The syntax of the challenge is:
- *
- * <code>otp-<algorithm identifier> <sequence integer> <seed></code>
- *
- * The three tokens MUST be separated by a white space (defined as any number
- * of spaces and/or tabs) and the entire challenge string MUST be terminated
- * with either a space or a new line. The string "otp-" MUST be in lower case.
- * The algorithm identifier is case sensitive (the existing identifiers are
- * all lower case), and the seed is case insensitive and converted before use
- * to lower case.
- *
- * An example of a valid OTP challenge would be:
- *
- * <code>otp-sha1 487 dog2</code>
- *
- * NOTE:
- * This method will throw an exception, if the provided challenge could not
- * be parsed correctly due to a corrupted format.
- *
- * @throws M_OneTimePasswordException
- * @access public
- * @param string $otpServerChallenge
- * @return M_OneTimePasswordGenerator $generator
- * Returns itself, for a fluent programming interface
- */
- public function setChallenge($otpServerChallenge) {
- // We split up the provided challenge string into 3 pieces: the algorithm,
- // the sequence number, and the seed:
- $parts = preg_split('/[\s]+/m', M_Helper::trimCharlist($otpServerChallenge));
- // We make sure that exactly 3 parts have been included in the provided
- // challenge:
- if(count($parts) != 3) {
- // If that's not the case, then the provided challenge is not a valid
- // One-Time Password Server Challenge. We throw an exception, to inform
- // about the error:
- throw new M_OneTimePasswordException(sprintf(
- 'Could not parse the One-Time Password Server Challenge "%s". ' .
- 'Expecting exactly 3 elements in the challenge: the algorithm ' .
- '(eg. otp-sha1), the sequence number (eg 471) and the seed (eg dog2)',
- $otpServerChallenge
- ));
- }
- // If we are still here, then we know that the challenge does contain
- // exactly 3 elements. We extract the hash function name:
- $hashFunction = substr($parts[0], 4);
- // We set the internals for the generator:
- $this
- ->_setHashFunction($hashFunction)
- ->_setSeed($parts[2])
- ->_setSequenceNumber((int) $parts[1]);
- // Returns itself:
- return $this;
- }
- /**
- * Set secret pass-phrase
- *
- * The generator must produce the appropriate one-time password from the
- * user's secret pass-phrase and from information provided in the challenge
- * from the server.
- *
- * In principle, the user's secret pass-phrase may be of any length. To
- * reduce the risk from techniques such as exhaustive search or dictionary
- * attacks, character string pass-phrases MUST contain at least 10 characters.
- * All implementations MUST support a pass-phrases of at least 63 characters.
- * The secret pass-phrase is frequently, but is not required to be, textual
- * information provided by a user.
- *
- * The secret pass-phrase is seen only by the OTP generator. To allow
- * interchangeability of generators, all generators MUST support a secret
- * pass-phrase of 10 to 63 characters. Implementations MAY support a longer
- * pass-phrase, but such implementations risk the loss of interchangeability
- * with implementations supporting only the minimum.
- *
- * NOTE:
- * This method will comply with the RFC 2289 specifications, and make sure
- * that the provided pass-phrase adheres to the requirements. If the pass-
- * phrase does not, an exception will be thrown.
- *
- * @throws M_OneTimePasswordException
- * @access public
- * @param string $passPhrase
- * @return M_OneTimePasswordGenerator $generator
- * Returns itself, for a fluent programming interface
- */
- public function setPassPhrase($passPhrase) {
- // We make sure that the provided passPhrase is a string, of at least
- // 10 characters long:
- $isValid = ((is_string($passPhrase) && strlen($passPhrase) >= 10));
- // If that is not the case:
- if(! $isValid) {
- // We throw an exception, to inform about the error
- throw new M_OneTimePasswordException(sprintf(
- 'The pass-phrase "%s" cannot be used to generate one-time passwords. ' .
- 'The pass-phrase should be at least 10 characters long!',
- $passPhrase
- ));
- }
- // Set the pass-phrase
- $this->_passPhrase = $passPhrase;
- // Returns itself
- return $this;
- }
- /* -- GETTERS -- */
- /**
- * Get One-Time Password in hexadecimal output
- *
- * To facilitate the implementation of smaller generators, hexadecimal output
- * is an acceptable alternative for the presentation of the one-time password.
- * According to the RFC 2289 specifications, all implementations of the server
- * software MUST accept case-insensitive hexadecimal as well as six-word
- * format. The hexadecimal digits may be separated by white space so servers
- * are REQUIRED to ignore all white space. If the representation is partitioned
- * by white space, leading zeros must be retained.
- *
- * Examples of hexadecimal format:
- * <code>
- * Representation Value
- * 3503785b369cda8b 0x3503785b369cda8b
- * e5cc a1b8 7c13 096b 0xe5cca1b87c13096b
- * C7 48 90 F4 27 7B A1 CF 0xc74890f4277ba1cf
- * 47 9 A68 28 4C 9D 0 1BC 0x479a68284c9d01bc
- * </code>
- *
- * @access public
- * @return string $hex
- * The one-time password, in hexadecimal representation
- */
- public function getOneTimePasswordHexadecimal() {
- // Convert binary data into hexadecimal representation:
- return bin2hex($this->_getOneTimePassword());
- }
- /**
- * Get One-Time Password in dictionary words
- *
- * The one-time password generated is 64 bits in length. Entering a 64 bit
- * number is a difficult and error prone process. Some generators insert this
- * password into the input stream and some others make it available for
- * system "cut and paste." Still other arrangements require the one-time
- * password to be entered manually. The OTP system is designed to facilitate
- * this manual entry without impeding automatic methods. The one-time password
- * therefore MAY be converted to, and all servers MUST be capable of accepting
- * it as, a sequence of six short (1 to 4 letter) easily typed words that
- * only use characters from ISO-646 IVCS. Each word is chosen from a dictionary
- * of 2048 words; at 11 bits per word, all one-time passwords may be encoded.
- *
- * Generators that produce the six-word format MUST present the words in
- * upper case with single spaces used as separators. All servers MUST accept
- * six-word format without regard to case and white space used as a separator.
- *
- * @access public
- * @return string $words
- * The one-time password, in dictionary words
- */
- public function getOneTimePasswordInDictionaryWords() {
- // Output
- $out = '';
- // First we get the bit representation of the one-time password:
- $bitString = $this->getOneTimePasswordInBits();
-
- // The bit representation is one of 64 bits. Two extra bits are foreseen
- // for a two-bit checksum (needed by the otp server). The result is a
- // bit string representation of 66 bits, or 11 bits per word.
- $bitString .= $this->_getBitStringChecksum($bitString);
- // We get the dictionary. This dictionary may either be an alternative one,
- // or the default one...
- $dictionary = $this->getDictionary();
- // We run through the bit string in chunks of 11 bits. Dividing 66 by 11,
- // we know that we will need 6 loops to do so:
- for($i = 0; $i < 6; $i ++) {
- // Each chunk of 11 bits can be converted to its decimal equivalent.
- // This number will be used as a key, to look up the corresponding
- // word from the dictionary array. So, first we get the decimal number:
- $number = bindec(substr($bitString, ($i * 11), 11));
- // Then, we add the word from the dictionary:
- $out .= (empty($out) ? '' : ' ') . $dictionary[$number];
- }
- // We return the one-time password, in words:
- return $out;
- }
- /**
- *
- */
- public function getOneTimePasswordInDecimals() {
- // Output
- $out = '';
- // First we get the bit representation of the one-time password:
- $bitString = $this->getOneTimePasswordInBits();
- // The bit representation is one of 64 bits. Two extra bits are foreseen
- // for a two-bit checksum (needed by the otp server). The result is a
- // bit string representation of 66 bits, or 11 bits per number.
- $bitString .= $this->_getBitStringChecksum($bitString);
- // We run through the bit string in chunks of 11 bits. Dividing 66 by 11,
- // we know that we will need 6 loops to do so:
- for($i = 0; $i < 6; $i ++) {
- // Each chunk of 11 bits can be converted to its decimal equivalent.
- // This number will is typically used as a key, to look up the
- // corresponding word from the dictionary array. However, in this
- // format, we will use the number as-is...
- $out .= (empty($out) ? '' : ' ') . bindec(substr($bitString, ($i * 11), 11));
- }
- // We return the one-time password, in numbers:
- return $out;
- }
- /**
- *
- */
- public function getOneTimePasswordInBits() {
- return $this->_getBitStringFromHexadecimal($this->getOneTimePasswordHexadecimal());
- }
- /* -- PRIVATE/PROTECTED -- */
- /**
- * Get secret pass-phrase
- *
- * @access protected
- * @return string
- */
- protected function _getPassPhrase() {
- return $this->_passPhrase;
- }
- /**
- * Get One-Time Password
- *
- * The generator must produce the appropriate one-time password from the
- * user's secret pass-phrase and from information provided in the challenge
- * from the server.
- *
- * This method will produce the *binary* one-time password, and return it
- * as result. This method is used to produce the one-time password in other
- * formats, by:
- *
- * - {@link M_OneTimePasswordGenerator::getOneTimePasswordHexadecimal()}
- * - {@link M_OneTimePasswordGenerator::getOneTimePasswordInDictionaryWords()}
- * - {@link M_OneTimePasswordGenerator::getOneTimePasswordInDecimals()}
- * - {@link M_OneTimePasswordGenerator::getOneTimePasswordInBits()}
- *
- * @see M_OneTimePasswordGenerator::setChallenge()
- * @see M_OneTimePasswordGenerator::setPassPhrase()
- * @access protected
- * @return string
- */
- protected function _getOneTimePassword() {
- // If not requested before:
- if(! $this->_oneTimePassword) {
- // The hash function that is to be used:
- $hashFunction = $this->getHashFunction();
- // The first step is to concatenate the pass phrase with the seed. The
- // output of this step is called S
- $otp = $this->getSeed() . $this->_getPassPhrase();
- // A sequence of one-time passwords is produced by applying the secure
- // hash function multiple times to S. That is, the first one-time password
- // to be used is produced by passing S through the secure hash function
- // a number of times (N) specified by the user. The next one-time
- // password to be used is generated by passing S though the secure hash
- // function N-1 times.
- // For each of the times that S should be passed through the hash function:
- for($i = 0, $n = $this->getSequenceNumber(); $i <= $n; $i ++) {
- // We digest the current S, with the hash function:
- switch($hashFunction) {
- // SHA1:
- case 'sha1':
- $otp = $this->_getSha1($otp);
- break;
- // MD5:
- case 'md5':
- $otp = $this->_getMd5($otp);
- break;
- }
- }
- // Set the final one-time password:
- $this->_oneTimePassword = $otp;
- }
- // Return the one-time password:
- return $this->_oneTimePassword;
- }
- }